mirror of
https://github.com/github/spec-kit.git
synced 2026-07-07 22:56:57 +08:00
Compare commits
401 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6fca5d83b2 | ||
|
|
465acd9024 | ||
|
|
04fc3fd1ba | ||
|
|
24d76b5d92 | ||
|
|
0f7d04b12b | ||
|
|
9402ebd00a | ||
|
|
d410d188fc | ||
|
|
686c91f94e | ||
|
|
22036732d8 | ||
|
|
c78f8423f6 | ||
|
|
76cca34293 | ||
|
|
9a1e3037b0 | ||
|
|
f14a47ea7d | ||
|
|
36d97235ad | ||
|
|
4afbd87abb | ||
|
|
b562438df9 | ||
|
|
9111699cd2 | ||
|
|
0049b1cdc2 | ||
|
|
3e883fa32c | ||
|
|
baeb829ed4 | ||
|
|
236bcb3987 | ||
|
|
6c3d698959 | ||
|
|
4d806672d6 | ||
|
|
1c7b09d947 | ||
|
|
3963c2ef10 | ||
|
|
84e1a69edc | ||
|
|
6f22393106 | ||
|
|
5a39f63d1e | ||
|
|
964b1418a3 | ||
|
|
cad12bd2c8 | ||
|
|
8d552e6d11 | ||
|
|
ad3bb1a5fe | ||
|
|
a998d13b73 | ||
|
|
418950ad07 | ||
|
|
952e2bb5f8 | ||
|
|
56085f9323 | ||
|
|
dadda123f0 | ||
|
|
b4e1c07817 | ||
|
|
bb21eedabc | ||
|
|
f65bf6ccb7 | ||
|
|
a0ca101aa4 | ||
|
|
bf5ae42085 | ||
|
|
33df8976ca | ||
|
|
41a9fc8859 | ||
|
|
71a20eb787 | ||
|
|
798015b537 | ||
|
|
42086045cf | ||
|
|
bf6d9e8baf | ||
|
|
694de562cc | ||
|
|
45d5176d07 | ||
|
|
537f349f1b | ||
|
|
15139c1e0f | ||
|
|
f205fa3b58 | ||
|
|
4767d77ab5 | ||
|
|
c88d00d452 | ||
|
|
cf8b67e187 | ||
|
|
7c4294b163 | ||
|
|
2f58f4b9f0 | ||
|
|
7777e145d9 | ||
|
|
7e568c1201 | ||
|
|
f4fcd82920 | ||
|
|
3dcbb6e3a9 | ||
|
|
392f8b7dfa | ||
|
|
24b6d31471 | ||
|
|
960e4c0a32 | ||
|
|
df15b8e2a2 | ||
|
|
161a415274 | ||
|
|
e20d79e78b | ||
|
|
a26ee578ae | ||
|
|
d471a6fa42 | ||
|
|
bcd3f8468a | ||
|
|
d79d99f0a9 | ||
|
|
57c54d3f0a | ||
|
|
e976080cbf | ||
|
|
f438a10c7c | ||
|
|
3c4081d30f | ||
|
|
392dbf20c4 | ||
|
|
d068e1c040 | ||
|
|
0369d2a50e | ||
|
|
de2cdc633d | ||
|
|
891736ffc8 | ||
|
|
02015e512a | ||
|
|
3110452c3f | ||
|
|
c4638a936e | ||
|
|
2a7c2e9398 | ||
|
|
e6d6f3cdee | ||
|
|
598148ca67 | ||
|
|
b40b41cf50 | ||
|
|
1f3d9b5fdd | ||
|
|
f7fe48bd6a | ||
|
|
d6136cb22f | ||
|
|
dafab39483 | ||
|
|
09274437fc | ||
|
|
5f1fc6b445 | ||
|
|
779e1f8afd | ||
|
|
177dcadd8c | ||
|
|
ba861cd165 | ||
|
|
926836e0fc | ||
|
|
af88930ffc | ||
|
|
89f5f9c0b9 | ||
|
|
9809b1a4ab | ||
|
|
7b536b578d | ||
|
|
7522eb3f9d | ||
|
|
d550634d8e | ||
|
|
72cb885eb7 | ||
|
|
a877af5575 | ||
|
|
2508d926c0 | ||
|
|
9f123e013a | ||
|
|
60bd9dc849 | ||
|
|
e77d99abd2 | ||
|
|
eb030dab19 | ||
|
|
be06a23fd7 | ||
|
|
d4d3139d5f | ||
|
|
65f8787b48 | ||
|
|
9786e588b7 | ||
|
|
0ac76c8c7e | ||
|
|
79328aa38d | ||
|
|
f7903192a8 | ||
|
|
115b4335d9 | ||
|
|
37e87c78a0 | ||
|
|
14a574a6a8 | ||
|
|
dbd1437aea | ||
|
|
317ae4dad9 | ||
|
|
8e9d25e9be | ||
|
|
c59be99dc4 | ||
|
|
15a5630047 | ||
|
|
f3ada747cf | ||
|
|
cbc8ab020c | ||
|
|
e0e62f6757 | ||
|
|
6c22085214 | ||
|
|
02c1549f80 | ||
|
|
ab14090813 | ||
|
|
546e9d6617 | ||
|
|
33a07969c3 | ||
|
|
8de5db7a3e | ||
|
|
ea90d02c41 | ||
|
|
3e85f46465 | ||
|
|
015440838a | ||
|
|
7050a3151c | ||
|
|
9e84f46e56 | ||
|
|
5e32de1f3f | ||
|
|
5558b24475 | ||
|
|
f892b9e1cb | ||
|
|
a66af9b7f5 | ||
|
|
a5fdd53a3e | ||
|
|
ed0fa8fffe | ||
|
|
098380a46f | ||
|
|
74f7e508a4 | ||
|
|
8130d98bcc | ||
|
|
315269d9a8 | ||
|
|
71c2c63d55 | ||
|
|
b37a9516d0 | ||
|
|
b009773d5c | ||
|
|
a8514da3e8 | ||
|
|
9d4e8e9eb9 | ||
|
|
f3c77e2f4f | ||
|
|
ecec4bc5e0 | ||
|
|
900bc2ed68 | ||
|
|
03c7021270 | ||
|
|
a97374ded0 | ||
|
|
6b58824a39 | ||
|
|
b291a6efb0 | ||
|
|
3b000fce4d | ||
|
|
c59595d065 | ||
|
|
1c16a68df2 | ||
|
|
39bf3e4d9a | ||
|
|
045696641a | ||
|
|
41690cd1d4 | ||
|
|
e45c469709 | ||
|
|
8c9e586662 | ||
|
|
ce844c6259 | ||
|
|
84b46cd1b9 | ||
|
|
0cca67fcd2 | ||
|
|
66fc4c292d | ||
|
|
2baae57b26 | ||
|
|
514b0548fe | ||
|
|
be7db635cc | ||
|
|
a945077b8d | ||
|
|
7b55522213 | ||
|
|
7ca792509b | ||
|
|
4522fb4c44 | ||
|
|
36ff7e6505 | ||
|
|
defb1870da | ||
|
|
b61f04c898 | ||
|
|
64745162df | ||
|
|
97df98b9a0 | ||
|
|
36383b411f | ||
|
|
3e476c2ba6 | ||
|
|
654a00aac9 | ||
|
|
4690d13f88 | ||
|
|
e7bb98de42 | ||
|
|
d4f5c75519 | ||
|
|
09f57a87fa | ||
|
|
b702fcbbc0 | ||
|
|
2c1de4202e | ||
|
|
e65660ffc3 | ||
|
|
f7ae5781b7 | ||
|
|
d09552fc63 | ||
|
|
ed5dbf197f | ||
|
|
df4d7fa062 | ||
|
|
b4ecd14ffa | ||
|
|
26fde7cfda | ||
|
|
8abc812c57 | ||
|
|
940714df0a | ||
|
|
f393ae9825 | ||
|
|
97dee3e4bf | ||
|
|
aec568949c | ||
|
|
ed9044345b | ||
|
|
058ee510a7 | ||
|
|
e91aca54ee | ||
|
|
9c87fdd5bb | ||
|
|
301a556110 | ||
|
|
bb9ec8e638 | ||
|
|
e83d2c777d | ||
|
|
68809bdacb | ||
|
|
3cc545243b | ||
|
|
9ef389baba | ||
|
|
426ac8ab2e | ||
|
|
4de1b6b6c3 | ||
|
|
199c63901f | ||
|
|
369ed643d7 | ||
|
|
6c947cc8d8 | ||
|
|
07d506feb5 | ||
|
|
0124a0f32e | ||
|
|
e7936c3fd0 | ||
|
|
583d556677 | ||
|
|
72ed39d8a1 | ||
|
|
7c4c1edd85 | ||
|
|
5846a38c68 | ||
|
|
89f4b0b38a | ||
|
|
af2b14e9be | ||
|
|
719ba762bc | ||
|
|
d4cce17f66 | ||
|
|
47e5f7c2e2 | ||
|
|
ec782979f0 | ||
|
|
2ab6e6590f | ||
|
|
15e5572a1d | ||
|
|
cde400f466 | ||
|
|
ba8144da89 | ||
|
|
4dc4887134 | ||
|
|
14ebde575f | ||
|
|
337e192abd | ||
|
|
efe4edc3b1 | ||
|
|
0037a3f484 | ||
|
|
a6be9bea31 | ||
|
|
e71ebbce1e | ||
|
|
4c62cab033 | ||
|
|
80b2544396 | ||
|
|
78638a9a37 | ||
|
|
5333409080 | ||
|
|
08b2a0ae55 | ||
|
|
b06f2b9f89 | ||
|
|
9e0db01b6e | ||
|
|
917f271d74 | ||
|
|
00fe1bb5a9 | ||
|
|
5499d157ab | ||
|
|
83dcb4a2f5 | ||
|
|
8374777206 | ||
|
|
a172e4cb6e | ||
|
|
265534c5ad | ||
|
|
5042c76558 | ||
|
|
e81c3b3c8d | ||
|
|
75df2999f4 | ||
|
|
23e0c5c83c | ||
|
|
3ee225f624 | ||
|
|
12b823e4c2 | ||
|
|
f430138895 | ||
|
|
7fa0cc5e70 | ||
|
|
e3b456c4c8 | ||
|
|
e04175b51e | ||
|
|
cc75a22e45 | ||
|
|
b2f749ef41 | ||
|
|
de1db34b08 | ||
|
|
aa599b8af1 | ||
|
|
09cf4f6cc4 | ||
|
|
321edbc62e | ||
|
|
fadd250a98 | ||
|
|
9ff9c9fd8d | ||
|
|
45f04abd38 | ||
|
|
1c0e7d14d5 | ||
|
|
55555eb39e | ||
|
|
6f3e450cd8 | ||
|
|
8bbacd4adb | ||
|
|
6a3e81f813 | ||
|
|
eb3c63fe0f | ||
|
|
68eba52a40 | ||
|
|
721ecc9bec | ||
|
|
df3e4930cc | ||
|
|
f3d55cff84 | ||
|
|
c6051a0b8a | ||
|
|
04cb5f00d4 | ||
|
|
e924a73625 | ||
|
|
e642acaea4 | ||
|
|
f1ddf33ac4 | ||
|
|
f1063321c6 | ||
|
|
62f20c601c | ||
|
|
26645103a6 | ||
|
|
a4b86f7571 | ||
|
|
adc1417b0f | ||
|
|
c4698b623b | ||
|
|
2c373aa47a | ||
|
|
a0faa222c8 | ||
|
|
2b2f5a7c2a | ||
|
|
c7b61f4bfd | ||
|
|
bc101a4578 | ||
|
|
9319f0425e | ||
|
|
8b09559690 | ||
|
|
5d764e8364 | ||
|
|
1db65aa6aa | ||
|
|
318b76de50 | ||
|
|
a85fdd4051 | ||
|
|
92621bca7d | ||
|
|
9348c45f9e | ||
|
|
ad746ce35d | ||
|
|
14ac43ba41 | ||
|
|
0857f83de8 | ||
|
|
18236f27d6 | ||
|
|
974347c758 | ||
|
|
e9aed2da44 | ||
|
|
ecf1757672 | ||
|
|
ceba130e52 | ||
|
|
494cdede53 | ||
|
|
bc896086f1 | ||
|
|
dceb903804 | ||
|
|
39b33ebd22 | ||
|
|
ef05d4846a | ||
|
|
026aa69aad | ||
|
|
ebf53e10b3 | ||
|
|
713af3c314 | ||
|
|
33652bf143 | ||
|
|
cef4e8f495 | ||
|
|
86aaf2daed | ||
|
|
c65b0fbb62 | ||
|
|
385d17c83c | ||
|
|
1a84b4b23c | ||
|
|
b03bba37ce | ||
|
|
2d89075106 | ||
|
|
a810b1bd1a | ||
|
|
5243137f25 | ||
|
|
8c4f348ac1 | ||
|
|
0e49e79610 | ||
|
|
93e41567d9 | ||
|
|
da60d35bc1 | ||
|
|
84b61bcd20 | ||
|
|
0672bfc6aa | ||
|
|
d682f5a164 | ||
|
|
895bcbef00 | ||
|
|
90f06521a2 | ||
|
|
d92d6f57db | ||
|
|
f9c9cd3b61 | ||
|
|
f4b16080da | ||
|
|
7c2fd502c8 | ||
|
|
5eccac5524 | ||
|
|
ee9f83929a | ||
|
|
3bdb1d9f3f | ||
|
|
0e5f7cee9a | ||
|
|
4cc15bab98 | ||
|
|
8d529599f1 | ||
|
|
406521c664 | ||
|
|
1a71b03195 | ||
|
|
f04e01d4a2 | ||
|
|
2d242b4732 | ||
|
|
84ec4611c4 | ||
|
|
2c1e1688e8 | ||
|
|
3f67cf2f5f | ||
|
|
505b956bfd | ||
|
|
0bebcf93b3 | ||
|
|
826c3a6102 | ||
|
|
e83e1cd8e3 | ||
|
|
aa08257d98 | ||
|
|
64171ec062 | ||
|
|
7c0f0a4627 | ||
|
|
219ad02e4a | ||
|
|
6f1970a0bd | ||
|
|
60b8d8fad2 | ||
|
|
2aa30cdbb2 | ||
|
|
d7d2c145c7 | ||
|
|
caee341af9 | ||
|
|
dc8fdc2dc8 | ||
|
|
6a3ff650f1 | ||
|
|
8784f39755 | ||
|
|
fbacd0b0df | ||
|
|
db9d97bcbd | ||
|
|
537332725b | ||
|
|
65ccbb62ca | ||
|
|
5659c869b5 | ||
|
|
d8bf98a88d | ||
|
|
e482072520 | ||
|
|
aaa6df9653 | ||
|
|
8c3e9db3bf | ||
|
|
d1d5c82a8e | ||
|
|
0889635e66 | ||
|
|
2825bb1247 | ||
|
|
3a0ae75bfb | ||
|
|
312703260c | ||
|
|
286ad553fd | ||
|
|
a39185c8be | ||
|
|
e29488d91f | ||
|
|
95fba17d20 | ||
|
|
b1688b9633 | ||
|
|
70413f5214 |
77
.devcontainer/devcontainer.json
Normal file
77
.devcontainer/devcontainer.json
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
|
||||||
|
// README at: https://github.com/devcontainers/templates/tree/main/src/python
|
||||||
|
{
|
||||||
|
"name": "SpecKitDevContainer",
|
||||||
|
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
|
||||||
|
"image": "mcr.microsoft.com/devcontainers/python:3.13-trixie", // based on Debian "Trixie" (13)
|
||||||
|
"features": {
|
||||||
|
"ghcr.io/devcontainers/features/common-utils:2": {
|
||||||
|
"installZsh": true,
|
||||||
|
"installOhMyZsh": true,
|
||||||
|
"installOhMyZshConfig": true,
|
||||||
|
"upgradePackages": true,
|
||||||
|
"username": "devcontainer",
|
||||||
|
"userUid": "automatic",
|
||||||
|
"userGid": "automatic"
|
||||||
|
},
|
||||||
|
"ghcr.io/devcontainers/features/dotnet:2": {
|
||||||
|
"version": "lts"
|
||||||
|
},
|
||||||
|
"ghcr.io/devcontainers/features/git:1": {
|
||||||
|
"ppa": true,
|
||||||
|
"version": "latest"
|
||||||
|
},
|
||||||
|
"ghcr.io/devcontainers/features/node": {
|
||||||
|
"version": "lts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||||
|
"forwardPorts": [
|
||||||
|
8080 // for Spec-Kit documentation site
|
||||||
|
],
|
||||||
|
"containerUser": "devcontainer",
|
||||||
|
"updateRemoteUserUID": true,
|
||||||
|
"postCreateCommand": "chmod +x ./.devcontainer/post-create.sh && ./.devcontainer/post-create.sh",
|
||||||
|
"postStartCommand": "git config --global --add safe.directory ${containerWorkspaceFolder}",
|
||||||
|
"customizations": {
|
||||||
|
"vscode": {
|
||||||
|
"extensions": [
|
||||||
|
"mhutchie.git-graph",
|
||||||
|
"eamodio.gitlens",
|
||||||
|
"anweber.reveal-button",
|
||||||
|
"chrisdias.promptboost",
|
||||||
|
// Github Copilot
|
||||||
|
"GitHub.copilot",
|
||||||
|
"GitHub.copilot-chat",
|
||||||
|
// Codex
|
||||||
|
"openai.chatgpt",
|
||||||
|
// Kilo Code
|
||||||
|
"kilocode.Kilo-Code",
|
||||||
|
// Roo Code
|
||||||
|
"RooVeterinaryInc.roo-cline",
|
||||||
|
// Amazon Developer Q
|
||||||
|
"AmazonWebServices.amazon-q-vscode",
|
||||||
|
// Claude Code
|
||||||
|
"anthropic.claude-code"
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"debug.javascript.autoAttachFilter": "disabled", // fix running commands in integrated terminal
|
||||||
|
|
||||||
|
// Specify settings for Github Copilot
|
||||||
|
"git.autofetch": true,
|
||||||
|
"chat.promptFilesRecommendations": {
|
||||||
|
"speckit.constitution": true,
|
||||||
|
"speckit.specify": true,
|
||||||
|
"speckit.plan": true,
|
||||||
|
"speckit.tasks": true,
|
||||||
|
"speckit.implement": true
|
||||||
|
},
|
||||||
|
"chat.tools.terminal.autoApprove": {
|
||||||
|
".specify/scripts/bash/": true,
|
||||||
|
".specify/scripts/powershell/": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
100
.devcontainer/post-create.sh
Executable file
100
.devcontainer/post-create.sh
Executable file
@@ -0,0 +1,100 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Exit immediately on error, treat unset variables as an error, and fail if any command in a pipeline fails.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Function to run a command and show logs only on error
|
||||||
|
run_command() {
|
||||||
|
local command_to_run="$*"
|
||||||
|
local output
|
||||||
|
local exit_code
|
||||||
|
|
||||||
|
# Capture all output (stdout and stderr)
|
||||||
|
output=$(eval "$command_to_run" 2>&1) || exit_code=$?
|
||||||
|
exit_code=${exit_code:-0}
|
||||||
|
|
||||||
|
if [ $exit_code -ne 0 ]; then
|
||||||
|
echo -e "\033[0;31m[ERROR] Command failed (Exit Code $exit_code): $command_to_run\033[0m" >&2
|
||||||
|
echo -e "\033[0;31m$output\033[0m" >&2
|
||||||
|
|
||||||
|
exit $exit_code
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Installing CLI-based AI Agents
|
||||||
|
|
||||||
|
echo -e "\n🤖 Installing Copilot CLI..."
|
||||||
|
run_command "npm install -g @github/copilot@latest"
|
||||||
|
echo "✅ Done"
|
||||||
|
|
||||||
|
echo -e "\n🤖 Installing Claude CLI..."
|
||||||
|
run_command "npm install -g @anthropic-ai/claude-code@latest"
|
||||||
|
echo "✅ Done"
|
||||||
|
|
||||||
|
echo -e "\n🤖 Installing Codex CLI..."
|
||||||
|
run_command "npm install -g @openai/codex@latest"
|
||||||
|
echo "✅ Done"
|
||||||
|
|
||||||
|
echo -e "\n🤖 Installing Gemini CLI..."
|
||||||
|
run_command "npm install -g @google/gemini-cli@latest"
|
||||||
|
echo "✅ Done"
|
||||||
|
|
||||||
|
echo -e "\n🤖 Installing Augie CLI..."
|
||||||
|
run_command "npm install -g @augmentcode/auggie@latest"
|
||||||
|
echo "✅ Done"
|
||||||
|
|
||||||
|
echo -e "\n🤖 Installing Qwen Code CLI..."
|
||||||
|
run_command "npm install -g @qwen-code/qwen-code@latest"
|
||||||
|
echo "✅ Done"
|
||||||
|
|
||||||
|
echo -e "\n🤖 Installing OpenCode CLI..."
|
||||||
|
run_command "npm install -g opencode-ai@latest"
|
||||||
|
echo "✅ Done"
|
||||||
|
|
||||||
|
echo -e "\n🤖 Installing Amazon Q CLI..."
|
||||||
|
# 👉🏾 https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/command-line-verify-download.html
|
||||||
|
|
||||||
|
run_command "curl --proto '=https' --tlsv1.2 -sSf 'https://desktop-release.q.us-east-1.amazonaws.com/latest/q-x86_64-linux.zip' -o 'q.zip'"
|
||||||
|
run_command "curl --proto '=https' --tlsv1.2 -sSf 'https://desktop-release.q.us-east-1.amazonaws.com/latest/q-x86_64-linux.zip.sig' -o 'q.zip.sig'"
|
||||||
|
cat > amazonq-public-key.asc << 'EOF'
|
||||||
|
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||||
|
|
||||||
|
mDMEZig60RYJKwYBBAHaRw8BAQdAy/+G05U5/EOA72WlcD4WkYn5SInri8pc4Z6D
|
||||||
|
BKNNGOm0JEFtYXpvbiBRIENMSSBUZWFtIDxxLWNsaUBhbWF6b24uY29tPoiZBBMW
|
||||||
|
CgBBFiEEmvYEF+gnQskUPgPsUNx6jcJMVmcFAmYoOtECGwMFCQPCZwAFCwkIBwIC
|
||||||
|
IgIGFQoJCAsCBBYCAwECHgcCF4AACgkQUNx6jcJMVmef5QD/QWWEGG/cOnbDnp68
|
||||||
|
SJXuFkwiNwlH2rPw9ZRIQMnfAS0A/0V6ZsGB4kOylBfc7CNfzRFGtovdBBgHqA6P
|
||||||
|
zQ/PNscGuDgEZig60RIKKwYBBAGXVQEFAQEHQC4qleONMBCq3+wJwbZSr0vbuRba
|
||||||
|
D1xr4wUPn4Avn4AnAwEIB4h+BBgWCgAmFiEEmvYEF+gnQskUPgPsUNx6jcJMVmcF
|
||||||
|
AmYoOtECGwwFCQPCZwAACgkQUNx6jcJMVmchMgEA6l3RveCM0YHAGQaSFMkguoAo
|
||||||
|
vK6FgOkDawgP0NPIP2oA/jIAO4gsAntuQgMOsPunEdDeji2t+AhV02+DQIsXZpoB
|
||||||
|
=f8yY
|
||||||
|
-----END PGP PUBLIC KEY BLOCK-----
|
||||||
|
EOF
|
||||||
|
run_command "gpg --batch --import amazonq-public-key.asc"
|
||||||
|
run_command "gpg --verify q.zip.sig q.zip"
|
||||||
|
run_command "unzip -q q.zip"
|
||||||
|
run_command "chmod +x ./q/install.sh"
|
||||||
|
run_command "./q/install.sh --no-confirm"
|
||||||
|
run_command "rm -rf ./q q.zip q.zip.sig amazonq-public-key.asc"
|
||||||
|
echo "✅ Done"
|
||||||
|
|
||||||
|
echo -e "\n🤖 Installing CodeBuddy CLI..."
|
||||||
|
run_command "npm install -g @tencent-ai/codebuddy-code@latest"
|
||||||
|
echo "✅ Done"
|
||||||
|
|
||||||
|
# Installing UV (Python package manager)
|
||||||
|
echo -e "\n🐍 Installing UV - Python Package Manager..."
|
||||||
|
run_command "pipx install uv"
|
||||||
|
echo "✅ Done"
|
||||||
|
|
||||||
|
# Installing DocFx (for documentation site)
|
||||||
|
echo -e "\n📚 Installing DocFx..."
|
||||||
|
run_command "dotnet tool update -g docfx"
|
||||||
|
echo "✅ Done"
|
||||||
|
|
||||||
|
echo -e "\n🧹 Cleaning cache..."
|
||||||
|
run_command "sudo apt-get autoclean"
|
||||||
|
run_command "sudo apt-get clean"
|
||||||
|
|
||||||
|
echo "✅ Setup completed. Happy coding! 🚀"
|
||||||
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
* text=auto eol=lf
|
||||||
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
@@ -1,2 +1,3 @@
|
|||||||
# Global code owner
|
# Global code owner
|
||||||
* @localden
|
* @localden
|
||||||
|
|
||||||
|
|||||||
22
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
22
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
## Description
|
||||||
|
|
||||||
|
<!-- What does this PR do? Why is it needed? -->
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
<!-- How did you test your changes? -->
|
||||||
|
|
||||||
|
- [ ] Tested locally with `uv run specify --help`
|
||||||
|
- [ ] Ran existing tests with `uv sync && uv run pytest`
|
||||||
|
- [ ] Tested with a sample project (if applicable)
|
||||||
|
|
||||||
|
## AI Disclosure
|
||||||
|
|
||||||
|
<!-- Per our Contributing guidelines, AI assistance must be disclosed. -->
|
||||||
|
<!-- See: https://github.com/github/spec-kit/blob/main/CONTRIBUTING.md#ai-contributions-in-spec-kit -->
|
||||||
|
|
||||||
|
- [ ] I **did not** use AI assistance for this contribution
|
||||||
|
- [ ] I **did** use AI assistance (describe below)
|
||||||
|
|
||||||
|
<!-- If you used AI, briefly describe how (e.g., "Code generated by Copilot", "Consulted ChatGPT for approach"): -->
|
||||||
|
|
||||||
11
.github/dependabot.yml
vendored
Normal file
11
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "pip"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
32
.github/workflows/codeql.yml
vendored
Normal file
32
.github/workflows/codeql.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
name: "CodeQL"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ main ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
analyze:
|
||||||
|
name: Analyze
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
security-events: write
|
||||||
|
contents: read
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
language: [ 'actions', 'python' ]
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Initialize CodeQL
|
||||||
|
uses: github/codeql-action/init@v4
|
||||||
|
with:
|
||||||
|
languages: ${{ matrix.language }}
|
||||||
|
|
||||||
|
- name: Perform CodeQL Analysis
|
||||||
|
uses: github/codeql-action/analyze@v4
|
||||||
|
with:
|
||||||
|
category: "/language:${{ matrix.language }}"
|
||||||
1
.github/workflows/docs.yml
vendored
1
.github/workflows/docs.yml
vendored
@@ -65,3 +65,4 @@ jobs:
|
|||||||
- name: Deploy to GitHub Pages
|
- name: Deploy to GitHub Pages
|
||||||
id: deployment
|
id: deployment
|
||||||
uses: actions/deploy-pages@v4
|
uses: actions/deploy-pages@v4
|
||||||
|
|
||||||
|
|||||||
22
.github/workflows/lint.yml
vendored
Normal file
22
.github/workflows/lint.yml
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
name: Lint
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: ["main"]
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
markdownlint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Run markdownlint-cli2
|
||||||
|
uses: DavidAnson/markdownlint-cli2-action@v19
|
||||||
|
with:
|
||||||
|
globs: |
|
||||||
|
'**/*.md'
|
||||||
|
!extensions/**/*.md
|
||||||
100
.github/workflows/release.yml
vendored
100
.github/workflows/release.yml
vendored
@@ -6,6 +6,7 @@ on:
|
|||||||
paths:
|
paths:
|
||||||
- 'memory/**'
|
- 'memory/**'
|
||||||
- 'scripts/**'
|
- 'scripts/**'
|
||||||
|
- 'src/**'
|
||||||
- 'templates/**'
|
- 'templates/**'
|
||||||
- '.github/workflows/**'
|
- '.github/workflows/**'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
@@ -25,33 +26,13 @@ jobs:
|
|||||||
- name: Get latest tag
|
- name: Get latest tag
|
||||||
id: get_tag
|
id: get_tag
|
||||||
run: |
|
run: |
|
||||||
# Get the latest tag, or use v0.0.0 if no tags exist
|
chmod +x .github/workflows/scripts/get-next-version.sh
|
||||||
LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
|
.github/workflows/scripts/get-next-version.sh
|
||||||
echo "latest_tag=$LATEST_TAG" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
# Extract version number and increment
|
|
||||||
VERSION=$(echo $LATEST_TAG | sed 's/v//')
|
|
||||||
IFS='.' read -ra VERSION_PARTS <<< "$VERSION"
|
|
||||||
MAJOR=${VERSION_PARTS[0]:-0}
|
|
||||||
MINOR=${VERSION_PARTS[1]:-0}
|
|
||||||
PATCH=${VERSION_PARTS[2]:-0}
|
|
||||||
|
|
||||||
# Increment patch version
|
|
||||||
PATCH=$((PATCH + 1))
|
|
||||||
NEW_VERSION="v$MAJOR.$MINOR.$PATCH"
|
|
||||||
|
|
||||||
echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
|
|
||||||
echo "New version will be: $NEW_VERSION"
|
|
||||||
- name: Check if release already exists
|
- name: Check if release already exists
|
||||||
id: check_release
|
id: check_release
|
||||||
run: |
|
run: |
|
||||||
if gh release view ${{ steps.get_tag.outputs.new_version }} >/dev/null 2>&1; then
|
chmod +x .github/workflows/scripts/check-release-exists.sh
|
||||||
echo "exists=true" >> $GITHUB_OUTPUT
|
.github/workflows/scripts/check-release-exists.sh ${{ steps.get_tag.outputs.new_version }}
|
||||||
echo "Release ${{ steps.get_tag.outputs.new_version }} already exists, skipping..."
|
|
||||||
else
|
|
||||||
echo "exists=false" >> $GITHUB_OUTPUT
|
|
||||||
echo "Release ${{ steps.get_tag.outputs.new_version }} does not exist, proceeding..."
|
|
||||||
fi
|
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Create release package variants
|
- name: Create release package variants
|
||||||
@@ -63,77 +44,18 @@ jobs:
|
|||||||
if: steps.check_release.outputs.exists == 'false'
|
if: steps.check_release.outputs.exists == 'false'
|
||||||
id: release_notes
|
id: release_notes
|
||||||
run: |
|
run: |
|
||||||
# Get commits since last tag
|
chmod +x .github/workflows/scripts/generate-release-notes.sh
|
||||||
LAST_TAG=${{ steps.get_tag.outputs.latest_tag }}
|
.github/workflows/scripts/generate-release-notes.sh ${{ steps.get_tag.outputs.new_version }} ${{ steps.get_tag.outputs.latest_tag }}
|
||||||
if [ "$LAST_TAG" = "v0.0.0" ]; then
|
|
||||||
# Check how many commits we have and use that as the limit
|
|
||||||
COMMIT_COUNT=$(git rev-list --count HEAD)
|
|
||||||
if [ "$COMMIT_COUNT" -gt 10 ]; then
|
|
||||||
COMMITS=$(git log --oneline --pretty=format:"- %s" HEAD~10..HEAD)
|
|
||||||
else
|
|
||||||
COMMITS=$(git log --oneline --pretty=format:"- %s" HEAD~$COMMIT_COUNT..HEAD 2>/dev/null || git log --oneline --pretty=format:"- %s")
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
COMMITS=$(git log --oneline --pretty=format:"- %s" $LAST_TAG..HEAD)
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Create release notes
|
|
||||||
cat > release_notes.md << EOF
|
|
||||||
Template release ${{ steps.get_tag.outputs.new_version }}
|
|
||||||
|
|
||||||
Updated specification-driven development templates for GitHub Copilot, Claude Code, Gemini CLI, Cursor, Qwen, and opencode.
|
|
||||||
|
|
||||||
Now includes per-script variants for POSIX shell (sh) and PowerShell (ps).
|
|
||||||
|
|
||||||
Download the template for your preferred AI assistant + script type:
|
|
||||||
- spec-kit-template-copilot-sh-${{ steps.get_tag.outputs.new_version }}.zip
|
|
||||||
- spec-kit-template-copilot-ps-${{ steps.get_tag.outputs.new_version }}.zip
|
|
||||||
- spec-kit-template-claude-sh-${{ steps.get_tag.outputs.new_version }}.zip
|
|
||||||
- spec-kit-template-claude-ps-${{ steps.get_tag.outputs.new_version }}.zip
|
|
||||||
- spec-kit-template-gemini-sh-${{ steps.get_tag.outputs.new_version }}.zip
|
|
||||||
- spec-kit-template-gemini-ps-${{ steps.get_tag.outputs.new_version }}.zip
|
|
||||||
- spec-kit-template-cursor-sh-${{ steps.get_tag.outputs.new_version }}.zip
|
|
||||||
- spec-kit-template-cursor-ps-${{ steps.get_tag.outputs.new_version }}.zip
|
|
||||||
- spec-kit-template-opencode-sh-${{ steps.get_tag.outputs.new_version }}.zip
|
|
||||||
- spec-kit-template-opencode-ps-${{ steps.get_tag.outputs.new_version }}.zip
|
|
||||||
- spec-kit-template-qwen-sh-${{ steps.get_tag.outputs.new_version }}.zip
|
|
||||||
- spec-kit-template-qwen-ps-${{ steps.get_tag.outputs.new_version }}.zip
|
|
||||||
EOF
|
|
||||||
|
|
||||||
echo "Generated release notes:"
|
|
||||||
cat release_notes.md
|
|
||||||
- name: Create GitHub Release
|
- name: Create GitHub Release
|
||||||
if: steps.check_release.outputs.exists == 'false'
|
if: steps.check_release.outputs.exists == 'false'
|
||||||
run: |
|
run: |
|
||||||
# Remove 'v' prefix from version for release title
|
chmod +x .github/workflows/scripts/create-github-release.sh
|
||||||
VERSION_NO_V=${{ steps.get_tag.outputs.new_version }}
|
.github/workflows/scripts/create-github-release.sh ${{ steps.get_tag.outputs.new_version }}
|
||||||
VERSION_NO_V=${VERSION_NO_V#v}
|
|
||||||
|
|
||||||
gh release create ${{ steps.get_tag.outputs.new_version }} \
|
|
||||||
spec-kit-template-copilot-sh-${{ steps.get_tag.outputs.new_version }}.zip \
|
|
||||||
spec-kit-template-copilot-ps-${{ steps.get_tag.outputs.new_version }}.zip \
|
|
||||||
spec-kit-template-claude-sh-${{ steps.get_tag.outputs.new_version }}.zip \
|
|
||||||
spec-kit-template-claude-ps-${{ steps.get_tag.outputs.new_version }}.zip \
|
|
||||||
spec-kit-template-gemini-sh-${{ steps.get_tag.outputs.new_version }}.zip \
|
|
||||||
spec-kit-template-gemini-ps-${{ steps.get_tag.outputs.new_version }}.zip \
|
|
||||||
spec-kit-template-cursor-sh-${{ steps.get_tag.outputs.new_version }}.zip \
|
|
||||||
spec-kit-template-cursor-ps-${{ steps.get_tag.outputs.new_version }}.zip \
|
|
||||||
spec-kit-template-opencode-sh-${{ steps.get_tag.outputs.new_version }}.zip \
|
|
||||||
spec-kit-template-opencode-ps-${{ steps.get_tag.outputs.new_version }}.zip \
|
|
||||||
spec-kit-template-qwen-sh-${{ steps.get_tag.outputs.new_version }}.zip \
|
|
||||||
spec-kit-template-qwen-ps-${{ steps.get_tag.outputs.new_version }}.zip \
|
|
||||||
--title "Spec Kit Templates - $VERSION_NO_V" \
|
|
||||||
--notes-file release_notes.md
|
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Update version in pyproject.toml (for release artifacts only)
|
- name: Update version in pyproject.toml (for release artifacts only)
|
||||||
if: steps.check_release.outputs.exists == 'false'
|
if: steps.check_release.outputs.exists == 'false'
|
||||||
run: |
|
run: |
|
||||||
# Update version in pyproject.toml (remove 'v' prefix for Python versioning)
|
chmod +x .github/workflows/scripts/update-version.sh
|
||||||
VERSION=${{ steps.get_tag.outputs.new_version }}
|
.github/workflows/scripts/update-version.sh ${{ steps.get_tag.outputs.new_version }}
|
||||||
PYTHON_VERSION=${VERSION#v}
|
|
||||||
|
|
||||||
if [ -f "pyproject.toml" ]; then
|
|
||||||
sed -i "s/version = \".*\"/version = \"$PYTHON_VERSION\"/" pyproject.toml
|
|
||||||
echo "Updated pyproject.toml version to $PYTHON_VERSION (for release artifacts only)"
|
|
||||||
fi
|
|
||||||
|
|||||||
21
.github/workflows/scripts/check-release-exists.sh
vendored
Normal file
21
.github/workflows/scripts/check-release-exists.sh
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# check-release-exists.sh
|
||||||
|
# Check if a GitHub release already exists for the given version
|
||||||
|
# Usage: check-release-exists.sh <version>
|
||||||
|
|
||||||
|
if [[ $# -ne 1 ]]; then
|
||||||
|
echo "Usage: $0 <version>" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
VERSION="$1"
|
||||||
|
|
||||||
|
if gh release view "$VERSION" >/dev/null 2>&1; then
|
||||||
|
echo "exists=true" >> $GITHUB_OUTPUT
|
||||||
|
echo "Release $VERSION already exists, skipping..."
|
||||||
|
else
|
||||||
|
echo "exists=false" >> $GITHUB_OUTPUT
|
||||||
|
echo "Release $VERSION does not exist, proceeding..."
|
||||||
|
fi
|
||||||
56
.github/workflows/scripts/create-github-release.sh
vendored
Normal file
56
.github/workflows/scripts/create-github-release.sh
vendored
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# create-github-release.sh
|
||||||
|
# Create a GitHub release with all template zip files
|
||||||
|
# Usage: create-github-release.sh <version>
|
||||||
|
|
||||||
|
if [[ $# -ne 1 ]]; then
|
||||||
|
echo "Usage: $0 <version>" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
VERSION="$1"
|
||||||
|
|
||||||
|
# Remove 'v' prefix from version for release title
|
||||||
|
VERSION_NO_V=${VERSION#v}
|
||||||
|
|
||||||
|
gh release create "$VERSION" \
|
||||||
|
.genreleases/spec-kit-template-copilot-sh-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-copilot-ps-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-claude-sh-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-claude-ps-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-gemini-sh-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-gemini-ps-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-cursor-agent-sh-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-cursor-agent-ps-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-opencode-sh-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-opencode-ps-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-qwen-sh-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-qwen-ps-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-windsurf-sh-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-windsurf-ps-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-codex-sh-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-codex-ps-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-kilocode-sh-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-kilocode-ps-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-auggie-sh-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-auggie-ps-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-roo-sh-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-roo-ps-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-codebuddy-sh-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-codebuddy-ps-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-qoder-sh-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-qoder-ps-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-amp-sh-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-amp-ps-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-shai-sh-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-shai-ps-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-q-sh-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-q-ps-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-agy-sh-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-agy-ps-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-bob-sh-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-bob-ps-"$VERSION".zip \
|
||||||
|
--title "Spec Kit Templates - $VERSION_NO_V" \
|
||||||
|
--notes-file release_notes.md
|
||||||
424
.github/workflows/scripts/create-release-packages.ps1
vendored
Normal file
424
.github/workflows/scripts/create-release-packages.ps1
vendored
Normal file
@@ -0,0 +1,424 @@
|
|||||||
|
#!/usr/bin/env pwsh
|
||||||
|
#requires -Version 7.0
|
||||||
|
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Build Spec Kit template release archives for each supported AI assistant and script type.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
create-release-packages.ps1 (workflow-local)
|
||||||
|
Build Spec Kit template release archives for each supported AI assistant and script type.
|
||||||
|
|
||||||
|
.PARAMETER Version
|
||||||
|
Version string with leading 'v' (e.g., v0.2.0)
|
||||||
|
|
||||||
|
.PARAMETER Agents
|
||||||
|
Comma or space separated subset of agents to build (default: all)
|
||||||
|
Valid agents: claude, gemini, copilot, cursor-agent, qwen, opencode, windsurf, codex, kilocode, auggie, roo, codebuddy, amp, q, bob, qoder
|
||||||
|
|
||||||
|
.PARAMETER Scripts
|
||||||
|
Comma or space separated subset of script types to build (default: both)
|
||||||
|
Valid scripts: sh, ps
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
.\create-release-packages.ps1 -Version v0.2.0
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
.\create-release-packages.ps1 -Version v0.2.0 -Agents claude,copilot -Scripts sh
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
.\create-release-packages.ps1 -Version v0.2.0 -Agents claude -Scripts ps
|
||||||
|
#>
|
||||||
|
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory=$true, Position=0)]
|
||||||
|
[string]$Version,
|
||||||
|
|
||||||
|
[Parameter(Mandatory=$false)]
|
||||||
|
[string]$Agents = "",
|
||||||
|
|
||||||
|
[Parameter(Mandatory=$false)]
|
||||||
|
[string]$Scripts = ""
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
# Validate version format
|
||||||
|
if ($Version -notmatch '^v\d+\.\d+\.\d+$') {
|
||||||
|
Write-Error "Version must look like v0.0.0"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Building release packages for $Version"
|
||||||
|
|
||||||
|
# Create and use .genreleases directory for all build artifacts
|
||||||
|
$GenReleasesDir = ".genreleases"
|
||||||
|
if (Test-Path $GenReleasesDir) {
|
||||||
|
Remove-Item -Path $GenReleasesDir -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
New-Item -ItemType Directory -Path $GenReleasesDir -Force | Out-Null
|
||||||
|
|
||||||
|
function Rewrite-Paths {
|
||||||
|
param([string]$Content)
|
||||||
|
|
||||||
|
$Content = $Content -replace '(/?)\bmemory/', '.specify/memory/'
|
||||||
|
$Content = $Content -replace '(/?)\bscripts/', '.specify/scripts/'
|
||||||
|
$Content = $Content -replace '(/?)\btemplates/', '.specify/templates/'
|
||||||
|
return $Content
|
||||||
|
}
|
||||||
|
|
||||||
|
function Generate-Commands {
|
||||||
|
param(
|
||||||
|
[string]$Agent,
|
||||||
|
[string]$Extension,
|
||||||
|
[string]$ArgFormat,
|
||||||
|
[string]$OutputDir,
|
||||||
|
[string]$ScriptVariant
|
||||||
|
)
|
||||||
|
|
||||||
|
New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null
|
||||||
|
|
||||||
|
$templates = Get-ChildItem -Path "templates/commands/*.md" -File -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
foreach ($template in $templates) {
|
||||||
|
$name = [System.IO.Path]::GetFileNameWithoutExtension($template.Name)
|
||||||
|
|
||||||
|
# Read file content and normalize line endings
|
||||||
|
$fileContent = (Get-Content -Path $template.FullName -Raw) -replace "`r`n", "`n"
|
||||||
|
|
||||||
|
# Extract description from YAML frontmatter
|
||||||
|
$description = ""
|
||||||
|
if ($fileContent -match '(?m)^description:\s*(.+)$') {
|
||||||
|
$description = $matches[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Extract script command from YAML frontmatter
|
||||||
|
$scriptCommand = ""
|
||||||
|
if ($fileContent -match "(?m)^\s*${ScriptVariant}:\s*(.+)$") {
|
||||||
|
$scriptCommand = $matches[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([string]::IsNullOrEmpty($scriptCommand)) {
|
||||||
|
Write-Warning "No script command found for $ScriptVariant in $($template.Name)"
|
||||||
|
$scriptCommand = "(Missing script command for $ScriptVariant)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Extract agent_script command from YAML frontmatter if present
|
||||||
|
$agentScriptCommand = ""
|
||||||
|
if ($fileContent -match "(?ms)agent_scripts:.*?^\s*${ScriptVariant}:\s*(.+?)$") {
|
||||||
|
$agentScriptCommand = $matches[1].Trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
# Replace {SCRIPT} placeholder with the script command
|
||||||
|
$body = $fileContent -replace '\{SCRIPT\}', $scriptCommand
|
||||||
|
|
||||||
|
# Replace {AGENT_SCRIPT} placeholder with the agent script command if found
|
||||||
|
if (-not [string]::IsNullOrEmpty($agentScriptCommand)) {
|
||||||
|
$body = $body -replace '\{AGENT_SCRIPT\}', $agentScriptCommand
|
||||||
|
}
|
||||||
|
|
||||||
|
# Remove the scripts: and agent_scripts: sections from frontmatter
|
||||||
|
$lines = $body -split "`n"
|
||||||
|
$outputLines = @()
|
||||||
|
$inFrontmatter = $false
|
||||||
|
$skipScripts = $false
|
||||||
|
$dashCount = 0
|
||||||
|
|
||||||
|
foreach ($line in $lines) {
|
||||||
|
if ($line -match '^---$') {
|
||||||
|
$outputLines += $line
|
||||||
|
$dashCount++
|
||||||
|
if ($dashCount -eq 1) {
|
||||||
|
$inFrontmatter = $true
|
||||||
|
} else {
|
||||||
|
$inFrontmatter = $false
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($inFrontmatter) {
|
||||||
|
if ($line -match '^(scripts|agent_scripts):$') {
|
||||||
|
$skipScripts = $true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ($line -match '^[a-zA-Z].*:' -and $skipScripts) {
|
||||||
|
$skipScripts = $false
|
||||||
|
}
|
||||||
|
if ($skipScripts -and $line -match '^\s+') {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$outputLines += $line
|
||||||
|
}
|
||||||
|
|
||||||
|
$body = $outputLines -join "`n"
|
||||||
|
|
||||||
|
# Apply other substitutions
|
||||||
|
$body = $body -replace '\{ARGS\}', $ArgFormat
|
||||||
|
$body = $body -replace '__AGENT__', $Agent
|
||||||
|
$body = Rewrite-Paths -Content $body
|
||||||
|
|
||||||
|
# Generate output file based on extension
|
||||||
|
$outputFile = Join-Path $OutputDir "speckit.$name.$Extension"
|
||||||
|
|
||||||
|
switch ($Extension) {
|
||||||
|
'toml' {
|
||||||
|
$body = $body -replace '\\', '\\'
|
||||||
|
$output = "description = `"$description`"`n`nprompt = `"`"`"`n$body`n`"`"`""
|
||||||
|
Set-Content -Path $outputFile -Value $output -NoNewline
|
||||||
|
}
|
||||||
|
'md' {
|
||||||
|
Set-Content -Path $outputFile -Value $body -NoNewline
|
||||||
|
}
|
||||||
|
'agent.md' {
|
||||||
|
Set-Content -Path $outputFile -Value $body -NoNewline
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Generate-CopilotPrompts {
|
||||||
|
param(
|
||||||
|
[string]$AgentsDir,
|
||||||
|
[string]$PromptsDir
|
||||||
|
)
|
||||||
|
|
||||||
|
New-Item -ItemType Directory -Path $PromptsDir -Force | Out-Null
|
||||||
|
|
||||||
|
$agentFiles = Get-ChildItem -Path "$AgentsDir/speckit.*.agent.md" -File -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
foreach ($agentFile in $agentFiles) {
|
||||||
|
$basename = $agentFile.Name -replace '\.agent\.md$', ''
|
||||||
|
$promptFile = Join-Path $PromptsDir "$basename.prompt.md"
|
||||||
|
|
||||||
|
$content = @"
|
||||||
|
---
|
||||||
|
agent: $basename
|
||||||
|
---
|
||||||
|
"@
|
||||||
|
Set-Content -Path $promptFile -Value $content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Build-Variant {
|
||||||
|
param(
|
||||||
|
[string]$Agent,
|
||||||
|
[string]$Script
|
||||||
|
)
|
||||||
|
|
||||||
|
$baseDir = Join-Path $GenReleasesDir "sdd-${Agent}-package-${Script}"
|
||||||
|
Write-Host "Building $Agent ($Script) package..."
|
||||||
|
New-Item -ItemType Directory -Path $baseDir -Force | Out-Null
|
||||||
|
|
||||||
|
# Copy base structure but filter scripts by variant
|
||||||
|
$specDir = Join-Path $baseDir ".specify"
|
||||||
|
New-Item -ItemType Directory -Path $specDir -Force | Out-Null
|
||||||
|
|
||||||
|
# Copy memory directory
|
||||||
|
if (Test-Path "memory") {
|
||||||
|
Copy-Item -Path "memory" -Destination $specDir -Recurse -Force
|
||||||
|
Write-Host "Copied memory -> .specify"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Only copy the relevant script variant directory
|
||||||
|
if (Test-Path "scripts") {
|
||||||
|
$scriptsDestDir = Join-Path $specDir "scripts"
|
||||||
|
New-Item -ItemType Directory -Path $scriptsDestDir -Force | Out-Null
|
||||||
|
|
||||||
|
switch ($Script) {
|
||||||
|
'sh' {
|
||||||
|
if (Test-Path "scripts/bash") {
|
||||||
|
Copy-Item -Path "scripts/bash" -Destination $scriptsDestDir -Recurse -Force
|
||||||
|
Write-Host "Copied scripts/bash -> .specify/scripts"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'ps' {
|
||||||
|
if (Test-Path "scripts/powershell") {
|
||||||
|
Copy-Item -Path "scripts/powershell" -Destination $scriptsDestDir -Recurse -Force
|
||||||
|
Write-Host "Copied scripts/powershell -> .specify/scripts"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Copy any script files that aren't in variant-specific directories
|
||||||
|
Get-ChildItem -Path "scripts" -File -ErrorAction SilentlyContinue | ForEach-Object {
|
||||||
|
Copy-Item -Path $_.FullName -Destination $scriptsDestDir -Force
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Copy templates (excluding commands directory and vscode-settings.json)
|
||||||
|
if (Test-Path "templates") {
|
||||||
|
$templatesDestDir = Join-Path $specDir "templates"
|
||||||
|
New-Item -ItemType Directory -Path $templatesDestDir -Force | Out-Null
|
||||||
|
|
||||||
|
Get-ChildItem -Path "templates" -Recurse -File | Where-Object {
|
||||||
|
$_.FullName -notmatch 'templates[/\\]commands[/\\]' -and $_.Name -ne 'vscode-settings.json'
|
||||||
|
} | ForEach-Object {
|
||||||
|
$relativePath = $_.FullName.Substring((Resolve-Path "templates").Path.Length + 1)
|
||||||
|
$destFile = Join-Path $templatesDestDir $relativePath
|
||||||
|
$destFileDir = Split-Path $destFile -Parent
|
||||||
|
New-Item -ItemType Directory -Path $destFileDir -Force | Out-Null
|
||||||
|
Copy-Item -Path $_.FullName -Destination $destFile -Force
|
||||||
|
}
|
||||||
|
Write-Host "Copied templates -> .specify/templates"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Generate agent-specific command files
|
||||||
|
switch ($Agent) {
|
||||||
|
'claude' {
|
||||||
|
$cmdDir = Join-Path $baseDir ".claude/commands"
|
||||||
|
Generate-Commands -Agent 'claude' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script
|
||||||
|
}
|
||||||
|
'gemini' {
|
||||||
|
$cmdDir = Join-Path $baseDir ".gemini/commands"
|
||||||
|
Generate-Commands -Agent 'gemini' -Extension 'toml' -ArgFormat '{{args}}' -OutputDir $cmdDir -ScriptVariant $Script
|
||||||
|
if (Test-Path "agent_templates/gemini/GEMINI.md") {
|
||||||
|
Copy-Item -Path "agent_templates/gemini/GEMINI.md" -Destination (Join-Path $baseDir "GEMINI.md")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'copilot' {
|
||||||
|
$agentsDir = Join-Path $baseDir ".github/agents"
|
||||||
|
Generate-Commands -Agent 'copilot' -Extension 'agent.md' -ArgFormat '$ARGUMENTS' -OutputDir $agentsDir -ScriptVariant $Script
|
||||||
|
|
||||||
|
# Generate companion prompt files
|
||||||
|
$promptsDir = Join-Path $baseDir ".github/prompts"
|
||||||
|
Generate-CopilotPrompts -AgentsDir $agentsDir -PromptsDir $promptsDir
|
||||||
|
|
||||||
|
# Create VS Code workspace settings
|
||||||
|
$vscodeDir = Join-Path $baseDir ".vscode"
|
||||||
|
New-Item -ItemType Directory -Path $vscodeDir -Force | Out-Null
|
||||||
|
if (Test-Path "templates/vscode-settings.json") {
|
||||||
|
Copy-Item -Path "templates/vscode-settings.json" -Destination (Join-Path $vscodeDir "settings.json")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'cursor-agent' {
|
||||||
|
$cmdDir = Join-Path $baseDir ".cursor/commands"
|
||||||
|
Generate-Commands -Agent 'cursor-agent' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script
|
||||||
|
}
|
||||||
|
'qwen' {
|
||||||
|
$cmdDir = Join-Path $baseDir ".qwen/commands"
|
||||||
|
Generate-Commands -Agent 'qwen' -Extension 'toml' -ArgFormat '{{args}}' -OutputDir $cmdDir -ScriptVariant $Script
|
||||||
|
if (Test-Path "agent_templates/qwen/QWEN.md") {
|
||||||
|
Copy-Item -Path "agent_templates/qwen/QWEN.md" -Destination (Join-Path $baseDir "QWEN.md")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'opencode' {
|
||||||
|
$cmdDir = Join-Path $baseDir ".opencode/command"
|
||||||
|
Generate-Commands -Agent 'opencode' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script
|
||||||
|
}
|
||||||
|
'windsurf' {
|
||||||
|
$cmdDir = Join-Path $baseDir ".windsurf/workflows"
|
||||||
|
Generate-Commands -Agent 'windsurf' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script
|
||||||
|
}
|
||||||
|
'codex' {
|
||||||
|
$cmdDir = Join-Path $baseDir ".codex/prompts"
|
||||||
|
Generate-Commands -Agent 'codex' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script
|
||||||
|
}
|
||||||
|
'kilocode' {
|
||||||
|
$cmdDir = Join-Path $baseDir ".kilocode/workflows"
|
||||||
|
Generate-Commands -Agent 'kilocode' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script
|
||||||
|
}
|
||||||
|
'auggie' {
|
||||||
|
$cmdDir = Join-Path $baseDir ".augment/commands"
|
||||||
|
Generate-Commands -Agent 'auggie' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script
|
||||||
|
}
|
||||||
|
'roo' {
|
||||||
|
$cmdDir = Join-Path $baseDir ".roo/commands"
|
||||||
|
Generate-Commands -Agent 'roo' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script
|
||||||
|
}
|
||||||
|
'codebuddy' {
|
||||||
|
$cmdDir = Join-Path $baseDir ".codebuddy/commands"
|
||||||
|
Generate-Commands -Agent 'codebuddy' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script
|
||||||
|
}
|
||||||
|
'amp' {
|
||||||
|
$cmdDir = Join-Path $baseDir ".agents/commands"
|
||||||
|
Generate-Commands -Agent 'amp' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script
|
||||||
|
}
|
||||||
|
'q' {
|
||||||
|
$cmdDir = Join-Path $baseDir ".amazonq/prompts"
|
||||||
|
Generate-Commands -Agent 'q' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script
|
||||||
|
}
|
||||||
|
'bob' {
|
||||||
|
$cmdDir = Join-Path $baseDir ".bob/commands"
|
||||||
|
Generate-Commands -Agent 'bob' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script
|
||||||
|
}
|
||||||
|
'qoder' {
|
||||||
|
$cmdDir = Join-Path $baseDir ".qoder/commands"
|
||||||
|
Generate-Commands -Agent 'qoder' -Extension 'md' -ArgFormat '$ARGUMENTS' -OutputDir $cmdDir -ScriptVariant $Script
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create zip archive
|
||||||
|
$zipFile = Join-Path $GenReleasesDir "spec-kit-template-${Agent}-${Script}-${Version}.zip"
|
||||||
|
Compress-Archive -Path "$baseDir/*" -DestinationPath $zipFile -Force
|
||||||
|
Write-Host "Created $zipFile"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Define all agents and scripts
|
||||||
|
$AllAgents = @('claude', 'gemini', 'copilot', 'cursor-agent', 'qwen', 'opencode', 'windsurf', 'codex', 'kilocode', 'auggie', 'roo', 'codebuddy', 'amp', 'q', 'bob', 'qoder')
|
||||||
|
$AllScripts = @('sh', 'ps')
|
||||||
|
|
||||||
|
function Normalize-List {
|
||||||
|
param([string]$Input)
|
||||||
|
|
||||||
|
if ([string]::IsNullOrEmpty($Input)) {
|
||||||
|
return @()
|
||||||
|
}
|
||||||
|
|
||||||
|
# Split by comma or space and remove duplicates while preserving order
|
||||||
|
$items = $Input -split '[,\s]+' | Where-Object { $_ } | Select-Object -Unique
|
||||||
|
return $items
|
||||||
|
}
|
||||||
|
|
||||||
|
function Validate-Subset {
|
||||||
|
param(
|
||||||
|
[string]$Type,
|
||||||
|
[string[]]$Allowed,
|
||||||
|
[string[]]$Items
|
||||||
|
)
|
||||||
|
|
||||||
|
$ok = $true
|
||||||
|
foreach ($item in $Items) {
|
||||||
|
if ($item -notin $Allowed) {
|
||||||
|
Write-Error "Unknown $Type '$item' (allowed: $($Allowed -join ', '))"
|
||||||
|
$ok = $false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $ok
|
||||||
|
}
|
||||||
|
|
||||||
|
# Determine agent list
|
||||||
|
if (-not [string]::IsNullOrEmpty($Agents)) {
|
||||||
|
$AgentList = Normalize-List -Input $Agents
|
||||||
|
if (-not (Validate-Subset -Type 'agent' -Allowed $AllAgents -Items $AgentList)) {
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$AgentList = $AllAgents
|
||||||
|
}
|
||||||
|
|
||||||
|
# Determine script list
|
||||||
|
if (-not [string]::IsNullOrEmpty($Scripts)) {
|
||||||
|
$ScriptList = Normalize-List -Input $Scripts
|
||||||
|
if (-not (Validate-Subset -Type 'script' -Allowed $AllScripts -Items $ScriptList)) {
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$ScriptList = $AllScripts
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Agents: $($AgentList -join ', ')"
|
||||||
|
Write-Host "Scripts: $($ScriptList -join ', ')"
|
||||||
|
|
||||||
|
# Build all variants
|
||||||
|
foreach ($agent in $AgentList) {
|
||||||
|
foreach ($script in $ScriptList) {
|
||||||
|
Build-Variant -Agent $agent -Script $script
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "`nArchives in ${GenReleasesDir}:"
|
||||||
|
Get-ChildItem -Path $GenReleasesDir -Filter "spec-kit-template-*-${Version}.zip" | ForEach-Object {
|
||||||
|
Write-Host " $($_.Name)"
|
||||||
|
}
|
||||||
155
.github/workflows/scripts/create-release-packages.sh
vendored
Normal file → Executable file
155
.github/workflows/scripts/create-release-packages.sh
vendored
Normal file → Executable file
@@ -6,7 +6,7 @@ set -euo pipefail
|
|||||||
# Usage: .github/workflows/scripts/create-release-packages.sh <version>
|
# Usage: .github/workflows/scripts/create-release-packages.sh <version>
|
||||||
# Version argument should include leading 'v'.
|
# Version argument should include leading 'v'.
|
||||||
# Optionally set AGENTS and/or SCRIPTS env vars to limit what gets built.
|
# Optionally set AGENTS and/or SCRIPTS env vars to limit what gets built.
|
||||||
# AGENTS : space or comma separated subset of: claude gemini copilot qwen opencode (default: all)
|
# AGENTS : space or comma separated subset of: claude gemini copilot cursor-agent qwen opencode windsurf codex amp shai bob (default: all)
|
||||||
# SCRIPTS : space or comma separated subset of: sh ps (default: both)
|
# SCRIPTS : space or comma separated subset of: sh ps (default: both)
|
||||||
# Examples:
|
# Examples:
|
||||||
# AGENTS=claude SCRIPTS=sh $0 v0.2.0
|
# AGENTS=claude SCRIPTS=sh $0 v0.2.0
|
||||||
@@ -25,13 +25,17 @@ fi
|
|||||||
|
|
||||||
echo "Building release packages for $NEW_VERSION"
|
echo "Building release packages for $NEW_VERSION"
|
||||||
|
|
||||||
rm -rf sdd-package-base* sdd-*-package-* spec-kit-template-*-"${NEW_VERSION}".zip || true
|
# Create and use .genreleases directory for all build artifacts
|
||||||
|
GENRELEASES_DIR=".genreleases"
|
||||||
|
mkdir -p "$GENRELEASES_DIR"
|
||||||
|
rm -rf "$GENRELEASES_DIR"/* || true
|
||||||
|
|
||||||
rewrite_paths() {
|
rewrite_paths() {
|
||||||
sed -E \
|
sed -E \
|
||||||
-e 's@(/?)memory/@.specify/memory/@g' \
|
-e 's@(/?)memory/@.specify/memory/@g' \
|
||||||
-e 's@(/?)scripts/@.specify/scripts/@g' \
|
-e 's@(/?)scripts/@.specify/scripts/@g' \
|
||||||
-e 's@(/?)templates/@.specify/templates/@g'
|
-e 's@(/?)templates/@.specify/templates/@g' \
|
||||||
|
-e 's@\.specify\.specify/@.specify/@g'
|
||||||
}
|
}
|
||||||
|
|
||||||
generate_commands() {
|
generate_commands() {
|
||||||
@@ -39,7 +43,7 @@ generate_commands() {
|
|||||||
mkdir -p "$output_dir"
|
mkdir -p "$output_dir"
|
||||||
for template in templates/commands/*.md; do
|
for template in templates/commands/*.md; do
|
||||||
[[ -f "$template" ]] || continue
|
[[ -f "$template" ]] || continue
|
||||||
local name description script_command body
|
local name description script_command agent_script_command body
|
||||||
name=$(basename "$template" .md)
|
name=$(basename "$template" .md)
|
||||||
|
|
||||||
# Normalize line endings
|
# Normalize line endings
|
||||||
@@ -54,13 +58,30 @@ generate_commands() {
|
|||||||
script_command="(Missing script command for $script_variant)"
|
script_command="(Missing script command for $script_variant)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Extract agent_script command from YAML frontmatter if present
|
||||||
|
agent_script_command=$(printf '%s\n' "$file_content" | awk '
|
||||||
|
/^agent_scripts:$/ { in_agent_scripts=1; next }
|
||||||
|
in_agent_scripts && /^[[:space:]]*'"$script_variant"':[[:space:]]*/ {
|
||||||
|
sub(/^[[:space:]]*'"$script_variant"':[[:space:]]*/, "")
|
||||||
|
print
|
||||||
|
exit
|
||||||
|
}
|
||||||
|
in_agent_scripts && /^[a-zA-Z]/ { in_agent_scripts=0 }
|
||||||
|
')
|
||||||
|
|
||||||
# Replace {SCRIPT} placeholder with the script command
|
# Replace {SCRIPT} placeholder with the script command
|
||||||
body=$(printf '%s\n' "$file_content" | sed "s|{SCRIPT}|${script_command}|g")
|
body=$(printf '%s\n' "$file_content" | sed "s|{SCRIPT}|${script_command}|g")
|
||||||
|
|
||||||
# Remove the scripts: section from frontmatter while preserving YAML structure
|
# Replace {AGENT_SCRIPT} placeholder with the agent script command if found
|
||||||
|
if [[ -n $agent_script_command ]]; then
|
||||||
|
body=$(printf '%s\n' "$body" | sed "s|{AGENT_SCRIPT}|${agent_script_command}|g")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Remove the scripts: and agent_scripts: sections from frontmatter while preserving YAML structure
|
||||||
body=$(printf '%s\n' "$body" | awk '
|
body=$(printf '%s\n' "$body" | awk '
|
||||||
/^---$/ { print; if (++dash_count == 1) in_frontmatter=1; else in_frontmatter=0; next }
|
/^---$/ { print; if (++dash_count == 1) in_frontmatter=1; else in_frontmatter=0; next }
|
||||||
in_frontmatter && /^scripts:$/ { skip_scripts=1; next }
|
in_frontmatter && /^scripts:$/ { skip_scripts=1; next }
|
||||||
|
in_frontmatter && /^agent_scripts:$/ { skip_scripts=1; next }
|
||||||
in_frontmatter && /^[a-zA-Z].*:/ && skip_scripts { skip_scripts=0 }
|
in_frontmatter && /^[a-zA-Z].*:/ && skip_scripts { skip_scripts=0 }
|
||||||
in_frontmatter && skip_scripts && /^[[:space:]]/ { next }
|
in_frontmatter && skip_scripts && /^[[:space:]]/ { next }
|
||||||
{ print }
|
{ print }
|
||||||
@@ -71,18 +92,39 @@ generate_commands() {
|
|||||||
|
|
||||||
case $ext in
|
case $ext in
|
||||||
toml)
|
toml)
|
||||||
{ echo "description = \"$description\""; echo; echo "prompt = \"\"\""; echo "$body"; echo "\"\"\""; } > "$output_dir/$name.$ext" ;;
|
body=$(printf '%s\n' "$body" | sed 's/\\/\\\\/g')
|
||||||
|
{ echo "description = \"$description\""; echo; echo "prompt = \"\"\""; echo "$body"; echo "\"\"\""; } > "$output_dir/speckit.$name.$ext" ;;
|
||||||
md)
|
md)
|
||||||
echo "$body" > "$output_dir/$name.$ext" ;;
|
echo "$body" > "$output_dir/speckit.$name.$ext" ;;
|
||||||
prompt.md)
|
agent.md)
|
||||||
echo "$body" > "$output_dir/$name.$ext" ;;
|
echo "$body" > "$output_dir/speckit.$name.$ext" ;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
generate_copilot_prompts() {
|
||||||
|
local agents_dir=$1 prompts_dir=$2
|
||||||
|
mkdir -p "$prompts_dir"
|
||||||
|
|
||||||
|
# Generate a .prompt.md file for each .agent.md file
|
||||||
|
for agent_file in "$agents_dir"/speckit.*.agent.md; do
|
||||||
|
[[ -f "$agent_file" ]] || continue
|
||||||
|
|
||||||
|
local basename=$(basename "$agent_file" .agent.md)
|
||||||
|
local prompt_file="$prompts_dir/${basename}.prompt.md"
|
||||||
|
|
||||||
|
# Create prompt file with agent frontmatter
|
||||||
|
cat > "$prompt_file" <<EOF
|
||||||
|
---
|
||||||
|
agent: ${basename}
|
||||||
|
---
|
||||||
|
EOF
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
build_variant() {
|
build_variant() {
|
||||||
local agent=$1 script=$2
|
local agent=$1 script=$2
|
||||||
local base_dir="sdd-${agent}-package-${script}"
|
local base_dir="$GENRELEASES_DIR/sdd-${agent}-package-${script}"
|
||||||
echo "Building $agent ($script) package..."
|
echo "Building $agent ($script) package..."
|
||||||
mkdir -p "$base_dir"
|
mkdir -p "$base_dir"
|
||||||
|
|
||||||
@@ -109,27 +151,10 @@ build_variant() {
|
|||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
[[ -d templates ]] && { mkdir -p "$SPEC_DIR/templates"; find templates -type f -not -path "templates/commands/*" -exec cp --parents {} "$SPEC_DIR"/ \; ; echo "Copied templates -> .specify/templates"; }
|
[[ -d templates ]] && { mkdir -p "$SPEC_DIR/templates"; find templates -type f -not -path "templates/commands/*" -not -name "vscode-settings.json" -exec cp --parents {} "$SPEC_DIR"/ \; ; echo "Copied templates -> .specify/templates"; }
|
||||||
# Inject variant into plan-template.md within .specify/templates if present
|
|
||||||
local plan_tpl="$base_dir/.specify/templates/plan-template.md"
|
|
||||||
if [[ -f "$plan_tpl" ]]; then
|
|
||||||
plan_norm=$(tr -d '\r' < "$plan_tpl")
|
|
||||||
# Extract script command from YAML frontmatter
|
|
||||||
script_command=$(printf '%s\n' "$plan_norm" | awk -v sv="$script" '/^[[:space:]]*'"$script"':[[:space:]]*/ {sub(/^[[:space:]]*'"$script"':[[:space:]]*/, ""); print; exit}')
|
|
||||||
if [[ -n $script_command ]]; then
|
|
||||||
# Always prefix with .specify/ for plan usage
|
|
||||||
script_command=".specify/$script_command"
|
|
||||||
# Replace {SCRIPT} placeholder with the script command and __AGENT__ with agent name
|
|
||||||
substituted=$(sed "s|{SCRIPT}|${script_command}|g" "$plan_tpl" | tr -d '\r' | sed "s|__AGENT__|${agent}|g")
|
|
||||||
# Strip YAML frontmatter from plan template output (keep body only)
|
|
||||||
stripped=$(printf '%s\n' "$substituted" | awk 'BEGIN{fm=0;dash=0} /^---$/ {dash++; if(dash==1){fm=1; next} else if(dash==2){fm=0; next}} {if(!fm) print}')
|
|
||||||
printf '%s\n' "$stripped" > "$plan_tpl"
|
|
||||||
else
|
|
||||||
echo "Warning: no plan-template script command found for $script in YAML frontmatter" >&2
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
# NOTE: We substitute {ARGS} internally. Outward tokens differ intentionally:
|
# NOTE: We substitute {ARGS} internally. Outward tokens differ intentionally:
|
||||||
# * Markdown/prompt (claude, copilot, cursor, opencode): $ARGUMENTS
|
# * Markdown/prompt (claude, copilot, cursor-agent, opencode): $ARGUMENTS
|
||||||
# * TOML (gemini, qwen): {{args}}
|
# * TOML (gemini, qwen): {{args}}
|
||||||
# This keeps formats readable without extra abstraction.
|
# This keeps formats readable without extra abstraction.
|
||||||
|
|
||||||
@@ -142,11 +167,17 @@ build_variant() {
|
|||||||
generate_commands gemini toml "{{args}}" "$base_dir/.gemini/commands" "$script"
|
generate_commands gemini toml "{{args}}" "$base_dir/.gemini/commands" "$script"
|
||||||
[[ -f agent_templates/gemini/GEMINI.md ]] && cp agent_templates/gemini/GEMINI.md "$base_dir/GEMINI.md" ;;
|
[[ -f agent_templates/gemini/GEMINI.md ]] && cp agent_templates/gemini/GEMINI.md "$base_dir/GEMINI.md" ;;
|
||||||
copilot)
|
copilot)
|
||||||
mkdir -p "$base_dir/.github/prompts"
|
mkdir -p "$base_dir/.github/agents"
|
||||||
generate_commands copilot prompt.md "\$ARGUMENTS" "$base_dir/.github/prompts" "$script" ;;
|
generate_commands copilot agent.md "\$ARGUMENTS" "$base_dir/.github/agents" "$script"
|
||||||
cursor)
|
# Generate companion prompt files
|
||||||
|
generate_copilot_prompts "$base_dir/.github/agents" "$base_dir/.github/prompts"
|
||||||
|
# Create VS Code workspace settings
|
||||||
|
mkdir -p "$base_dir/.vscode"
|
||||||
|
[[ -f templates/vscode-settings.json ]] && cp templates/vscode-settings.json "$base_dir/.vscode/settings.json"
|
||||||
|
;;
|
||||||
|
cursor-agent)
|
||||||
mkdir -p "$base_dir/.cursor/commands"
|
mkdir -p "$base_dir/.cursor/commands"
|
||||||
generate_commands cursor md "\$ARGUMENTS" "$base_dir/.cursor/commands" "$script" ;;
|
generate_commands cursor-agent md "\$ARGUMENTS" "$base_dir/.cursor/commands" "$script" ;;
|
||||||
qwen)
|
qwen)
|
||||||
mkdir -p "$base_dir/.qwen/commands"
|
mkdir -p "$base_dir/.qwen/commands"
|
||||||
generate_commands qwen toml "{{args}}" "$base_dir/.qwen/commands" "$script"
|
generate_commands qwen toml "{{args}}" "$base_dir/.qwen/commands" "$script"
|
||||||
@@ -154,33 +185,68 @@ build_variant() {
|
|||||||
opencode)
|
opencode)
|
||||||
mkdir -p "$base_dir/.opencode/command"
|
mkdir -p "$base_dir/.opencode/command"
|
||||||
generate_commands opencode md "\$ARGUMENTS" "$base_dir/.opencode/command" "$script" ;;
|
generate_commands opencode md "\$ARGUMENTS" "$base_dir/.opencode/command" "$script" ;;
|
||||||
|
windsurf)
|
||||||
|
mkdir -p "$base_dir/.windsurf/workflows"
|
||||||
|
generate_commands windsurf md "\$ARGUMENTS" "$base_dir/.windsurf/workflows" "$script" ;;
|
||||||
|
codex)
|
||||||
|
mkdir -p "$base_dir/.codex/prompts"
|
||||||
|
generate_commands codex md "\$ARGUMENTS" "$base_dir/.codex/prompts" "$script" ;;
|
||||||
|
kilocode)
|
||||||
|
mkdir -p "$base_dir/.kilocode/workflows"
|
||||||
|
generate_commands kilocode md "\$ARGUMENTS" "$base_dir/.kilocode/workflows" "$script" ;;
|
||||||
|
auggie)
|
||||||
|
mkdir -p "$base_dir/.augment/commands"
|
||||||
|
generate_commands auggie md "\$ARGUMENTS" "$base_dir/.augment/commands" "$script" ;;
|
||||||
|
roo)
|
||||||
|
mkdir -p "$base_dir/.roo/commands"
|
||||||
|
generate_commands roo md "\$ARGUMENTS" "$base_dir/.roo/commands" "$script" ;;
|
||||||
|
codebuddy)
|
||||||
|
mkdir -p "$base_dir/.codebuddy/commands"
|
||||||
|
generate_commands codebuddy md "\$ARGUMENTS" "$base_dir/.codebuddy/commands" "$script" ;;
|
||||||
|
qoder)
|
||||||
|
mkdir -p "$base_dir/.qoder/commands"
|
||||||
|
generate_commands qoder md "\$ARGUMENTS" "$base_dir/.qoder/commands" "$script" ;;
|
||||||
|
amp)
|
||||||
|
mkdir -p "$base_dir/.agents/commands"
|
||||||
|
generate_commands amp md "\$ARGUMENTS" "$base_dir/.agents/commands" "$script" ;;
|
||||||
|
shai)
|
||||||
|
mkdir -p "$base_dir/.shai/commands"
|
||||||
|
generate_commands shai md "\$ARGUMENTS" "$base_dir/.shai/commands" "$script" ;;
|
||||||
|
q)
|
||||||
|
mkdir -p "$base_dir/.amazonq/prompts"
|
||||||
|
generate_commands q md "\$ARGUMENTS" "$base_dir/.amazonq/prompts" "$script" ;;
|
||||||
|
agy)
|
||||||
|
mkdir -p "$base_dir/.agent/workflows"
|
||||||
|
generate_commands agy md "\$ARGUMENTS" "$base_dir/.agent/workflows" "$script" ;;
|
||||||
|
bob)
|
||||||
|
mkdir -p "$base_dir/.bob/commands"
|
||||||
|
generate_commands bob md "\$ARGUMENTS" "$base_dir/.bob/commands" "$script" ;;
|
||||||
esac
|
esac
|
||||||
( cd "$base_dir" && zip -r "../spec-kit-template-${agent}-${script}-${NEW_VERSION}.zip" . )
|
( cd "$base_dir" && zip -r "../spec-kit-template-${agent}-${script}-${NEW_VERSION}.zip" . )
|
||||||
echo "Created spec-kit-template-${agent}-${script}-${NEW_VERSION}.zip"
|
echo "Created $GENRELEASES_DIR/spec-kit-template-${agent}-${script}-${NEW_VERSION}.zip"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Determine agent list
|
# Determine agent list
|
||||||
ALL_AGENTS=(claude gemini copilot cursor qwen opencode)
|
ALL_AGENTS=(claude gemini copilot cursor-agent qwen opencode windsurf codex kilocode auggie roo codebuddy amp shai q agy bob qoder)
|
||||||
ALL_SCRIPTS=(sh ps)
|
ALL_SCRIPTS=(sh ps)
|
||||||
|
|
||||||
|
|
||||||
norm_list() {
|
norm_list() {
|
||||||
# convert comma+space separated -> space separated unique while preserving order of first occurrence
|
# convert comma+space separated -> line separated unique while preserving order of first occurrence
|
||||||
tr ',\n' ' ' | awk '{for(i=1;i<=NF;i++){if(!seen[$i]++){printf((out?" ":"") $i)}}}END{printf("\n")}'
|
tr ',\n' ' ' | awk '{for(i=1;i<=NF;i++){if(!seen[$i]++){printf((out?"\n":"") $i);out=1}}}END{printf("\n")}'
|
||||||
}
|
}
|
||||||
|
|
||||||
validate_subset() {
|
validate_subset() {
|
||||||
local type=$1; shift; local -n allowed=$1; shift; local items=("$@")
|
local type=$1; shift; local -n allowed=$1; shift; local items=("$@")
|
||||||
local ok=1
|
local invalid=0
|
||||||
for it in "${items[@]}"; do
|
for it in "${items[@]}"; do
|
||||||
local found=0
|
local found=0
|
||||||
for a in "${allowed[@]}"; do [[ $it == "$a" ]] && { found=1; break; }; done
|
for a in "${allowed[@]}"; do [[ $it == "$a" ]] && { found=1; break; }; done
|
||||||
if [[ $found -eq 0 ]]; then
|
if [[ $found -eq 0 ]]; then
|
||||||
echo "Error: unknown $type '$it' (allowed: ${allowed[*]})" >&2
|
echo "Error: unknown $type '$it' (allowed: ${allowed[*]})" >&2
|
||||||
ok=0
|
invalid=1
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
return $ok
|
return $invalid
|
||||||
}
|
}
|
||||||
|
|
||||||
if [[ -n ${AGENTS:-} ]]; then
|
if [[ -n ${AGENTS:-} ]]; then
|
||||||
@@ -206,5 +272,6 @@ for agent in "${AGENT_LIST[@]}"; do
|
|||||||
done
|
done
|
||||||
done
|
done
|
||||||
|
|
||||||
echo "Archives:"
|
echo "Archives in $GENRELEASES_DIR:"
|
||||||
ls -1 spec-kit-template-*-"${NEW_VERSION}".zip
|
ls -1 "$GENRELEASES_DIR"/spec-kit-template-*-"${NEW_VERSION}".zip
|
||||||
|
|
||||||
|
|||||||
40
.github/workflows/scripts/generate-release-notes.sh
vendored
Normal file
40
.github/workflows/scripts/generate-release-notes.sh
vendored
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# generate-release-notes.sh
|
||||||
|
# Generate release notes from git history
|
||||||
|
# Usage: generate-release-notes.sh <new_version> <last_tag>
|
||||||
|
|
||||||
|
if [[ $# -ne 2 ]]; then
|
||||||
|
echo "Usage: $0 <new_version> <last_tag>" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
NEW_VERSION="$1"
|
||||||
|
LAST_TAG="$2"
|
||||||
|
|
||||||
|
# Get commits since last tag
|
||||||
|
if [ "$LAST_TAG" = "v0.0.0" ]; then
|
||||||
|
# Check how many commits we have and use that as the limit
|
||||||
|
COMMIT_COUNT=$(git rev-list --count HEAD)
|
||||||
|
if [ "$COMMIT_COUNT" -gt 10 ]; then
|
||||||
|
COMMITS=$(git log --oneline --pretty=format:"- %s" HEAD~10..HEAD)
|
||||||
|
else
|
||||||
|
COMMITS=$(git log --oneline --pretty=format:"- %s" HEAD~$COMMIT_COUNT..HEAD 2>/dev/null || git log --oneline --pretty=format:"- %s")
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
COMMITS=$(git log --oneline --pretty=format:"- %s" $LAST_TAG..HEAD)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create release notes
|
||||||
|
cat > release_notes.md << EOF
|
||||||
|
This is the latest set of releases that you can use with your agent of choice. We recommend using the Specify CLI to scaffold your projects, however you can download these independently and manage them yourself.
|
||||||
|
|
||||||
|
## Changelog
|
||||||
|
|
||||||
|
$COMMITS
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "Generated release notes:"
|
||||||
|
cat release_notes.md
|
||||||
24
.github/workflows/scripts/get-next-version.sh
vendored
Normal file
24
.github/workflows/scripts/get-next-version.sh
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# get-next-version.sh
|
||||||
|
# Calculate the next version based on the latest git tag and output GitHub Actions variables
|
||||||
|
# Usage: get-next-version.sh
|
||||||
|
|
||||||
|
# Get the latest tag, or use v0.0.0 if no tags exist
|
||||||
|
LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
|
||||||
|
echo "latest_tag=$LATEST_TAG" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
# Extract version number and increment
|
||||||
|
VERSION=$(echo $LATEST_TAG | sed 's/v//')
|
||||||
|
IFS='.' read -ra VERSION_PARTS <<< "$VERSION"
|
||||||
|
MAJOR=${VERSION_PARTS[0]:-0}
|
||||||
|
MINOR=${VERSION_PARTS[1]:-0}
|
||||||
|
PATCH=${VERSION_PARTS[2]:-0}
|
||||||
|
|
||||||
|
# Increment patch version
|
||||||
|
PATCH=$((PATCH + 1))
|
||||||
|
NEW_VERSION="v$MAJOR.$MINOR.$PATCH"
|
||||||
|
|
||||||
|
echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
|
||||||
|
echo "New version will be: $NEW_VERSION"
|
||||||
23
.github/workflows/scripts/update-version.sh
vendored
Normal file
23
.github/workflows/scripts/update-version.sh
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# update-version.sh
|
||||||
|
# Update version in pyproject.toml (for release artifacts only)
|
||||||
|
# Usage: update-version.sh <version>
|
||||||
|
|
||||||
|
if [[ $# -ne 1 ]]; then
|
||||||
|
echo "Usage: $0 <version>" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
VERSION="$1"
|
||||||
|
|
||||||
|
# Remove 'v' prefix for Python versioning
|
||||||
|
PYTHON_VERSION=${VERSION#v}
|
||||||
|
|
||||||
|
if [ -f "pyproject.toml" ]; then
|
||||||
|
sed -i "s/version = \".*\"/version = \"$PYTHON_VERSION\"/" pyproject.toml
|
||||||
|
echo "Updated pyproject.toml version to $PYTHON_VERSION (for release artifacts only)"
|
||||||
|
else
|
||||||
|
echo "Warning: pyproject.toml not found, skipping version update"
|
||||||
|
fi
|
||||||
42
.github/workflows/stale.yml
vendored
Normal file
42
.github/workflows/stale.yml
vendored
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
name: 'Close stale issues and PRs'
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 0 * * *' # Run daily at midnight UTC
|
||||||
|
workflow_dispatch: # Allow manual triggering
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
issues: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
stale:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/stale@v10
|
||||||
|
with:
|
||||||
|
# Days of inactivity before an issue or PR becomes stale
|
||||||
|
days-before-stale: 150
|
||||||
|
# Days of inactivity before a stale issue or PR is closed (after being marked stale)
|
||||||
|
days-before-close: 30
|
||||||
|
|
||||||
|
# Stale issue settings
|
||||||
|
stale-issue-message: 'This issue has been automatically marked as stale because it has not had any activity for 150 days. It will be closed in 30 days if no further activity occurs.'
|
||||||
|
close-issue-message: 'This issue has been automatically closed due to inactivity (180 days total). If you believe this issue is still relevant, please reopen it or create a new issue.'
|
||||||
|
stale-issue-label: 'stale'
|
||||||
|
|
||||||
|
# Stale PR settings
|
||||||
|
stale-pr-message: 'This pull request has been automatically marked as stale because it has not had any activity for 150 days. It will be closed in 30 days if no further activity occurs.'
|
||||||
|
close-pr-message: 'This pull request has been automatically closed due to inactivity (180 days total). If you believe this PR is still relevant, please reopen it or create a new PR.'
|
||||||
|
stale-pr-label: 'stale'
|
||||||
|
|
||||||
|
# Exempt issues and PRs with these labels from being marked as stale
|
||||||
|
exempt-issue-labels: 'pinned,security'
|
||||||
|
exempt-pr-labels: 'pinned,security'
|
||||||
|
|
||||||
|
# Only issues or PRs with all of these labels are checked
|
||||||
|
# Leave empty to check all issues and PRs
|
||||||
|
any-of-labels: ''
|
||||||
|
|
||||||
|
# Operations per run (helps avoid rate limits)
|
||||||
|
operations-per-run: 100
|
||||||
50
.github/workflows/test.yml
vendored
Normal file
50
.github/workflows/test.yml
vendored
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
name: Test & Lint Python
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: ["main"]
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
ruff:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install uv
|
||||||
|
uses: astral-sh/setup-uv@v6
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: "3.13"
|
||||||
|
|
||||||
|
- name: Run ruff check
|
||||||
|
run: uvx ruff check src/
|
||||||
|
|
||||||
|
pytest:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
python-version: ["3.11", "3.12", "3.13"]
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install uv
|
||||||
|
uses: astral-sh/setup-uv@v6
|
||||||
|
|
||||||
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python-version }}
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: uv sync --extra test
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: uv run pytest
|
||||||
12
.gitignore
vendored
12
.gitignore
vendored
@@ -32,9 +32,21 @@ env/
|
|||||||
*.swp
|
*.swp
|
||||||
*.swo
|
*.swo
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
*.tmp
|
||||||
|
|
||||||
# Project specific
|
# Project specific
|
||||||
*.log
|
*.log
|
||||||
.env
|
.env
|
||||||
.env.local
|
.env.local
|
||||||
*.lock
|
*.lock
|
||||||
|
|
||||||
|
# Spec Kit-specific files
|
||||||
|
.genreleases/
|
||||||
|
*.zip
|
||||||
|
sdd-*/
|
||||||
|
docs/dev
|
||||||
|
|
||||||
|
# Extension system
|
||||||
|
.specify/extensions/.cache/
|
||||||
|
.specify/extensions/.backup/
|
||||||
|
.specify/extensions/*/local-config.yml
|
||||||
|
|||||||
29
.markdownlint-cli2.jsonc
Normal file
29
.markdownlint-cli2.jsonc
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
// https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md
|
||||||
|
"config": {
|
||||||
|
"default": true,
|
||||||
|
"MD003": {
|
||||||
|
"style": "atx"
|
||||||
|
},
|
||||||
|
"MD007": {
|
||||||
|
"indent": 2
|
||||||
|
},
|
||||||
|
"MD013": false,
|
||||||
|
"MD024": {
|
||||||
|
"siblings_only": true
|
||||||
|
},
|
||||||
|
"MD033": false,
|
||||||
|
"MD041": false,
|
||||||
|
"MD049": {
|
||||||
|
"style": "asterisk"
|
||||||
|
},
|
||||||
|
"MD050": {
|
||||||
|
"style": "asterisk"
|
||||||
|
},
|
||||||
|
"MD036": false,
|
||||||
|
"MD060": false
|
||||||
|
},
|
||||||
|
"ignores": [
|
||||||
|
".genreleases/"
|
||||||
|
]
|
||||||
|
}
|
||||||
413
AGENTS.md
Normal file
413
AGENTS.md
Normal file
@@ -0,0 +1,413 @@
|
|||||||
|
# AGENTS.md
|
||||||
|
|
||||||
|
## About Spec Kit and Specify
|
||||||
|
|
||||||
|
**GitHub Spec Kit** is a comprehensive toolkit for implementing Spec-Driven Development (SDD) - a methodology that emphasizes creating clear specifications before implementation. The toolkit includes templates, scripts, and workflows that guide development teams through a structured approach to building software.
|
||||||
|
|
||||||
|
**Specify CLI** is the command-line interface that bootstraps projects with the Spec Kit framework. It sets up the necessary directory structures, templates, and AI agent integrations to support the Spec-Driven Development workflow.
|
||||||
|
|
||||||
|
The toolkit supports multiple AI coding assistants, allowing teams to use their preferred tools while maintaining consistent project structure and development practices.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## General practices
|
||||||
|
|
||||||
|
- Any changes to `__init__.py` for the Specify CLI require a version rev in `pyproject.toml` and addition of entries to `CHANGELOG.md`.
|
||||||
|
|
||||||
|
## Adding New Agent Support
|
||||||
|
|
||||||
|
This section explains how to add support for new AI agents/assistants to the Specify CLI. Use this guide as a reference when integrating new AI tools into the Spec-Driven Development workflow.
|
||||||
|
|
||||||
|
### Overview
|
||||||
|
|
||||||
|
Specify supports multiple AI agents by generating agent-specific command files and directory structures when initializing projects. Each agent has its own conventions for:
|
||||||
|
|
||||||
|
- **Command file formats** (Markdown, TOML, etc.)
|
||||||
|
- **Directory structures** (`.claude/commands/`, `.windsurf/workflows/`, etc.)
|
||||||
|
- **Command invocation patterns** (slash commands, CLI tools, etc.)
|
||||||
|
- **Argument passing conventions** (`$ARGUMENTS`, `{{args}}`, etc.)
|
||||||
|
|
||||||
|
### Current Supported Agents
|
||||||
|
|
||||||
|
| Agent | Directory | Format | CLI Tool | Description |
|
||||||
|
| -------------------------- | ---------------------- | -------- | --------------- | --------------------------- |
|
||||||
|
| **Claude Code** | `.claude/commands/` | Markdown | `claude` | Anthropic's Claude Code CLI |
|
||||||
|
| **Gemini CLI** | `.gemini/commands/` | TOML | `gemini` | Google's Gemini CLI |
|
||||||
|
| **GitHub Copilot** | `.github/agents/` | Markdown | N/A (IDE-based) | GitHub Copilot in VS Code |
|
||||||
|
| **Cursor** | `.cursor/commands/` | Markdown | `cursor-agent` | Cursor CLI |
|
||||||
|
| **Qwen Code** | `.qwen/commands/` | TOML | `qwen` | Alibaba's Qwen Code CLI |
|
||||||
|
| **opencode** | `.opencode/command/` | Markdown | `opencode` | opencode CLI |
|
||||||
|
| **Codex CLI** | `.codex/commands/` | Markdown | `codex` | Codex CLI |
|
||||||
|
| **Windsurf** | `.windsurf/workflows/` | Markdown | N/A (IDE-based) | Windsurf IDE workflows |
|
||||||
|
| **Kilo Code** | `.kilocode/rules/` | Markdown | N/A (IDE-based) | Kilo Code IDE |
|
||||||
|
| **Auggie CLI** | `.augment/rules/` | Markdown | `auggie` | Auggie CLI |
|
||||||
|
| **Roo Code** | `.roo/rules/` | Markdown | N/A (IDE-based) | Roo Code IDE |
|
||||||
|
| **CodeBuddy CLI** | `.codebuddy/commands/` | Markdown | `codebuddy` | CodeBuddy CLI |
|
||||||
|
| **Qoder CLI** | `.qoder/commands/` | Markdown | `qoder` | Qoder CLI |
|
||||||
|
| **Amazon Q Developer CLI** | `.amazonq/prompts/` | Markdown | `q` | Amazon Q Developer CLI |
|
||||||
|
| **Amp** | `.agents/commands/` | Markdown | `amp` | Amp CLI |
|
||||||
|
| **SHAI** | `.shai/commands/` | Markdown | `shai` | SHAI CLI |
|
||||||
|
| **IBM Bob** | `.bob/commands/` | Markdown | N/A (IDE-based) | IBM Bob IDE |
|
||||||
|
|
||||||
|
### Step-by-Step Integration Guide
|
||||||
|
|
||||||
|
Follow these steps to add a new agent (using a hypothetical new agent as an example):
|
||||||
|
|
||||||
|
#### 1. Add to AGENT_CONFIG
|
||||||
|
|
||||||
|
**IMPORTANT**: Use the actual CLI tool name as the key, not a shortened version.
|
||||||
|
|
||||||
|
Add the new agent to the `AGENT_CONFIG` dictionary in `src/specify_cli/__init__.py`. This is the **single source of truth** for all agent metadata:
|
||||||
|
|
||||||
|
```python
|
||||||
|
AGENT_CONFIG = {
|
||||||
|
# ... existing agents ...
|
||||||
|
"new-agent-cli": { # Use the ACTUAL CLI tool name (what users type in terminal)
|
||||||
|
"name": "New Agent Display Name",
|
||||||
|
"folder": ".newagent/", # Directory for agent files
|
||||||
|
"install_url": "https://example.com/install", # URL for installation docs (or None if IDE-based)
|
||||||
|
"requires_cli": True, # True if CLI tool required, False for IDE-based agents
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key Design Principle**: The dictionary key should match the actual executable name that users install. For example:
|
||||||
|
|
||||||
|
- ✅ Use `"cursor-agent"` because the CLI tool is literally called `cursor-agent`
|
||||||
|
- ❌ Don't use `"cursor"` as a shortcut if the tool is `cursor-agent`
|
||||||
|
|
||||||
|
This eliminates the need for special-case mappings throughout the codebase.
|
||||||
|
|
||||||
|
**Field Explanations**:
|
||||||
|
|
||||||
|
- `name`: Human-readable display name shown to users
|
||||||
|
- `folder`: Directory where agent-specific files are stored (relative to project root)
|
||||||
|
- `install_url`: Installation documentation URL (set to `None` for IDE-based agents)
|
||||||
|
- `requires_cli`: Whether the agent requires a CLI tool check during initialization
|
||||||
|
|
||||||
|
#### 2. Update CLI Help Text
|
||||||
|
|
||||||
|
Update the `--ai` parameter help text in the `init()` command to include the new agent:
|
||||||
|
|
||||||
|
```python
|
||||||
|
ai_assistant: str = typer.Option(None, "--ai", help="AI assistant to use: claude, gemini, copilot, cursor-agent, qwen, opencode, codex, windsurf, kilocode, auggie, codebuddy, new-agent-cli, or q"),
|
||||||
|
```
|
||||||
|
|
||||||
|
Also update any function docstrings, examples, and error messages that list available agents.
|
||||||
|
|
||||||
|
#### 3. Update README Documentation
|
||||||
|
|
||||||
|
Update the **Supported AI Agents** section in `README.md` to include the new agent:
|
||||||
|
|
||||||
|
- Add the new agent to the table with appropriate support level (Full/Partial)
|
||||||
|
- Include the agent's official website link
|
||||||
|
- Add any relevant notes about the agent's implementation
|
||||||
|
- Ensure the table formatting remains aligned and consistent
|
||||||
|
|
||||||
|
#### 4. Update Release Package Script
|
||||||
|
|
||||||
|
Modify `.github/workflows/scripts/create-release-packages.sh`:
|
||||||
|
|
||||||
|
##### Add to ALL_AGENTS array
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ALL_AGENTS=(claude gemini copilot cursor-agent qwen opencode windsurf q)
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Add case statement for directory structure
|
||||||
|
|
||||||
|
```bash
|
||||||
|
case $agent in
|
||||||
|
# ... existing cases ...
|
||||||
|
windsurf)
|
||||||
|
mkdir -p "$base_dir/.windsurf/workflows"
|
||||||
|
generate_commands windsurf md "\$ARGUMENTS" "$base_dir/.windsurf/workflows" "$script" ;;
|
||||||
|
esac
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4. Update GitHub Release Script
|
||||||
|
|
||||||
|
Modify `.github/workflows/scripts/create-github-release.sh` to include the new agent's packages:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gh release create "$VERSION" \
|
||||||
|
# ... existing packages ...
|
||||||
|
.genreleases/spec-kit-template-windsurf-sh-"$VERSION".zip \
|
||||||
|
.genreleases/spec-kit-template-windsurf-ps-"$VERSION".zip \
|
||||||
|
# Add new agent packages here
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 5. Update Agent Context Scripts
|
||||||
|
|
||||||
|
##### Bash script (`scripts/bash/update-agent-context.sh`)
|
||||||
|
|
||||||
|
Add file variable:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
WINDSURF_FILE="$REPO_ROOT/.windsurf/rules/specify-rules.md"
|
||||||
|
```
|
||||||
|
|
||||||
|
Add to case statement:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
case "$AGENT_TYPE" in
|
||||||
|
# ... existing cases ...
|
||||||
|
windsurf) update_agent_file "$WINDSURF_FILE" "Windsurf" ;;
|
||||||
|
"")
|
||||||
|
# ... existing checks ...
|
||||||
|
[ -f "$WINDSURF_FILE" ] && update_agent_file "$WINDSURF_FILE" "Windsurf";
|
||||||
|
# Update default creation condition
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
```
|
||||||
|
|
||||||
|
##### PowerShell script (`scripts/powershell/update-agent-context.ps1`)
|
||||||
|
|
||||||
|
Add file variable:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
$windsurfFile = Join-Path $repoRoot '.windsurf/rules/specify-rules.md'
|
||||||
|
```
|
||||||
|
|
||||||
|
Add to switch statement:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
switch ($AgentType) {
|
||||||
|
# ... existing cases ...
|
||||||
|
'windsurf' { Update-AgentFile $windsurfFile 'Windsurf' }
|
||||||
|
'' {
|
||||||
|
foreach ($pair in @(
|
||||||
|
# ... existing pairs ...
|
||||||
|
@{file=$windsurfFile; name='Windsurf'}
|
||||||
|
)) {
|
||||||
|
if (Test-Path $pair.file) { Update-AgentFile $pair.file $pair.name }
|
||||||
|
}
|
||||||
|
# Update default creation condition
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 6. Update CLI Tool Checks (Optional)
|
||||||
|
|
||||||
|
For agents that require CLI tools, add checks in the `check()` command and agent validation:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# In check() command
|
||||||
|
tracker.add("windsurf", "Windsurf IDE (optional)")
|
||||||
|
windsurf_ok = check_tool_for_tracker("windsurf", "https://windsurf.com/", tracker)
|
||||||
|
|
||||||
|
# In init validation (only if CLI tool required)
|
||||||
|
elif selected_ai == "windsurf":
|
||||||
|
if not check_tool("windsurf", "Install from: https://windsurf.com/"):
|
||||||
|
console.print("[red]Error:[/red] Windsurf CLI is required for Windsurf projects")
|
||||||
|
agent_tool_missing = True
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note**: CLI tool checks are now handled automatically based on the `requires_cli` field in AGENT_CONFIG. No additional code changes needed in the `check()` or `init()` commands - they automatically loop through AGENT_CONFIG and check tools as needed.
|
||||||
|
|
||||||
|
## Important Design Decisions
|
||||||
|
|
||||||
|
### Using Actual CLI Tool Names as Keys
|
||||||
|
|
||||||
|
**CRITICAL**: When adding a new agent to AGENT_CONFIG, always use the **actual executable name** as the dictionary key, not a shortened or convenient version.
|
||||||
|
|
||||||
|
**Why this matters:**
|
||||||
|
|
||||||
|
- The `check_tool()` function uses `shutil.which(tool)` to find executables in the system PATH
|
||||||
|
- If the key doesn't match the actual CLI tool name, you'll need special-case mappings throughout the codebase
|
||||||
|
- This creates unnecessary complexity and maintenance burden
|
||||||
|
|
||||||
|
**Example - The Cursor Lesson:**
|
||||||
|
|
||||||
|
❌ **Wrong approach** (requires special-case mapping):
|
||||||
|
|
||||||
|
```python
|
||||||
|
AGENT_CONFIG = {
|
||||||
|
"cursor": { # Shorthand that doesn't match the actual tool
|
||||||
|
"name": "Cursor",
|
||||||
|
# ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Then you need special cases everywhere:
|
||||||
|
cli_tool = agent_key
|
||||||
|
if agent_key == "cursor":
|
||||||
|
cli_tool = "cursor-agent" # Map to the real tool name
|
||||||
|
```
|
||||||
|
|
||||||
|
✅ **Correct approach** (no mapping needed):
|
||||||
|
|
||||||
|
```python
|
||||||
|
AGENT_CONFIG = {
|
||||||
|
"cursor-agent": { # Matches the actual executable name
|
||||||
|
"name": "Cursor",
|
||||||
|
# ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# No special cases needed - just use agent_key directly!
|
||||||
|
```
|
||||||
|
|
||||||
|
**Benefits of this approach:**
|
||||||
|
|
||||||
|
- Eliminates special-case logic scattered throughout the codebase
|
||||||
|
- Makes the code more maintainable and easier to understand
|
||||||
|
- Reduces the chance of bugs when adding new agents
|
||||||
|
- Tool checking "just works" without additional mappings
|
||||||
|
|
||||||
|
#### 7. Update Devcontainer files (Optional)
|
||||||
|
|
||||||
|
For agents that have VS Code extensions or require CLI installation, update the devcontainer configuration files:
|
||||||
|
|
||||||
|
##### VS Code Extension-based Agents
|
||||||
|
|
||||||
|
For agents available as VS Code extensions, add them to `.devcontainer/devcontainer.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"customizations": {
|
||||||
|
"vscode": {
|
||||||
|
"extensions": [
|
||||||
|
// ... existing extensions ...
|
||||||
|
// [New Agent Name]
|
||||||
|
"[New Agent Extension ID]"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### CLI-based Agents
|
||||||
|
|
||||||
|
For agents that require CLI tools, add installation commands to `.devcontainer/post-create.sh`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Existing installations...
|
||||||
|
|
||||||
|
echo -e "\n🤖 Installing [New Agent Name] CLI..."
|
||||||
|
# run_command "npm install -g [agent-cli-package]@latest" # Example for node-based CLI
|
||||||
|
# or other installation instructions (must be non-interactive and compatible with Linux Debian "Trixie" or later)...
|
||||||
|
echo "✅ Done"
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
**Quick Tips:**
|
||||||
|
|
||||||
|
- **Extension-based agents**: Add to the `extensions` array in `devcontainer.json`
|
||||||
|
- **CLI-based agents**: Add installation scripts to `post-create.sh`
|
||||||
|
- **Hybrid agents**: May require both extension and CLI installation
|
||||||
|
- **Test thoroughly**: Ensure installations work in the devcontainer environment
|
||||||
|
|
||||||
|
## Agent Categories
|
||||||
|
|
||||||
|
### CLI-Based Agents
|
||||||
|
|
||||||
|
Require a command-line tool to be installed:
|
||||||
|
|
||||||
|
- **Claude Code**: `claude` CLI
|
||||||
|
- **Gemini CLI**: `gemini` CLI
|
||||||
|
- **Cursor**: `cursor-agent` CLI
|
||||||
|
- **Qwen Code**: `qwen` CLI
|
||||||
|
- **opencode**: `opencode` CLI
|
||||||
|
- **Amazon Q Developer CLI**: `q` CLI
|
||||||
|
- **CodeBuddy CLI**: `codebuddy` CLI
|
||||||
|
- **Qoder CLI**: `qoder` CLI
|
||||||
|
- **Amp**: `amp` CLI
|
||||||
|
- **SHAI**: `shai` CLI
|
||||||
|
|
||||||
|
### IDE-Based Agents
|
||||||
|
|
||||||
|
Work within integrated development environments:
|
||||||
|
|
||||||
|
- **GitHub Copilot**: Built into VS Code/compatible editors
|
||||||
|
- **Windsurf**: Built into Windsurf IDE
|
||||||
|
- **IBM Bob**: Built into IBM Bob IDE
|
||||||
|
|
||||||
|
## Command File Formats
|
||||||
|
|
||||||
|
### Markdown Format
|
||||||
|
|
||||||
|
Used by: Claude, Cursor, opencode, Windsurf, Amazon Q Developer, Amp, SHAI, IBM Bob
|
||||||
|
|
||||||
|
**Standard format:**
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
description: "Command description"
|
||||||
|
---
|
||||||
|
|
||||||
|
Command content with {SCRIPT} and $ARGUMENTS placeholders.
|
||||||
|
```
|
||||||
|
|
||||||
|
**GitHub Copilot Chat Mode format:**
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
description: "Command description"
|
||||||
|
mode: speckit.command-name
|
||||||
|
---
|
||||||
|
|
||||||
|
Command content with {SCRIPT} and $ARGUMENTS placeholders.
|
||||||
|
```
|
||||||
|
|
||||||
|
### TOML Format
|
||||||
|
|
||||||
|
Used by: Gemini, Qwen
|
||||||
|
|
||||||
|
```toml
|
||||||
|
description = "Command description"
|
||||||
|
|
||||||
|
prompt = """
|
||||||
|
Command content with {SCRIPT} and {{args}} placeholders.
|
||||||
|
"""
|
||||||
|
```
|
||||||
|
|
||||||
|
## Directory Conventions
|
||||||
|
|
||||||
|
- **CLI agents**: Usually `.<agent-name>/commands/`
|
||||||
|
- **IDE agents**: Follow IDE-specific patterns:
|
||||||
|
- Copilot: `.github/agents/`
|
||||||
|
- Cursor: `.cursor/commands/`
|
||||||
|
- Windsurf: `.windsurf/workflows/`
|
||||||
|
|
||||||
|
## Argument Patterns
|
||||||
|
|
||||||
|
Different agents use different argument placeholders:
|
||||||
|
|
||||||
|
- **Markdown/prompt-based**: `$ARGUMENTS`
|
||||||
|
- **TOML-based**: `{{args}}`
|
||||||
|
- **Script placeholders**: `{SCRIPT}` (replaced with actual script path)
|
||||||
|
- **Agent placeholders**: `__AGENT__` (replaced with agent name)
|
||||||
|
|
||||||
|
## Testing New Agent Integration
|
||||||
|
|
||||||
|
1. **Build test**: Run package creation script locally
|
||||||
|
2. **CLI test**: Test `specify init --ai <agent>` command
|
||||||
|
3. **File generation**: Verify correct directory structure and files
|
||||||
|
4. **Command validation**: Ensure generated commands work with the agent
|
||||||
|
5. **Context update**: Test agent context update scripts
|
||||||
|
|
||||||
|
## Common Pitfalls
|
||||||
|
|
||||||
|
1. **Using shorthand keys instead of actual CLI tool names**: Always use the actual executable name as the AGENT_CONFIG key (e.g., `"cursor-agent"` not `"cursor"`). This prevents the need for special-case mappings throughout the codebase.
|
||||||
|
2. **Forgetting update scripts**: Both bash and PowerShell scripts must be updated when adding new agents.
|
||||||
|
3. **Incorrect `requires_cli` value**: Set to `True` only for agents that actually have CLI tools to check; set to `False` for IDE-based agents.
|
||||||
|
4. **Wrong argument format**: Use correct placeholder format for each agent type (`$ARGUMENTS` for Markdown, `{{args}}` for TOML).
|
||||||
|
5. **Directory naming**: Follow agent-specific conventions exactly (check existing agents for patterns).
|
||||||
|
6. **Help text inconsistency**: Update all user-facing text consistently (help strings, docstrings, README, error messages).
|
||||||
|
|
||||||
|
## Future Considerations
|
||||||
|
|
||||||
|
When adding new agents:
|
||||||
|
|
||||||
|
- Consider the agent's native command/workflow patterns
|
||||||
|
- Ensure compatibility with the Spec-Driven Development process
|
||||||
|
- Document any special requirements or limitations
|
||||||
|
- Update this guide with lessons learned
|
||||||
|
- Verify the actual CLI tool name before adding to AGENT_CONFIG
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*This documentation should be updated whenever new agents are added to maintain accuracy and completeness.*
|
||||||
284
CHANGELOG.md
284
CHANGELOG.md
@@ -1,11 +1,289 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
All notable changes to the Specify CLI will be documented in this file.
|
<!-- markdownlint-disable MD024 -->
|
||||||
|
|
||||||
|
|
||||||
|
All notable changes to the Specify CLI and templates are documented here.
|
||||||
|
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## [Unreleased]
|
## [0.1.2] - 2026-02-20
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **Python 3.14 / Homebrew compatibility**: Added explicit `click>=8.1` dependency so the resolver always selects a Click version compatible with Python 3.14 and avoids errors such as `TypeError: ParamType.get_metavar() got an unexpected keyword argument 'ctx'` when an older Click would otherwise be used. Note that broader uv/Homebrew environment isolation or `sys.path` bleed issues (see [#1631](https://github.com/github/spec-kit/issues/1631)) may still require environment-level workarounds.
|
||||||
|
|
||||||
|
## [0.1.1] - 2026-02-13
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **Agent Skills Installation**: New `--ai-skills` CLI option to install Prompt.MD templates as agent skills following [agentskills.io specification](https://agentskills.io/specification)
|
||||||
|
- Skills are installed to agent-specific directories (e.g., `.claude/skills/`, `.gemini/skills/`, `.github/skills/`)
|
||||||
|
- Codex uses `.agents/skills/` following Codex agent directory conventions
|
||||||
|
- Default fallback directory is `.agents/skills/` for agents without a specific mapping
|
||||||
|
- Requires `--ai` flag to be specified
|
||||||
|
- Converts all 9 spec-kit command templates (specify, plan, tasks, implement, analyze, clarify, constitution, checklist, taskstoissues) to properly formatted SKILL.md files
|
||||||
|
- **New projects**: command files are not installed when `--ai-skills` is used (skills replace commands)
|
||||||
|
- **Existing repos** (`--here`): pre-existing command files are preserved — no breaking changes
|
||||||
|
- `pyyaml` dependency (already present) used for YAML frontmatter parsing
|
||||||
|
- **Unit tests** for `install_ai_skills`, `_get_skills_dir`, and `--ai-skills` CLI validation (51 test cases covering all 18 supported agents)
|
||||||
|
|
||||||
|
## [0.1.0] - 2026-01-28
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **Extension System**: Introduced modular extension architecture for Spec Kit
|
||||||
|
- Extensions are self-contained packages that add commands and functionality without bloating core
|
||||||
|
- Extension manifest schema (`extension.yml`) with validation
|
||||||
|
- Extension registry (`.specify/extensions/.registry`) for tracking installed extensions
|
||||||
|
- Extension manager module (`src/specify_cli/extensions.py`) for installation/removal
|
||||||
|
- New CLI commands:
|
||||||
|
- `specify extension list` - List installed extensions
|
||||||
|
- `specify extension add` - Install extension from local directory or URL
|
||||||
|
- `specify extension remove` - Uninstall extension
|
||||||
|
- `specify extension search` - Search extension catalog
|
||||||
|
- `specify extension info` - Show detailed extension information
|
||||||
|
- Semantic versioning compatibility checks
|
||||||
|
- Support for extension configuration files
|
||||||
|
- Command registration system for AI agents (Claude support initially)
|
||||||
|
- Added dependencies: `pyyaml>=6.0`, `packaging>=23.0`
|
||||||
|
|
||||||
|
- **Extension Catalog**: Extension discovery and distribution system
|
||||||
|
- Central catalog (`extensions/catalog.json`) for published extensions
|
||||||
|
- Extension catalog manager (`ExtensionCatalog` class) with:
|
||||||
|
- Catalog fetching from GitHub
|
||||||
|
- 1-hour local caching for performance
|
||||||
|
- Search by query, tag, author, or verification status
|
||||||
|
- Extension info retrieval
|
||||||
|
- Catalog cache stored in `.specify/extensions/.cache/`
|
||||||
|
- Search and info commands with rich console output
|
||||||
|
- Added 9 catalog-specific unit tests (100% pass rate)
|
||||||
|
|
||||||
|
- **Jira Extension**: First official extension for Jira integration
|
||||||
|
- Extension ID: `jira`
|
||||||
|
- Version: 1.0.0
|
||||||
|
- Commands:
|
||||||
|
- `/speckit.jira.specstoissues` - Create Jira hierarchy from spec and tasks
|
||||||
|
- `/speckit.jira.discover-fields` - Discover Jira custom fields
|
||||||
|
- `/speckit.jira.sync-status` - Sync task completion status
|
||||||
|
- Comprehensive documentation (README, usage guide, examples)
|
||||||
|
- MIT licensed
|
||||||
|
|
||||||
|
- **Hook System**: Extension lifecycle hooks for automation
|
||||||
|
- `HookExecutor` class for managing extension hooks
|
||||||
|
- Hooks registered in `.specify/extensions.yml`
|
||||||
|
- Hook registration during extension installation
|
||||||
|
- Hook unregistration during extension removal
|
||||||
|
- Support for optional and mandatory hooks
|
||||||
|
- Hook execution messages for AI agent integration
|
||||||
|
- Condition support for conditional hook execution (placeholder)
|
||||||
|
|
||||||
|
- **Extension Management**: Advanced extension management commands
|
||||||
|
- `specify extension update` - Check and update extensions to latest version
|
||||||
|
- `specify extension enable` - Enable a disabled extension
|
||||||
|
- `specify extension disable` - Disable extension without removing it
|
||||||
|
- Version comparison with catalog
|
||||||
|
- Update notifications
|
||||||
|
- Preserve configuration during updates
|
||||||
|
|
||||||
|
- **Multi-Agent Support**: Extensions now work with all supported AI agents (Phase 6)
|
||||||
|
- Automatic detection and registration for all agents in project
|
||||||
|
- Support for 16+ AI agents (Claude, Gemini, Copilot, Cursor, Qwen, and more)
|
||||||
|
- Agent-specific command formats (Markdown and TOML)
|
||||||
|
- Automatic argument placeholder conversion ($ARGUMENTS → {{args}})
|
||||||
|
- Commands registered for all detected agents during installation
|
||||||
|
- Multi-agent command unregistration on extension removal
|
||||||
|
- `CommandRegistrar.register_commands_for_agent()` method
|
||||||
|
- `CommandRegistrar.register_commands_for_all_agents()` method
|
||||||
|
|
||||||
|
- **Configuration Layers**: Full configuration cascade system (Phase 6)
|
||||||
|
- **Layer 1**: Defaults from extension manifest (`extension.yml`)
|
||||||
|
- **Layer 2**: Project config (`.specify/extensions/{ext-id}/{ext-id}-config.yml`)
|
||||||
|
- **Layer 3**: Local config (`.specify/extensions/{ext-id}/local-config.yml`, gitignored)
|
||||||
|
- **Layer 4**: Environment variables (`SPECKIT_{EXT_ID}_{KEY}` pattern)
|
||||||
|
- Recursive config merging with proper precedence
|
||||||
|
- `ConfigManager` class for programmatic config access
|
||||||
|
- `get_config()`, `get_value()`, `has_value()` methods
|
||||||
|
- Support for nested configuration paths with dot-notation
|
||||||
|
|
||||||
|
- **Hook Condition Evaluation**: Smart hook execution based on runtime conditions (Phase 6)
|
||||||
|
- Config conditions: `config.key.path is set`, `config.key == 'value'`, `config.key != 'value'`
|
||||||
|
- Environment conditions: `env.VAR is set`, `env.VAR == 'value'`, `env.VAR != 'value'`
|
||||||
|
- Automatic filtering of hooks based on condition evaluation
|
||||||
|
- Safe fallback behavior on evaluation errors
|
||||||
|
- Case-insensitive pattern matching
|
||||||
|
|
||||||
|
- **Hook Integration**: Agent-level hook checking and execution (Phase 6)
|
||||||
|
- `check_hooks_for_event()` method for AI agents to query hooks after core commands
|
||||||
|
- Condition-aware hook filtering before execution
|
||||||
|
- `enable_hooks()` and `disable_hooks()` methods per extension
|
||||||
|
- Formatted hook messages for agent display
|
||||||
|
- `execute_hook()` method for hook execution information
|
||||||
|
|
||||||
|
- **Documentation Suite**: Comprehensive documentation for users and developers
|
||||||
|
- **EXTENSION-USER-GUIDE.md**: Complete user guide with installation, usage, configuration, and troubleshooting
|
||||||
|
- **EXTENSION-API-REFERENCE.md**: Technical API reference with manifest schema, Python API, and CLI commands
|
||||||
|
- **EXTENSION-PUBLISHING-GUIDE.md**: Publishing guide for extension authors
|
||||||
|
- **RFC-EXTENSION-SYSTEM.md**: Extension architecture design document
|
||||||
|
|
||||||
|
- **Extension Template**: Starter template in `extensions/template/` for creating new extensions
|
||||||
|
- Fully commented `extension.yml` manifest template
|
||||||
|
- Example command file with detailed explanations
|
||||||
|
- Configuration template with all options
|
||||||
|
- Complete project structure (README, LICENSE, CHANGELOG, .gitignore)
|
||||||
|
- EXAMPLE-README.md showing final documentation format
|
||||||
|
|
||||||
|
- **Unit Tests**: Comprehensive test suite with 39 tests covering all extension system components
|
||||||
|
- Test coverage: 83% of extension module code
|
||||||
|
- Test dependencies: `pytest>=7.0`, `pytest-cov>=4.0`
|
||||||
|
- Configured pytest in `pyproject.toml`
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Version bumped to 0.1.0 (minor release for new feature)
|
||||||
|
|
||||||
|
## [0.0.22] - 2025-11-07
|
||||||
|
|
||||||
|
- Support for VS Code/Copilot agents, and moving away from prompts to proper agents with hand-offs.
|
||||||
|
- Move to use `AGENTS.md` for Copilot workloads, since it's already supported out-of-the-box.
|
||||||
|
- Adds support for the version command. ([#486](https://github.com/github/spec-kit/issues/486))
|
||||||
|
- Fixes potential bug with the `create-new-feature.ps1` script that ignores existing feature branches when determining next feature number ([#975](https://github.com/github/spec-kit/issues/975))
|
||||||
|
- Add graceful fallback and logging for GitHub API rate-limiting during template fetch ([#970](https://github.com/github/spec-kit/issues/970))
|
||||||
|
|
||||||
|
## [0.0.21] - 2025-10-21
|
||||||
|
|
||||||
|
- Fixes [#975](https://github.com/github/spec-kit/issues/975) (thank you [@fgalarraga](https://github.com/fgalarraga)).
|
||||||
|
- Adds support for Amp CLI.
|
||||||
|
- Adds support for VS Code hand-offs and moves prompts to be full-fledged chat modes.
|
||||||
|
- Adds support for `version` command (addresses [#811](https://github.com/github/spec-kit/issues/811) and [#486](https://github.com/github/spec-kit/issues/486), thank you [@mcasalaina](https://github.com/mcasalaina) and [@dentity007](https://github.com/dentity007)).
|
||||||
|
- Adds support for rendering the rate limit errors from the CLI when encountered ([#970](https://github.com/github/spec-kit/issues/970), thank you [@psmman](https://github.com/psmman)).
|
||||||
|
|
||||||
|
## [0.0.20] - 2025-10-14
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **Intelligent Branch Naming**: `create-new-feature` scripts now support `--short-name` parameter for custom branch names
|
||||||
|
- When `--short-name` provided: Uses the custom name directly (cleaned and formatted)
|
||||||
|
- When omitted: Automatically generates meaningful names using stop word filtering and length-based filtering
|
||||||
|
- Filters out common stop words (I, want, to, the, for, etc.)
|
||||||
|
- Removes words shorter than 3 characters (unless they're uppercase acronyms)
|
||||||
|
- Takes 3-4 most meaningful words from the description
|
||||||
|
- **Enforces GitHub's 244-byte branch name limit** with automatic truncation and warnings
|
||||||
|
- Examples:
|
||||||
|
- "I want to create user authentication" → `001-create-user-authentication`
|
||||||
|
- "Implement OAuth2 integration for API" → `001-implement-oauth2-integration-api`
|
||||||
|
- "Fix payment processing bug" → `001-fix-payment-processing`
|
||||||
|
- Very long descriptions are automatically truncated at word boundaries to stay within limits
|
||||||
|
- Designed for AI agents to provide semantic short names while maintaining standalone usability
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Enhanced help documentation for `create-new-feature.sh` and `create-new-feature.ps1` scripts with examples
|
||||||
|
- Branch names now validated against GitHub's 244-byte limit with automatic truncation if needed
|
||||||
|
|
||||||
|
## [0.0.19] - 2025-10-10
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Support for CodeBuddy (thank you to [@lispking](https://github.com/lispking) for the contribution).
|
||||||
|
- You can now see Git-sourced errors in the Specify CLI.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Fixed the path to the constitution in `plan.md` (thank you to [@lyzno1](https://github.com/lyzno1) for spotting).
|
||||||
|
- Fixed backslash escapes in generated TOML files for Gemini (thank you to [@hsin19](https://github.com/hsin19) for the contribution).
|
||||||
|
- Implementation command now ensures that the correct ignore files are added (thank you to [@sigent-amazon](https://github.com/sigent-amazon) for the contribution).
|
||||||
|
|
||||||
|
## [0.0.18] - 2025-10-06
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Support for using `.` as a shorthand for current directory in `specify init .` command, equivalent to `--here` flag but more intuitive for users.
|
||||||
|
- Use the `/speckit.` command prefix to easily discover Spec Kit-related commands.
|
||||||
|
- Refactor the prompts and templates to simplify their capabilities and how they are tracked. No more polluting things with tests when they are not needed.
|
||||||
|
- Ensure that tasks are created per user story (simplifies testing and validation).
|
||||||
|
- Add support for Visual Studio Code prompt shortcuts and automatic script execution.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- All command files now prefixed with `speckit.` (e.g., `speckit.specify.md`, `speckit.plan.md`) for better discoverability and differentiation in IDE/CLI command palettes and file explorers
|
||||||
|
|
||||||
|
## [0.0.17] - 2025-09-22
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- New `/clarify` command template to surface up to 5 targeted clarification questions for an existing spec and persist answers into a Clarifications section in the spec.
|
||||||
|
- New `/analyze` command template providing a non-destructive cross-artifact discrepancy and alignment report (spec, clarifications, plan, tasks, constitution) inserted after `/tasks` and before `/implement`.
|
||||||
|
- Note: Constitution rules are explicitly treated as non-negotiable; any conflict is a CRITICAL finding requiring artifact remediation, not weakening of principles.
|
||||||
|
|
||||||
|
## [0.0.16] - 2025-09-22
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- `--force` flag for `init` command to bypass confirmation when using `--here` in a non-empty directory and proceed with merging/overwriting files.
|
||||||
|
|
||||||
|
## [0.0.15] - 2025-09-21
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Support for Roo Code.
|
||||||
|
|
||||||
|
## [0.0.14] - 2025-09-21
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Error messages are now shown consistently.
|
||||||
|
|
||||||
|
## [0.0.13] - 2025-09-21
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Support for Kilo Code. Thank you [@shahrukhkhan489](https://github.com/shahrukhkhan489) with [#394](https://github.com/github/spec-kit/pull/394).
|
||||||
|
- Support for Auggie CLI. Thank you [@hungthai1401](https://github.com/hungthai1401) with [#137](https://github.com/github/spec-kit/pull/137).
|
||||||
|
- Agent folder security notice displayed after project provisioning completion, warning users that some agents may store credentials or auth tokens in their agent folders and recommending adding relevant folders to `.gitignore` to prevent accidental credential leakage.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Warning displayed to ensure that folks are aware that they might need to add their agent folder to `.gitignore`.
|
||||||
|
- Cleaned up the `check` command output.
|
||||||
|
|
||||||
|
## [0.0.12] - 2025-09-21
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Added additional context for OpenAI Codex users - they need to set an additional environment variable, as described in [#417](https://github.com/github/spec-kit/issues/417).
|
||||||
|
|
||||||
|
## [0.0.11] - 2025-09-20
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Codex CLI support (thank you [@honjo-hiroaki-gtt](https://github.com/honjo-hiroaki-gtt) for the contribution in [#14](https://github.com/github/spec-kit/pull/14))
|
||||||
|
- Codex-aware context update tooling (Bash and PowerShell) so feature plans refresh `AGENTS.md` alongside existing assistants without manual edits.
|
||||||
|
|
||||||
|
## [0.0.10] - 2025-09-20
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Addressed [#378](https://github.com/github/spec-kit/issues/378) where a GitHub token may be attached to the request when it was empty.
|
||||||
|
|
||||||
|
## [0.0.9] - 2025-09-19
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Improved agent selector UI with cyan highlighting for agent keys and gray parentheses for full names
|
||||||
|
|
||||||
|
## [0.0.8] - 2025-09-19
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Windsurf IDE support as additional AI assistant option (thank you [@raedkit](https://github.com/raedkit) for the work in [#151](https://github.com/github/spec-kit/pull/151))
|
||||||
|
- GitHub token support for API requests to handle corporate environments and rate limiting (contributed by [@zryfish](https://github.com/@zryfish) in [#243](https://github.com/github/spec-kit/pull/243))
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Updated README with Windsurf examples and GitHub token usage
|
||||||
|
- Enhanced release workflow to include Windsurf templates
|
||||||
|
|
||||||
## [0.0.7] - 2025-09-18
|
## [0.0.7] - 2025-09-18
|
||||||
|
|
||||||
@@ -14,7 +292,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Updated command instructions in the CLI.
|
- Updated command instructions in the CLI.
|
||||||
- Cleaned up the code to not render agent-specific information when it's generic.
|
- Cleaned up the code to not render agent-specific information when it's generic.
|
||||||
|
|
||||||
|
|
||||||
## [0.0.6] - 2025-09-17
|
## [0.0.6] - 2025-09-17
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
@@ -40,4 +317,3 @@ N/A
|
|||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
N/A
|
N/A
|
||||||
|
|
||||||
|
|||||||
@@ -14,21 +14,21 @@ orientation.
|
|||||||
Examples of behavior that contributes to creating a positive environment
|
Examples of behavior that contributes to creating a positive environment
|
||||||
include:
|
include:
|
||||||
|
|
||||||
* Using welcoming and inclusive language
|
- Using welcoming and inclusive language
|
||||||
* Being respectful of differing viewpoints and experiences
|
- Being respectful of differing viewpoints and experiences
|
||||||
* Gracefully accepting constructive criticism
|
- Gracefully accepting constructive criticism
|
||||||
* Focusing on what is best for the community
|
- Focusing on what is best for the community
|
||||||
* Showing empathy towards other community members
|
- Showing empathy towards other community members
|
||||||
|
|
||||||
Examples of unacceptable behavior by participants include:
|
Examples of unacceptable behavior by participants include:
|
||||||
|
|
||||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
- The use of sexualized language or imagery and unwelcome sexual attention or
|
||||||
advances
|
advances
|
||||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
- Trolling, insulting/derogatory comments, and personal or political attacks
|
||||||
* Public or private harassment
|
- Public or private harassment
|
||||||
* Publishing others' private information, such as a physical or electronic
|
- Publishing others' private information, such as a physical or electronic
|
||||||
address, without explicit permission
|
address, without explicit permission
|
||||||
* Other conduct which could reasonably be considered inappropriate in a
|
- Other conduct which could reasonably be considered inappropriate in a
|
||||||
professional setting
|
professional setting
|
||||||
|
|
||||||
## Our Responsibilities
|
## Our Responsibilities
|
||||||
|
|||||||
107
CONTRIBUTING.md
107
CONTRIBUTING.md
@@ -1,4 +1,4 @@
|
|||||||
## Contributing to Spec Kit
|
# Contributing to Spec Kit
|
||||||
|
|
||||||
Hi there! We're thrilled that you'd like to contribute to Spec Kit. Contributions to this project are [released](https://help.github.com/articles/github-terms-of-service/#6-contributions-under-repository-license) to the public under the [project's open source license](LICENSE).
|
Hi there! We're thrilled that you'd like to contribute to Spec Kit. Contributions to this project are [released](https://help.github.com/articles/github-terms-of-service/#6-contributions-under-repository-license) to the public under the [project's open source license](LICENSE).
|
||||||
|
|
||||||
@@ -11,12 +11,29 @@ These are one time installations required to be able to test your changes locall
|
|||||||
1. Install [Python 3.11+](https://www.python.org/downloads/)
|
1. Install [Python 3.11+](https://www.python.org/downloads/)
|
||||||
1. Install [uv](https://docs.astral.sh/uv/) for package management
|
1. Install [uv](https://docs.astral.sh/uv/) for package management
|
||||||
1. Install [Git](https://git-scm.com/downloads)
|
1. Install [Git](https://git-scm.com/downloads)
|
||||||
1. Have an AI coding agent available: [Claude Code](https://www.anthropic.com/claude-code), [GitHub Copilot](https://code.visualstudio.com/), [Gemini CLI](https://github.com/google-gemini/gemini-cli), or [Qwen Code](https://github.com/QwenLM/qwen-code). We're working on adding support for other agents as well.
|
1. Have an [AI coding agent available](README.md#-supported-ai-agents)
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><b>💡 Hint if you are using <code>VSCode</code> or <code>GitHub Codespaces</code> as your IDE</b></summary>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
Provided you have [Docker](https://docker.com) installed on your machine, you can leverage [Dev Containers](https://containers.dev) through this [VSCode extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers), to easily set up your development environment, with aforementioned tools already installed and configured, thanks to the `.devcontainer/devcontainer.json` file (located at the root of the project).
|
||||||
|
|
||||||
|
To do so, simply:
|
||||||
|
|
||||||
|
- Checkout the repo
|
||||||
|
- Open it with VSCode
|
||||||
|
- Open the [Command Palette](https://code.visualstudio.com/docs/getstarted/userinterface#_command-palette) and select "Dev Containers: Open Folder in Container..."
|
||||||
|
|
||||||
|
On [GitHub Codespaces](https://github.com/features/codespaces) it's even simpler, as it leverages the `.devcontainer/devcontainer.json` automatically upon opening the codespace.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
## Submitting a pull request
|
## Submitting a pull request
|
||||||
|
|
||||||
>[!NOTE]
|
> [!NOTE]
|
||||||
>If your pull request introduces a large change that materially impacts the work of the CLI or the rest of the repository (e.g., you're introducing new templates, arguments, or otherwise major changes), make sure that it was **discussed and agreed upon** by the project maintainers. Pull requests with large changes that did not have a prior conversation and agreement will be closed.
|
> If your pull request introduces a large change that materially impacts the work of the CLI or the rest of the repository (e.g., you're introducing new templates, arguments, or otherwise major changes), make sure that it was **discussed and agreed upon** by the project maintainers. Pull requests with large changes that did not have a prior conversation and agreement will be closed.
|
||||||
|
|
||||||
1. Fork and clone the repository
|
1. Fork and clone the repository
|
||||||
1. Configure and install the dependencies: `uv sync`
|
1. Configure and install the dependencies: `uv sync`
|
||||||
@@ -40,11 +57,91 @@ Here are a few things you can do that will increase the likelihood of your pull
|
|||||||
|
|
||||||
When working on spec-kit:
|
When working on spec-kit:
|
||||||
|
|
||||||
1. Test changes with the `specify` CLI commands (`/specify`, `/plan`, `/tasks`) in your coding agent of choice
|
1. Test changes with the `specify` CLI commands (`/speckit.specify`, `/speckit.plan`, `/speckit.tasks`) in your coding agent of choice
|
||||||
2. Verify templates are working correctly in `templates/` directory
|
2. Verify templates are working correctly in `templates/` directory
|
||||||
3. Test script functionality in the `scripts/` directory
|
3. Test script functionality in the `scripts/` directory
|
||||||
4. Ensure memory files (`memory/constitution.md`) are updated if major process changes are made
|
4. Ensure memory files (`memory/constitution.md`) are updated if major process changes are made
|
||||||
|
|
||||||
|
### Testing template and command changes locally
|
||||||
|
|
||||||
|
Running `uv run specify init` pulls released packages, which won’t include your local changes.
|
||||||
|
To test your templates, commands, and other changes locally, follow these steps:
|
||||||
|
|
||||||
|
1. **Create release packages**
|
||||||
|
|
||||||
|
Run the following command to generate the local packages:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./.github/workflows/scripts/create-release-packages.sh v1.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Copy the relevant package to your test project**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp -r .genreleases/sdd-copilot-package-sh/. <path-to-test-project>/
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Open and test the agent**
|
||||||
|
|
||||||
|
Navigate to your test project folder and open the agent to verify your implementation.
|
||||||
|
|
||||||
|
## AI contributions in Spec Kit
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
>
|
||||||
|
> If you are using **any kind of AI assistance** to contribute to Spec Kit,
|
||||||
|
> it must be disclosed in the pull request or issue.
|
||||||
|
|
||||||
|
We welcome and encourage the use of AI tools to help improve Spec Kit! Many valuable contributions have been enhanced with AI assistance for code generation, issue detection, and feature definition.
|
||||||
|
|
||||||
|
That being said, if you are using any kind of AI assistance (e.g., agents, ChatGPT) while contributing to Spec Kit,
|
||||||
|
**this must be disclosed in the pull request or issue**, along with the extent to which AI assistance was used (e.g., documentation comments vs. code generation).
|
||||||
|
|
||||||
|
If your PR responses or comments are being generated by an AI, disclose that as well.
|
||||||
|
|
||||||
|
As an exception, trivial spacing or typo fixes don't need to be disclosed, so long as the changes are limited to small parts of the code or short phrases.
|
||||||
|
|
||||||
|
An example disclosure:
|
||||||
|
|
||||||
|
> This PR was written primarily by GitHub Copilot.
|
||||||
|
|
||||||
|
Or a more detailed disclosure:
|
||||||
|
|
||||||
|
> I consulted ChatGPT to understand the codebase but the solution
|
||||||
|
> was fully authored manually by myself.
|
||||||
|
|
||||||
|
Failure to disclose this is first and foremost rude to the human operators on the other end of the pull request, but it also makes it difficult to
|
||||||
|
determine how much scrutiny to apply to the contribution.
|
||||||
|
|
||||||
|
In a perfect world, AI assistance would produce equal or higher quality work than any human. That isn't the world we live in today, and in most cases
|
||||||
|
where human supervision or expertise is not in the loop, it's generating code that cannot be reasonably maintained or evolved.
|
||||||
|
|
||||||
|
### What we're looking for
|
||||||
|
|
||||||
|
When submitting AI-assisted contributions, please ensure they include:
|
||||||
|
|
||||||
|
- **Clear disclosure of AI use** - You are transparent about AI use and degree to which you're using it for the contribution
|
||||||
|
- **Human understanding and testing** - You've personally tested the changes and understand what they do
|
||||||
|
- **Clear rationale** - You can explain why the change is needed and how it fits within Spec Kit's goals
|
||||||
|
- **Concrete evidence** - Include test cases, scenarios, or examples that demonstrate the improvement
|
||||||
|
- **Your own analysis** - Share your thoughts on the end-to-end developer experience
|
||||||
|
|
||||||
|
### What we'll close
|
||||||
|
|
||||||
|
We reserve the right to close contributions that appear to be:
|
||||||
|
|
||||||
|
- Untested changes submitted without verification
|
||||||
|
- Generic suggestions that don't address specific Spec Kit needs
|
||||||
|
- Bulk submissions that show no human review or understanding
|
||||||
|
|
||||||
|
### Guidelines for success
|
||||||
|
|
||||||
|
The key is demonstrating that you understand and have validated your proposed changes. If a maintainer can easily tell that a contribution was generated entirely by AI without human input or testing, it likely needs more work before submission.
|
||||||
|
|
||||||
|
Contributors who consistently submit low-effort AI-generated changes may be restricted from further contributions at the maintainers' discretion.
|
||||||
|
|
||||||
|
Please be respectful to maintainers and disclose AI assistance.
|
||||||
|
|
||||||
## Resources
|
## Resources
|
||||||
|
|
||||||
- [Spec-Driven Development Methodology](./spec-driven.md)
|
- [Spec-Driven Development Methodology](./spec-driven.md)
|
||||||
|
|||||||
1
LICENSE
1
LICENSE
@@ -19,3 +19,4 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
SOFTWARE.
|
SOFTWARE.
|
||||||
|
|
||||||
|
|||||||
406
README.md
406
README.md
@@ -1,31 +1,36 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
<img src="./media/logo_small.webp"/>
|
<img src="./media/logo_large.webp" alt="Spec Kit Logo" width="200" height="200"/>
|
||||||
<h1>🌱 Spec Kit</h1>
|
<h1>🌱 Spec Kit</h1>
|
||||||
<h3><em>Build high-quality software faster.</em></h3>
|
<h3><em>Build high-quality software faster.</em></h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<strong>An effort to allow organizations to focus on product scenarios rather than writing undifferentiated code with the help of Spec-Driven Development.</strong>
|
<strong>An open source toolkit that allows you to focus on product scenarios and predictable outcomes instead of vibe coding every piece from scratch.</strong>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
[](https://github.com/github/spec-kit/actions/workflows/release.yml)
|
<p align="center">
|
||||||
|
<a href="https://github.com/github/spec-kit/actions/workflows/release.yml"><img src="https://github.com/github/spec-kit/actions/workflows/release.yml/badge.svg" alt="Release"/></a>
|
||||||
|
<a href="https://github.com/github/spec-kit/stargazers"><img src="https://img.shields.io/github/stars/github/spec-kit?style=social" alt="GitHub stars"/></a>
|
||||||
|
<a href="https://github.com/github/spec-kit/blob/main/LICENSE"><img src="https://img.shields.io/github/license/github/spec-kit" alt="License"/></a>
|
||||||
|
<a href="https://github.github.io/spec-kit/"><img src="https://img.shields.io/badge/docs-GitHub_Pages-blue" alt="Documentation"/></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
|
|
||||||
- [🤔 What is Spec-Driven Development?](#-what-is-spec-driven-development)
|
- [🤔 What is Spec-Driven Development?](#-what-is-spec-driven-development)
|
||||||
- [⚡ Get started](#-get-started)
|
- [⚡ Get Started](#-get-started)
|
||||||
- [📽️ Video Overview](#️-video-overview)
|
- [📽️ Video Overview](#️-video-overview)
|
||||||
|
- [🤖 Supported AI Agents](#-supported-ai-agents)
|
||||||
- [🔧 Specify CLI Reference](#-specify-cli-reference)
|
- [🔧 Specify CLI Reference](#-specify-cli-reference)
|
||||||
- [📚 Core philosophy](#-core-philosophy)
|
- [📚 Core Philosophy](#-core-philosophy)
|
||||||
- [🌟 Development phases](#-development-phases)
|
- [🌟 Development Phases](#-development-phases)
|
||||||
- [🎯 Experimental goals](#-experimental-goals)
|
- [🎯 Experimental Goals](#-experimental-goals)
|
||||||
- [🔧 Prerequisites](#-prerequisites)
|
- [🔧 Prerequisites](#-prerequisites)
|
||||||
- [📖 Learn more](#-learn-more)
|
- [📖 Learn More](#-learn-more)
|
||||||
- [📋 Detailed process](#-detailed-process)
|
- [📋 Detailed Process](#-detailed-process)
|
||||||
- [🔍 Troubleshooting](#-troubleshooting)
|
- [🔍 Troubleshooting](#-troubleshooting)
|
||||||
- [👥 Maintainers](#-maintainers)
|
|
||||||
- [💬 Support](#-support)
|
- [💬 Support](#-support)
|
||||||
- [🙏 Acknowledgements](#-acknowledgements)
|
- [🙏 Acknowledgements](#-acknowledgements)
|
||||||
- [📄 License](#-license)
|
- [📄 License](#-license)
|
||||||
@@ -34,54 +39,96 @@
|
|||||||
|
|
||||||
Spec-Driven Development **flips the script** on traditional software development. For decades, code has been king — specifications were just scaffolding we built and discarded once the "real work" of coding began. Spec-Driven Development changes this: **specifications become executable**, directly generating working implementations rather than just guiding them.
|
Spec-Driven Development **flips the script** on traditional software development. For decades, code has been king — specifications were just scaffolding we built and discarded once the "real work" of coding began. Spec-Driven Development changes this: **specifications become executable**, directly generating working implementations rather than just guiding them.
|
||||||
|
|
||||||
## ⚡ Get started
|
## ⚡ Get Started
|
||||||
|
|
||||||
### 1. Install Specify
|
### 1. Install Specify CLI
|
||||||
|
|
||||||
Initialize your project depending on the coding agent you're using:
|
Choose your preferred installation method:
|
||||||
|
|
||||||
|
#### Option 1: Persistent Installation (Recommended)
|
||||||
|
|
||||||
|
Install once and use everywhere:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv tool install specify-cli --from git+https://github.com/github/spec-kit.git
|
||||||
|
```
|
||||||
|
|
||||||
|
Then use the tool directly:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create new project
|
||||||
|
specify init <PROJECT_NAME>
|
||||||
|
|
||||||
|
# Or initialize in existing project
|
||||||
|
specify init . --ai claude
|
||||||
|
# or
|
||||||
|
specify init --here --ai claude
|
||||||
|
|
||||||
|
# Check installed tools
|
||||||
|
specify check
|
||||||
|
```
|
||||||
|
|
||||||
|
To upgrade Specify, see the [Upgrade Guide](./docs/upgrade.md) for detailed instructions. Quick upgrade:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv tool install specify-cli --force --from git+https://github.com/github/spec-kit.git
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Option 2: One-time Usage
|
||||||
|
|
||||||
|
Run directly without installing:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
uvx --from git+https://github.com/github/spec-kit.git specify init <PROJECT_NAME>
|
uvx --from git+https://github.com/github/spec-kit.git specify init <PROJECT_NAME>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Benefits of persistent installation:**
|
||||||
|
|
||||||
|
- Tool stays installed and available in PATH
|
||||||
|
- No need to create shell aliases
|
||||||
|
- Better tool management with `uv tool list`, `uv tool upgrade`, `uv tool uninstall`
|
||||||
|
- Cleaner shell configuration
|
||||||
|
|
||||||
### 2. Establish project principles
|
### 2. Establish project principles
|
||||||
|
|
||||||
Use the **`/constitution`** command to create your project's governing principles and development guidelines that will guide all subsequent development.
|
Launch your AI assistant in the project directory. The `/speckit.*` commands are available in the assistant.
|
||||||
|
|
||||||
|
Use the **`/speckit.constitution`** command to create your project's governing principles and development guidelines that will guide all subsequent development.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
/constitution Create principles focused on code quality, testing standards, user experience consistency, and performance requirements
|
/speckit.constitution Create principles focused on code quality, testing standards, user experience consistency, and performance requirements
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Create the spec
|
### 3. Create the spec
|
||||||
|
|
||||||
Use the **`/specify`** command to describe what you want to build. Focus on the **what** and **why**, not the tech stack.
|
Use the **`/speckit.specify`** command to describe what you want to build. Focus on the **what** and **why**, not the tech stack.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
/specify Build an application that can help me organize my photos in separate photo albums. Albums are grouped by date and can be re-organized by dragging and dropping on the main page. Albums are never in other nested albums. Within each album, photos are previewed in a tile-like interface.
|
/speckit.specify Build an application that can help me organize my photos in separate photo albums. Albums are grouped by date and can be re-organized by dragging and dropping on the main page. Albums are never in other nested albums. Within each album, photos are previewed in a tile-like interface.
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4. Create a technical implementation plan
|
### 4. Create a technical implementation plan
|
||||||
|
|
||||||
Use the **`/plan`** command to provide your tech stack and architecture choices.
|
Use the **`/speckit.plan`** command to provide your tech stack and architecture choices.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
/plan The application uses Vite with minimal number of libraries. Use vanilla HTML, CSS, and JavaScript as much as possible. Images are not uploaded anywhere and metadata is stored in a local SQLite database.
|
/speckit.plan The application uses Vite with minimal number of libraries. Use vanilla HTML, CSS, and JavaScript as much as possible. Images are not uploaded anywhere and metadata is stored in a local SQLite database.
|
||||||
```
|
```
|
||||||
|
|
||||||
### 5. Break down into tasks
|
### 5. Break down into tasks
|
||||||
|
|
||||||
Use **`/tasks`** to create an actionable task list from your implementation plan.
|
Use **`/speckit.tasks`** to create an actionable task list from your implementation plan.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
/tasks
|
/speckit.tasks
|
||||||
```
|
```
|
||||||
|
|
||||||
### 6. Execute implementation
|
### 6. Execute implementation
|
||||||
|
|
||||||
Use **`/implement`** to execute all tasks and build your feature according to the plan.
|
Use **`/speckit.implement`** to execute all tasks and build your feature according to the plan.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
/implement
|
/speckit.implement
|
||||||
```
|
```
|
||||||
|
|
||||||
For detailed step-by-step instructions, see our [comprehensive guide](./spec-driven.md).
|
For detailed step-by-step instructions, see our [comprehensive guide](./spec-driven.md).
|
||||||
@@ -92,29 +139,56 @@ Want to see Spec Kit in action? Watch our [video overview](https://www.youtube.c
|
|||||||
|
|
||||||
[](https://www.youtube.com/watch?v=a9eR1xsfvHg&pp=0gcJCckJAYcqIYzv)
|
[](https://www.youtube.com/watch?v=a9eR1xsfvHg&pp=0gcJCckJAYcqIYzv)
|
||||||
|
|
||||||
|
## 🤖 Supported AI Agents
|
||||||
|
|
||||||
|
| Agent | Support | Notes |
|
||||||
|
| ------------------------------------------------------------------------------------ | ------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| [Qoder CLI](https://qoder.com/cli) | ✅ | |
|
||||||
|
| [Amazon Q Developer CLI](https://aws.amazon.com/developer/learning/q-developer-cli/) | ⚠️ | Amazon Q Developer CLI [does not support](https://github.com/aws/amazon-q-developer-cli/issues/3064) custom arguments for slash commands. |
|
||||||
|
| [Amp](https://ampcode.com/) | ✅ | |
|
||||||
|
| [Auggie CLI](https://docs.augmentcode.com/cli/overview) | ✅ | |
|
||||||
|
| [Claude Code](https://www.anthropic.com/claude-code) | ✅ | |
|
||||||
|
| [CodeBuddy CLI](https://www.codebuddy.ai/cli) | ✅ | |
|
||||||
|
| [Codex CLI](https://github.com/openai/codex) | ✅ | |
|
||||||
|
| [Cursor](https://cursor.sh/) | ✅ | |
|
||||||
|
| [Gemini CLI](https://github.com/google-gemini/gemini-cli) | ✅ | |
|
||||||
|
| [GitHub Copilot](https://code.visualstudio.com/) | ✅ | |
|
||||||
|
| [IBM Bob](https://www.ibm.com/products/bob) | ✅ | IDE-based agent with slash command support |
|
||||||
|
| [Jules](https://jules.google.com/) | ✅ | |
|
||||||
|
| [Kilo Code](https://github.com/Kilo-Org/kilocode) | ✅ | |
|
||||||
|
| [opencode](https://opencode.ai/) | ✅ | |
|
||||||
|
| [Qwen Code](https://github.com/QwenLM/qwen-code) | ✅ | |
|
||||||
|
| [Roo Code](https://roocode.com/) | ✅ | |
|
||||||
|
| [SHAI (OVHcloud)](https://github.com/ovh/shai) | ✅ | |
|
||||||
|
| [Windsurf](https://windsurf.com/) | ✅ | |
|
||||||
|
| [Antigravity (agy)](https://agy.ai/) | ✅ | |
|
||||||
|
|
||||||
## 🔧 Specify CLI Reference
|
## 🔧 Specify CLI Reference
|
||||||
|
|
||||||
The `specify` command supports the following options:
|
The `specify` command supports the following options:
|
||||||
|
|
||||||
### Commands
|
### Commands
|
||||||
|
|
||||||
| Command | Description |
|
| Command | Description |
|
||||||
|-------------|----------------------------------------------------------------|
|
| ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `init` | Initialize a new Specify project from the latest template |
|
| `init` | Initialize a new Specify project from the latest template |
|
||||||
| `check` | Check for installed tools (`git`, `claude`, `gemini`, `code`/`code-insiders`, `opencode`, `cursor-agent`) |
|
| `check` | Check for installed tools (`git`, `claude`, `gemini`, `code`/`code-insiders`, `cursor-agent`, `windsurf`, `qwen`, `opencode`, `codex`, `shai`, `qoder`) |
|
||||||
|
|
||||||
### `specify init` Arguments & Options
|
### `specify init` Arguments & Options
|
||||||
|
|
||||||
| Argument/Option | Type | Description |
|
| Argument/Option | Type | Description |
|
||||||
|------------------------|----------|------------------------------------------------------------------------------|
|
| ---------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `<project-name>` | Argument | Name for your new project directory (optional if using `--here`) |
|
| `<project-name>` | Argument | Name for your new project directory (optional if using `--here`, or use `.` for current directory) |
|
||||||
| `--ai` | Option | AI assistant to use: `claude`, `gemini`, `copilot`, `opencode`, or `cursor` |
|
| `--ai` | Option | AI assistant to use: `claude`, `gemini`, `copilot`, `cursor-agent`, `qwen`, `opencode`, `codex`, `windsurf`, `kilocode`, `auggie`, `roo`, `codebuddy`, `amp`, `shai`, `q`, `agy`, `bob`, or `qoder` |
|
||||||
| `--script` | Option | Script variant to use: `sh` (bash/zsh) or `ps` (PowerShell) |
|
| `--script` | Option | Script variant to use: `sh` (bash/zsh) or `ps` (PowerShell) |
|
||||||
| `--ignore-agent-tools` | Flag | Skip checks for AI agent tools like Claude Code |
|
| `--ignore-agent-tools` | Flag | Skip checks for AI agent tools like Claude Code |
|
||||||
| `--no-git` | Flag | Skip git repository initialization |
|
| `--no-git` | Flag | Skip git repository initialization |
|
||||||
| `--here` | Flag | Initialize project in the current directory instead of creating a new one |
|
| `--here` | Flag | Initialize project in the current directory instead of creating a new one |
|
||||||
| `--skip-tls` | Flag | Skip SSL/TLS verification (not recommended) |
|
| `--force` | Flag | Force merge/overwrite when initializing in current directory (skip confirmation) |
|
||||||
| `--debug` | Flag | Enable detailed debug output for troubleshooting |
|
| `--skip-tls` | Flag | Skip SSL/TLS verification (not recommended) |
|
||||||
|
| `--debug` | Flag | Enable detailed debug output for troubleshooting |
|
||||||
|
| `--github-token` | Option | GitHub token for API requests (or set GH_TOKEN/GITHUB_TOKEN env variable) |
|
||||||
|
| `--ai-skills` | Flag | Install Prompt.MD templates as agent skills in agent-specific `skills/` directory (requires `--ai`) |
|
||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
|
|
||||||
@@ -126,20 +200,51 @@ specify init my-project
|
|||||||
specify init my-project --ai claude
|
specify init my-project --ai claude
|
||||||
|
|
||||||
# Initialize with Cursor support
|
# Initialize with Cursor support
|
||||||
specify init my-project --ai cursor
|
specify init my-project --ai cursor-agent
|
||||||
|
|
||||||
|
# Initialize with Qoder support
|
||||||
|
specify init my-project --ai qoder
|
||||||
|
|
||||||
|
# Initialize with Windsurf support
|
||||||
|
specify init my-project --ai windsurf
|
||||||
|
|
||||||
|
# Initialize with Amp support
|
||||||
|
specify init my-project --ai amp
|
||||||
|
|
||||||
|
# Initialize with SHAI support
|
||||||
|
specify init my-project --ai shai
|
||||||
|
|
||||||
|
# Initialize with IBM Bob support
|
||||||
|
specify init my-project --ai bob
|
||||||
|
|
||||||
# Initialize with PowerShell scripts (Windows/cross-platform)
|
# Initialize with PowerShell scripts (Windows/cross-platform)
|
||||||
specify init my-project --ai copilot --script ps
|
specify init my-project --ai copilot --script ps
|
||||||
|
|
||||||
# Initialize in current directory
|
# Initialize in current directory
|
||||||
|
specify init . --ai copilot
|
||||||
|
# or use the --here flag
|
||||||
specify init --here --ai copilot
|
specify init --here --ai copilot
|
||||||
|
|
||||||
|
# Force merge into current (non-empty) directory without confirmation
|
||||||
|
specify init . --force --ai copilot
|
||||||
|
# or
|
||||||
|
specify init --here --force --ai copilot
|
||||||
|
|
||||||
# Skip git initialization
|
# Skip git initialization
|
||||||
specify init my-project --ai gemini --no-git
|
specify init my-project --ai gemini --no-git
|
||||||
|
|
||||||
# Enable debug output for troubleshooting
|
# Enable debug output for troubleshooting
|
||||||
specify init my-project --ai claude --debug
|
specify init my-project --ai claude --debug
|
||||||
|
|
||||||
|
# Use GitHub token for API requests (helpful for corporate environments)
|
||||||
|
specify init my-project --ai claude --github-token ghp_your_token_here
|
||||||
|
|
||||||
|
# Install agent skills with the project
|
||||||
|
specify init my-project --ai claude --ai-skills
|
||||||
|
|
||||||
|
# Initialize in current directory with agent skills
|
||||||
|
specify init --here --ai gemini --ai-skills
|
||||||
|
|
||||||
# Check system requirements
|
# Check system requirements
|
||||||
specify check
|
specify check
|
||||||
```
|
```
|
||||||
@@ -148,32 +253,52 @@ specify check
|
|||||||
|
|
||||||
After running `specify init`, your AI coding agent will have access to these slash commands for structured development:
|
After running `specify init`, your AI coding agent will have access to these slash commands for structured development:
|
||||||
|
|
||||||
| Command | Description |
|
#### Core Commands
|
||||||
|-----------------|-----------------------------------------------------------------------|
|
|
||||||
| `/constitution` | Create or update project governing principles and development guidelines |
|
|
||||||
| `/specify` | Define what you want to build (requirements and user stories) |
|
|
||||||
| `/plan` | Create technical implementation plans with your chosen tech stack |
|
|
||||||
| `/tasks` | Generate actionable task lists for implementation |
|
|
||||||
| `/implement` | Execute all tasks to build the feature according to the plan |
|
|
||||||
|
|
||||||
## 📚 Core philosophy
|
Essential commands for the Spec-Driven Development workflow:
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
| ----------------------- | ------------------------------------------------------------------------ |
|
||||||
|
| `/speckit.constitution` | Create or update project governing principles and development guidelines |
|
||||||
|
| `/speckit.specify` | Define what you want to build (requirements and user stories) |
|
||||||
|
| `/speckit.plan` | Create technical implementation plans with your chosen tech stack |
|
||||||
|
| `/speckit.tasks` | Generate actionable task lists for implementation |
|
||||||
|
| `/speckit.implement` | Execute all tasks to build the feature according to the plan |
|
||||||
|
|
||||||
|
#### Optional Commands
|
||||||
|
|
||||||
|
Additional commands for enhanced quality and validation:
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
|
||||||
|
| `/speckit.clarify` | Clarify underspecified areas (recommended before `/speckit.plan`; formerly `/quizme`) |
|
||||||
|
| `/speckit.analyze` | Cross-artifact consistency & coverage analysis (run after `/speckit.tasks`, before `/speckit.implement`) |
|
||||||
|
| `/speckit.checklist` | Generate custom quality checklists that validate requirements completeness, clarity, and consistency (like "unit tests for English") |
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
|
| Variable | Description |
|
||||||
|
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||||
|
| `SPECIFY_FEATURE` | Override feature detection for non-Git repositories. Set to the feature directory name (e.g., `001-photo-albums`) to work on a specific feature when not using Git branches.<br/>\*\*Must be set in the context of the agent you're working with prior to using `/speckit.plan` or follow-up commands. |
|
||||||
|
|
||||||
|
## 📚 Core Philosophy
|
||||||
|
|
||||||
Spec-Driven Development is a structured process that emphasizes:
|
Spec-Driven Development is a structured process that emphasizes:
|
||||||
|
|
||||||
- **Intent-driven development** where specifications define the "_what_" before the "_how_"
|
- **Intent-driven development** where specifications define the "*what*" before the "*how*"
|
||||||
- **Rich specification creation** using guardrails and organizational principles
|
- **Rich specification creation** using guardrails and organizational principles
|
||||||
- **Multi-step refinement** rather than one-shot code generation from prompts
|
- **Multi-step refinement** rather than one-shot code generation from prompts
|
||||||
- **Heavy reliance** on advanced AI model capabilities for specification interpretation
|
- **Heavy reliance** on advanced AI model capabilities for specification interpretation
|
||||||
|
|
||||||
## 🌟 Development phases
|
## 🌟 Development Phases
|
||||||
|
|
||||||
| Phase | Focus | Key Activities |
|
| Phase | Focus | Key Activities |
|
||||||
|-------|-------|----------------|
|
| ---------------------------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||||
| **0-to-1 Development** ("Greenfield") | Generate from scratch | <ul><li>Start with high-level requirements</li><li>Generate specifications</li><li>Plan implementation steps</li><li>Build production-ready applications</li></ul> |
|
| **0-to-1 Development** ("Greenfield") | Generate from scratch | <ul><li>Start with high-level requirements</li><li>Generate specifications</li><li>Plan implementation steps</li><li>Build production-ready applications</li></ul> |
|
||||||
| **Creative Exploration** | Parallel implementations | <ul><li>Explore diverse solutions</li><li>Support multiple technology stacks & architectures</li><li>Experiment with UX patterns</li></ul> |
|
| **Creative Exploration** | Parallel implementations | <ul><li>Explore diverse solutions</li><li>Support multiple technology stacks & architectures</li><li>Experiment with UX patterns</li></ul> |
|
||||||
| **Iterative Enhancement** ("Brownfield") | Brownfield modernization | <ul><li>Add features iteratively</li><li>Modernize legacy systems</li><li>Adapt processes</li></ul> |
|
| **Iterative Enhancement** ("Brownfield") | Brownfield modernization | <ul><li>Add features iteratively</li><li>Modernize legacy systems</li><li>Adapt processes</li></ul> |
|
||||||
|
|
||||||
## 🎯 Experimental goals
|
## 🎯 Experimental Goals
|
||||||
|
|
||||||
Our research and experimentation focus on:
|
Our research and experimentation focus on:
|
||||||
|
|
||||||
@@ -201,20 +326,22 @@ Our research and experimentation focus on:
|
|||||||
|
|
||||||
## 🔧 Prerequisites
|
## 🔧 Prerequisites
|
||||||
|
|
||||||
- **Linux/macOS** (or WSL2 on Windows)
|
- **Linux/macOS/Windows**
|
||||||
- AI coding agent: [Claude Code](https://www.anthropic.com/claude-code), [GitHub Copilot](https://code.visualstudio.com/), [Gemini CLI](https://github.com/google-gemini/gemini-cli), [Cursor](https://cursor.sh/), [Qwen CLI](https://github.com/QwenLM/qwen-code) or [opencode](https://opencode.ai/)
|
- [Supported](#-supported-ai-agents) AI coding agent.
|
||||||
- [uv](https://docs.astral.sh/uv/) for package management
|
- [uv](https://docs.astral.sh/uv/) for package management
|
||||||
- [Python 3.11+](https://www.python.org/downloads/)
|
- [Python 3.11+](https://www.python.org/downloads/)
|
||||||
- [Git](https://git-scm.com/downloads)
|
- [Git](https://git-scm.com/downloads)
|
||||||
|
|
||||||
## 📖 Learn more
|
If you encounter issues with an agent, please open an issue so we can refine the integration.
|
||||||
|
|
||||||
|
## 📖 Learn More
|
||||||
|
|
||||||
- **[Complete Spec-Driven Development Methodology](./spec-driven.md)** - Deep dive into the full process
|
- **[Complete Spec-Driven Development Methodology](./spec-driven.md)** - Deep dive into the full process
|
||||||
- **[Detailed Walkthrough](#-detailed-process)** - Step-by-step implementation guide
|
- **[Detailed Walkthrough](#-detailed-process)** - Step-by-step implementation guide
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📋 Detailed process
|
## 📋 Detailed Process
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Click to expand the detailed step-by-step walkthrough</summary>
|
<summary>Click to expand the detailed step-by-step walkthrough</summary>
|
||||||
@@ -228,7 +355,13 @@ specify init <project_name>
|
|||||||
Or initialize in the current directory:
|
Or initialize in the current directory:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
specify init .
|
||||||
|
# or use the --here flag
|
||||||
specify init --here
|
specify init --here
|
||||||
|
# Skip confirmation when the directory already has files
|
||||||
|
specify init . --force
|
||||||
|
# or
|
||||||
|
specify init --here --force
|
||||||
```
|
```
|
||||||
|
|
||||||

|

|
||||||
@@ -239,13 +372,23 @@ You will be prompted to select the AI agent you are using. You can also proactiv
|
|||||||
specify init <project_name> --ai claude
|
specify init <project_name> --ai claude
|
||||||
specify init <project_name> --ai gemini
|
specify init <project_name> --ai gemini
|
||||||
specify init <project_name> --ai copilot
|
specify init <project_name> --ai copilot
|
||||||
specify init <project_name> --ai qwen
|
|
||||||
specify init <project_name> --ai opencode
|
|
||||||
# Or in current directory:
|
# Or in current directory:
|
||||||
|
specify init . --ai claude
|
||||||
|
specify init . --ai codex
|
||||||
|
|
||||||
|
# or use --here flag
|
||||||
specify init --here --ai claude
|
specify init --here --ai claude
|
||||||
|
specify init --here --ai codex
|
||||||
|
|
||||||
|
# Force merge into a non-empty current directory
|
||||||
|
specify init . --force --ai claude
|
||||||
|
|
||||||
|
# or
|
||||||
|
specify init --here --force --ai claude
|
||||||
```
|
```
|
||||||
|
|
||||||
The CLI will check if you have Claude Code, Gemini CLI, Qwen CLI or opencode installed. If you do not, or you prefer to get the templates without checking for the right tools, use `--ignore-agent-tools` with your command:
|
The CLI will check if you have Claude Code, Gemini CLI, Cursor CLI, Qwen CLI, opencode, Codex CLI, Qoder CLI, or Amazon Q Developer CLI installed. If you do not, or you prefer to get the templates without checking for the right tools, use `--ignore-agent-tools` with your command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
specify init <project_name> --ai claude --ignore-agent-tools
|
specify init <project_name> --ai claude --ignore-agent-tools
|
||||||
@@ -257,22 +400,22 @@ Go to the project folder and run your AI agent. In our example, we're using `cla
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
You will know that things are configured correctly if you see the `/constitution`, `/specify`, `/plan`, `/tasks`, and `/implement` commands available.
|
You will know that things are configured correctly if you see the `/speckit.constitution`, `/speckit.specify`, `/speckit.plan`, `/speckit.tasks`, and `/speckit.implement` commands available.
|
||||||
|
|
||||||
The first step should be establishing your project's governing principles using the `/constitution` command. This helps ensure consistent decision-making throughout all subsequent development phases:
|
The first step should be establishing your project's governing principles using the `/speckit.constitution` command. This helps ensure consistent decision-making throughout all subsequent development phases:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
/constitution Create principles focused on code quality, testing standards, user experience consistency, and performance requirements. Include governance for how these principles should guide technical decisions and implementation choices.
|
/speckit.constitution Create principles focused on code quality, testing standards, user experience consistency, and performance requirements. Include governance for how these principles should guide technical decisions and implementation choices.
|
||||||
```
|
```
|
||||||
|
|
||||||
This step creates or updates the `/memory/constitution.md` file with your project's foundational guidelines that the AI agent will reference during specification, planning, and implementation phases.
|
This step creates or updates the `.specify/memory/constitution.md` file with your project's foundational guidelines that the AI agent will reference during specification, planning, and implementation phases.
|
||||||
|
|
||||||
### **STEP 2:** Create project specifications
|
### **STEP 2:** Create project specifications
|
||||||
|
|
||||||
With your project principles established, you can now create the functional specifications. Use the `/specify` command and then provide the concrete requirements for the project you want to develop.
|
With your project principles established, you can now create the functional specifications. Use the `/speckit.specify` command and then provide the concrete requirements for the project you want to develop.
|
||||||
|
|
||||||
>[!IMPORTANT]
|
> [!IMPORTANT]
|
||||||
>Be as explicit as possible about _what_ you are trying to build and _why_. **Do not focus on the tech stack at this point**.
|
> Be as explicit as possible about *what* you are trying to build and *why*. **Do not focus on the tech stack at this point**.
|
||||||
|
|
||||||
An example prompt:
|
An example prompt:
|
||||||
|
|
||||||
@@ -304,28 +447,38 @@ The produced specification should contain a set of user stories and functional r
|
|||||||
At this stage, your project folder contents should resemble the following:
|
At this stage, your project folder contents should resemble the following:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
├── memory
|
└── .specify
|
||||||
│ ├── constitution.md
|
├── memory
|
||||||
│ └── constitution_update_checklist.md
|
│ └── constitution.md
|
||||||
├── scripts
|
├── scripts
|
||||||
│ ├── check-task-prerequisites.sh
|
│ ├── check-prerequisites.sh
|
||||||
│ ├── common.sh
|
│ ├── common.sh
|
||||||
│ ├── create-new-feature.sh
|
│ ├── create-new-feature.sh
|
||||||
│ ├── get-feature-paths.sh
|
│ ├── setup-plan.sh
|
||||||
│ ├── setup-plan.sh
|
│ └── update-claude-md.sh
|
||||||
│ └── update-claude-md.sh
|
├── specs
|
||||||
├── specs
|
│ └── 001-create-taskify
|
||||||
│ └── 001-create-taskify
|
│ └── spec.md
|
||||||
│ └── spec.md
|
└── templates
|
||||||
└── templates
|
├── plan-template.md
|
||||||
├── plan-template.md
|
├── spec-template.md
|
||||||
├── spec-template.md
|
└── tasks-template.md
|
||||||
└── tasks-template.md
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### **STEP 3:** Functional specification clarification
|
### **STEP 3:** Functional specification clarification (required before planning)
|
||||||
|
|
||||||
With the baseline specification created, you can go ahead and clarify any of the requirements that were not captured properly within the first shot attempt. For example, you could use a prompt like this within the same Claude Code session:
|
With the baseline specification created, you can go ahead and clarify any of the requirements that were not captured properly within the first shot attempt.
|
||||||
|
|
||||||
|
You should run the structured clarification workflow **before** creating a technical plan to reduce rework downstream.
|
||||||
|
|
||||||
|
Preferred order:
|
||||||
|
|
||||||
|
1. Use `/speckit.clarify` (structured) – sequential, coverage-based questioning that records answers in a Clarifications section.
|
||||||
|
2. Optionally follow up with ad-hoc free-form refinement if something still feels vague.
|
||||||
|
|
||||||
|
If you intentionally want to skip clarification (e.g., spike or exploratory prototype), explicitly state that so the agent doesn't block on missing clarifications.
|
||||||
|
|
||||||
|
Example free-form refinement prompt (after `/speckit.clarify` if still needed):
|
||||||
|
|
||||||
```text
|
```text
|
||||||
For each sample project or project that you create there should be a variable number of tasks between 5 and 15
|
For each sample project or project that you create there should be a variable number of tasks between 5 and 15
|
||||||
@@ -343,7 +496,7 @@ It's important to use the interaction with Claude Code as an opportunity to clar
|
|||||||
|
|
||||||
### **STEP 4:** Generate a plan
|
### **STEP 4:** Generate a plan
|
||||||
|
|
||||||
You can now be specific about the tech stack and other technical requirements. You can use the `/plan` command that is built into the project template with a prompt like this:
|
You can now be specific about the tech stack and other technical requirements. You can use the `/speckit.plan` command that is built into the project template with a prompt like this:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
We are going to generate this using .NET Aspire, using Postgres as the database. The frontend should use
|
We are going to generate this using .NET Aspire, using Postgres as the database. The frontend should use
|
||||||
@@ -357,25 +510,23 @@ The output of this step will include a number of implementation detail documents
|
|||||||
.
|
.
|
||||||
├── CLAUDE.md
|
├── CLAUDE.md
|
||||||
├── memory
|
├── memory
|
||||||
│ ├── constitution.md
|
│ └── constitution.md
|
||||||
│ └── constitution_update_checklist.md
|
|
||||||
├── scripts
|
├── scripts
|
||||||
│ ├── check-task-prerequisites.sh
|
│ ├── check-prerequisites.sh
|
||||||
│ ├── common.sh
|
│ ├── common.sh
|
||||||
│ ├── create-new-feature.sh
|
│ ├── create-new-feature.sh
|
||||||
│ ├── get-feature-paths.sh
|
│ ├── setup-plan.sh
|
||||||
│ ├── setup-plan.sh
|
│ └── update-claude-md.sh
|
||||||
│ └── update-claude-md.sh
|
|
||||||
├── specs
|
├── specs
|
||||||
│ └── 001-create-taskify
|
│ └── 001-create-taskify
|
||||||
│ ├── contracts
|
│ ├── contracts
|
||||||
│ │ ├── api-spec.json
|
│ │ ├── api-spec.json
|
||||||
│ │ └── signalr-spec.md
|
│ │ └── signalr-spec.md
|
||||||
│ ├── data-model.md
|
│ ├── data-model.md
|
||||||
│ ├── plan.md
|
│ ├── plan.md
|
||||||
│ ├── quickstart.md
|
│ ├── quickstart.md
|
||||||
│ ├── research.md
|
│ ├── research.md
|
||||||
│ └── spec.md
|
│ └── spec.md
|
||||||
└── templates
|
└── templates
|
||||||
├── CLAUDE-template.md
|
├── CLAUDE-template.md
|
||||||
├── plan-template.md
|
├── plan-template.md
|
||||||
@@ -407,8 +558,8 @@ researching .NET Aspire in general and I don't think that's gonna do much for us
|
|||||||
That's way too untargeted research. The research needs to help you solve a specific targeted question.
|
That's way too untargeted research. The research needs to help you solve a specific targeted question.
|
||||||
```
|
```
|
||||||
|
|
||||||
>[!NOTE]
|
> [!NOTE]
|
||||||
>Claude Code might be over-eager and add components that you did not ask for. Ask it to clarify the rationale and the source of the change.
|
> Claude Code might be over-eager and add components that you did not ask for. Ask it to clarify the rationale and the source of the change.
|
||||||
|
|
||||||
### **STEP 5:** Have Claude Code validate the plan
|
### **STEP 5:** Have Claude Code validate the plan
|
||||||
|
|
||||||
@@ -426,26 +577,46 @@ This helps refine the implementation plan and helps you avoid potential blind sp
|
|||||||
|
|
||||||
You can also ask Claude Code (if you have the [GitHub CLI](https://docs.github.com/en/github-cli/github-cli) installed) to go ahead and create a pull request from your current branch to `main` with a detailed description, to make sure that the effort is properly tracked.
|
You can also ask Claude Code (if you have the [GitHub CLI](https://docs.github.com/en/github-cli/github-cli) installed) to go ahead and create a pull request from your current branch to `main` with a detailed description, to make sure that the effort is properly tracked.
|
||||||
|
|
||||||
>[!NOTE]
|
> [!NOTE]
|
||||||
>Before you have the agent implement it, it's also worth prompting Claude Code to cross-check the details to see if there are any over-engineered pieces (remember - it can be over-eager). If over-engineered components or decisions exist, you can ask Claude Code to resolve them. Ensure that Claude Code follows the [constitution](base/memory/constitution.md) as the foundational piece that it must adhere to when establishing the plan.
|
> Before you have the agent implement it, it's also worth prompting Claude Code to cross-check the details to see if there are any over-engineered pieces (remember - it can be over-eager). If over-engineered components or decisions exist, you can ask Claude Code to resolve them. Ensure that Claude Code follows the [constitution](base/memory/constitution.md) as the foundational piece that it must adhere to when establishing the plan.
|
||||||
|
|
||||||
### STEP 6: Implementation
|
### **STEP 6:** Generate task breakdown with /speckit.tasks
|
||||||
|
|
||||||
Once ready, use the `/implement` command to execute your implementation plan:
|
With the implementation plan validated, you can now break down the plan into specific, actionable tasks that can be executed in the correct order. Use the `/speckit.tasks` command to automatically generate a detailed task breakdown from your implementation plan:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
/implement
|
/speckit.tasks
|
||||||
```
|
```
|
||||||
|
|
||||||
The `/implement` command will:
|
This step creates a `tasks.md` file in your feature specification directory that contains:
|
||||||
|
|
||||||
|
- **Task breakdown organized by user story** - Each user story becomes a separate implementation phase with its own set of tasks
|
||||||
|
- **Dependency management** - Tasks are ordered to respect dependencies between components (e.g., models before services, services before endpoints)
|
||||||
|
- **Parallel execution markers** - Tasks that can run in parallel are marked with `[P]` to optimize development workflow
|
||||||
|
- **File path specifications** - Each task includes the exact file paths where implementation should occur
|
||||||
|
- **Test-driven development structure** - If tests are requested, test tasks are included and ordered to be written before implementation
|
||||||
|
- **Checkpoint validation** - Each user story phase includes checkpoints to validate independent functionality
|
||||||
|
|
||||||
|
The generated tasks.md provides a clear roadmap for the `/speckit.implement` command, ensuring systematic implementation that maintains code quality and allows for incremental delivery of user stories.
|
||||||
|
|
||||||
|
### **STEP 7:** Implementation
|
||||||
|
|
||||||
|
Once ready, use the `/speckit.implement` command to execute your implementation plan:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/speckit.implement
|
||||||
|
```
|
||||||
|
|
||||||
|
The `/speckit.implement` command will:
|
||||||
|
|
||||||
- Validate that all prerequisites are in place (constitution, spec, plan, and tasks)
|
- Validate that all prerequisites are in place (constitution, spec, plan, and tasks)
|
||||||
- Parse the task breakdown from `tasks.md`
|
- Parse the task breakdown from `tasks.md`
|
||||||
- Execute tasks in the correct order, respecting dependencies and parallel execution markers
|
- Execute tasks in the correct order, respecting dependencies and parallel execution markers
|
||||||
- Follow the TDD approach defined in your task plan
|
- Follow the TDD approach defined in your task plan
|
||||||
- Provide progress updates and handle errors appropriately
|
- Provide progress updates and handle errors appropriately
|
||||||
|
|
||||||
>[!IMPORTANT]
|
> [!IMPORTANT]
|
||||||
>The AI agent will execute local CLI commands (such as `dotnet`, `npm`, etc.) - make sure you have the required tools installed on your machine.
|
> The AI agent will execute local CLI commands (such as `dotnet`, `npm`, etc.) - make sure you have the required tools installed on your machine.
|
||||||
|
|
||||||
Once the implementation is complete, test the application and resolve any runtime errors that may not be visible in CLI logs (e.g., browser console errors). You can copy and paste such errors back to your AI agent for resolution.
|
Once the implementation is complete, test the application and resolve any runtime errors that may not be visible in CLI logs (e.g., browser console errors). You can copy and paste such errors back to your AI agent for resolution.
|
||||||
|
|
||||||
@@ -472,11 +643,6 @@ echo "Cleaning up..."
|
|||||||
rm gcm-linux_amd64.2.6.1.deb
|
rm gcm-linux_amd64.2.6.1.deb
|
||||||
```
|
```
|
||||||
|
|
||||||
## 👥 Maintainers
|
|
||||||
|
|
||||||
- Den Delimarsky ([@localden](https://github.com/localden))
|
|
||||||
- John Lam ([@jflam](https://github.com/jflam))
|
|
||||||
|
|
||||||
## 💬 Support
|
## 💬 Support
|
||||||
|
|
||||||
For support, please open a [GitHub issue](https://github.com/github/spec-kit/issues/new). We welcome bug reports, feature requests, and questions about using Spec-Driven Development.
|
For support, please open a [GitHub issue](https://github.com/github/spec-kit/issues/new). We welcome bug reports, feature requests, and questions about using Spec-Driven Development.
|
||||||
|
|||||||
18
SECURITY.md
18
SECURITY.md
@@ -1,6 +1,6 @@
|
|||||||
Thanks for helping make GitHub safe for everyone.
|
# Security Policy
|
||||||
|
|
||||||
# Security
|
Thanks for helping make GitHub safe for everyone.
|
||||||
|
|
||||||
GitHub takes the security of our software products and services seriously, including all of the open source code repositories managed through our GitHub organizations, such as [GitHub](https://github.com/GitHub).
|
GitHub takes the security of our software products and services seriously, including all of the open source code repositories managed through our GitHub organizations, such as [GitHub](https://github.com/GitHub).
|
||||||
|
|
||||||
@@ -16,13 +16,13 @@ Instead, please send an email to opensource-security[@]github.com.
|
|||||||
|
|
||||||
Please include as much of the information listed below as you can to help us better understand and resolve the issue:
|
Please include as much of the information listed below as you can to help us better understand and resolve the issue:
|
||||||
|
|
||||||
* The type of issue (e.g., buffer overflow, SQL injection, or cross-site scripting)
|
- The type of issue (e.g., buffer overflow, SQL injection, or cross-site scripting)
|
||||||
* Full paths of source file(s) related to the manifestation of the issue
|
- Full paths of source file(s) related to the manifestation of the issue
|
||||||
* The location of the affected source code (tag/branch/commit or direct URL)
|
- The location of the affected source code (tag/branch/commit or direct URL)
|
||||||
* Any special configuration required to reproduce the issue
|
- Any special configuration required to reproduce the issue
|
||||||
* Step-by-step instructions to reproduce the issue
|
- Step-by-step instructions to reproduce the issue
|
||||||
* Proof-of-concept or exploit code (if possible)
|
- Proof-of-concept or exploit code (if possible)
|
||||||
* Impact of the issue, including how an attacker might exploit the issue
|
- Impact of the issue, including how an attacker might exploit the issue
|
||||||
|
|
||||||
This information will help us triage your report more quickly.
|
This information will help us triage your report more quickly.
|
||||||
|
|
||||||
|
|||||||
1
docs/.gitignore
vendored
1
docs/.gitignore
vendored
@@ -6,3 +6,4 @@ obj/
|
|||||||
# Temporary files
|
# Temporary files
|
||||||
*.tmp
|
*.tmp
|
||||||
*.log
|
*.log
|
||||||
|
|
||||||
|
|||||||
@@ -7,11 +7,13 @@ This folder contains the documentation source files for Spec Kit, built using [D
|
|||||||
To build the documentation locally:
|
To build the documentation locally:
|
||||||
|
|
||||||
1. Install DocFX:
|
1. Install DocFX:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
dotnet tool install -g docfx
|
dotnet tool install -g docfx
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Build the documentation:
|
2. Build the documentation:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd docs
|
cd docs
|
||||||
docfx docfx.json --serve
|
docfx docfx.json --serve
|
||||||
|
|||||||
@@ -68,3 +68,4 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,13 +12,14 @@ Spec-Driven Development **flips the script** on traditional software development
|
|||||||
|
|
||||||
- [Installation Guide](installation.md)
|
- [Installation Guide](installation.md)
|
||||||
- [Quick Start Guide](quickstart.md)
|
- [Quick Start Guide](quickstart.md)
|
||||||
|
- [Upgrade Guide](upgrade.md)
|
||||||
- [Local Development](local-development.md)
|
- [Local Development](local-development.md)
|
||||||
|
|
||||||
## Core Philosophy
|
## Core Philosophy
|
||||||
|
|
||||||
Spec-Driven Development is a structured process that emphasizes:
|
Spec-Driven Development is a structured process that emphasizes:
|
||||||
|
|
||||||
- **Intent-driven development** where specifications define the "_what_" before the "_how_"
|
- **Intent-driven development** where specifications define the "*what*" before the "*how*"
|
||||||
- **Rich specification creation** using guardrails and organizational principles
|
- **Rich specification creation** using guardrails and organizational principles
|
||||||
- **Multi-step refinement** rather than one-shot code generation from prompts
|
- **Multi-step refinement** rather than one-shot code generation from prompts
|
||||||
- **Heavy reliance** on advanced AI model capabilities for specification interpretation
|
- **Heavy reliance** on advanced AI model capabilities for specification interpretation
|
||||||
@@ -36,27 +37,31 @@ Spec-Driven Development is a structured process that emphasizes:
|
|||||||
Our research and experimentation focus on:
|
Our research and experimentation focus on:
|
||||||
|
|
||||||
### Technology Independence
|
### Technology Independence
|
||||||
|
|
||||||
- Create applications using diverse technology stacks
|
- Create applications using diverse technology stacks
|
||||||
- Validate the hypothesis that Spec-Driven Development is a process not tied to specific technologies, programming languages, or frameworks
|
- Validate the hypothesis that Spec-Driven Development is a process not tied to specific technologies, programming languages, or frameworks
|
||||||
|
|
||||||
### Enterprise Constraints
|
### Enterprise Constraints
|
||||||
|
|
||||||
- Demonstrate mission-critical application development
|
- Demonstrate mission-critical application development
|
||||||
- Incorporate organizational constraints (cloud providers, tech stacks, engineering practices)
|
- Incorporate organizational constraints (cloud providers, tech stacks, engineering practices)
|
||||||
- Support enterprise design systems and compliance requirements
|
- Support enterprise design systems and compliance requirements
|
||||||
|
|
||||||
### User-Centric Development
|
### User-Centric Development
|
||||||
|
|
||||||
- Build applications for different user cohorts and preferences
|
- Build applications for different user cohorts and preferences
|
||||||
- Support various development approaches (from vibe-coding to AI-native development)
|
- Support various development approaches (from vibe-coding to AI-native development)
|
||||||
|
|
||||||
### Creative & Iterative Processes
|
### Creative & Iterative Processes
|
||||||
|
|
||||||
- Validate the concept of parallel implementation exploration
|
- Validate the concept of parallel implementation exploration
|
||||||
- Provide robust iterative feature development workflows
|
- Provide robust iterative feature development workflows
|
||||||
- Extend processes to handle upgrades and modernization tasks
|
- Extend processes to handle upgrades and modernization tasks
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Please see our [Contributing Guide](CONTRIBUTING.md) for information on how to contribute to this project.
|
Please see our [Contributing Guide](https://github.com/github/spec-kit/blob/main/CONTRIBUTING.md) for information on how to contribute to this project.
|
||||||
|
|
||||||
## Support
|
## Support
|
||||||
|
|
||||||
For support, please check our [Support Guide](SUPPORT.md) or open an issue on GitHub.
|
For support, please check our [Support Guide](https://github.com/github/spec-kit/blob/main/SUPPORT.md) or open an issue on GitHub.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
- **Linux/macOS** (or Windows; PowerShell scripts now supported without WSL)
|
- **Linux/macOS** (or Windows; PowerShell scripts now supported without WSL)
|
||||||
- AI coding agent: [Claude Code](https://www.anthropic.com/claude-code), [GitHub Copilot](https://code.visualstudio.com/), or [Gemini CLI](https://github.com/google-gemini/gemini-cli)
|
- AI coding agent: [Claude Code](https://www.anthropic.com/claude-code), [GitHub Copilot](https://code.visualstudio.com/), [Codebuddy CLI](https://www.codebuddy.ai/cli) or [Gemini CLI](https://github.com/google-gemini/gemini-cli)
|
||||||
- [uv](https://docs.astral.sh/uv/) for package management
|
- [uv](https://docs.astral.sh/uv/) for package management
|
||||||
- [Python 3.11+](https://www.python.org/downloads/)
|
- [Python 3.11+](https://www.python.org/downloads/)
|
||||||
- [Git](https://git-scm.com/downloads)
|
- [Git](https://git-scm.com/downloads)
|
||||||
@@ -21,6 +21,8 @@ uvx --from git+https://github.com/github/spec-kit.git specify init <PROJECT_NAME
|
|||||||
Or initialize in the current directory:
|
Or initialize in the current directory:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
uvx --from git+https://github.com/github/spec-kit.git specify init .
|
||||||
|
# or use the --here flag
|
||||||
uvx --from git+https://github.com/github/spec-kit.git specify init --here
|
uvx --from git+https://github.com/github/spec-kit.git specify init --here
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -32,6 +34,7 @@ You can proactively specify your AI agent during initialization:
|
|||||||
uvx --from git+https://github.com/github/spec-kit.git specify init <project_name> --ai claude
|
uvx --from git+https://github.com/github/spec-kit.git specify init <project_name> --ai claude
|
||||||
uvx --from git+https://github.com/github/spec-kit.git specify init <project_name> --ai gemini
|
uvx --from git+https://github.com/github/spec-kit.git specify init <project_name> --ai gemini
|
||||||
uvx --from git+https://github.com/github/spec-kit.git specify init <project_name> --ai copilot
|
uvx --from git+https://github.com/github/spec-kit.git specify init <project_name> --ai copilot
|
||||||
|
uvx --from git+https://github.com/github/spec-kit.git specify init <project_name> --ai codebuddy
|
||||||
```
|
```
|
||||||
|
|
||||||
### Specify Script Type (Shell vs PowerShell)
|
### Specify Script Type (Shell vs PowerShell)
|
||||||
@@ -39,11 +42,13 @@ uvx --from git+https://github.com/github/spec-kit.git specify init <project_name
|
|||||||
All automation scripts now have both Bash (`.sh`) and PowerShell (`.ps1`) variants.
|
All automation scripts now have both Bash (`.sh`) and PowerShell (`.ps1`) variants.
|
||||||
|
|
||||||
Auto behavior:
|
Auto behavior:
|
||||||
|
|
||||||
- Windows default: `ps`
|
- Windows default: `ps`
|
||||||
- Other OS default: `sh`
|
- Other OS default: `sh`
|
||||||
- Interactive mode: you'll be prompted unless you pass `--script`
|
- Interactive mode: you'll be prompted unless you pass `--script`
|
||||||
|
|
||||||
Force a specific script type:
|
Force a specific script type:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
uvx --from git+https://github.com/github/spec-kit.git specify init <project_name> --script sh
|
uvx --from git+https://github.com/github/spec-kit.git specify init <project_name> --script sh
|
||||||
uvx --from git+https://github.com/github/spec-kit.git specify init <project_name> --script ps
|
uvx --from git+https://github.com/github/spec-kit.git specify init <project_name> --script ps
|
||||||
@@ -60,9 +65,10 @@ uvx --from git+https://github.com/github/spec-kit.git specify init <project_name
|
|||||||
## Verification
|
## Verification
|
||||||
|
|
||||||
After initialization, you should see the following commands available in your AI agent:
|
After initialization, you should see the following commands available in your AI agent:
|
||||||
- `/specify` - Create specifications
|
|
||||||
- `/plan` - Generate implementation plans
|
- `/speckit.specify` - Create specifications
|
||||||
- `/tasks` - Break down into actionable tasks
|
- `/speckit.plan` - Generate implementation plans
|
||||||
|
- `/speckit.tasks` - Break down into actionable tasks
|
||||||
|
|
||||||
The `.specify/scripts` directory will contain both `.sh` and `.ps1` scripts.
|
The `.specify/scripts` directory will contain both `.sh` and `.ps1` scripts.
|
||||||
|
|
||||||
|
|||||||
@@ -73,12 +73,14 @@ uvx --from /mnt/c/GitHub/spec-kit specify init demo-anywhere --ai copilot --igno
|
|||||||
```
|
```
|
||||||
|
|
||||||
Set an environment variable for convenience:
|
Set an environment variable for convenience:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
export SPEC_KIT_SRC=/mnt/c/GitHub/spec-kit
|
export SPEC_KIT_SRC=/mnt/c/GitHub/spec-kit
|
||||||
uvx --from "$SPEC_KIT_SRC" specify init demo-env --ai copilot --ignore-agent-tools --script ps
|
uvx --from "$SPEC_KIT_SRC" specify init demo-env --ai copilot --ignore-agent-tools --script ps
|
||||||
```
|
```
|
||||||
|
|
||||||
(Optional) Define a shell function:
|
(Optional) Define a shell function:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
specify-dev() { uvx --from /mnt/c/GitHub/spec-kit specify "$@"; }
|
specify-dev() { uvx --from /mnt/c/GitHub/spec-kit specify "$@"; }
|
||||||
# Then
|
# Then
|
||||||
@@ -93,11 +95,13 @@ After running an `init`, check that shell scripts are executable on POSIX system
|
|||||||
ls -l scripts | grep .sh
|
ls -l scripts | grep .sh
|
||||||
# Expect owner execute bit (e.g. -rwxr-xr-x)
|
# Expect owner execute bit (e.g. -rwxr-xr-x)
|
||||||
```
|
```
|
||||||
|
|
||||||
On Windows you will instead use the `.ps1` scripts (no chmod needed).
|
On Windows you will instead use the `.ps1` scripts (no chmod needed).
|
||||||
|
|
||||||
## 6. Run Lint / Basic Checks (Add Your Own)
|
## 6. Run Lint / Basic Checks (Add Your Own)
|
||||||
|
|
||||||
Currently no enforced lint config is bundled, but you can quickly sanity check importability:
|
Currently no enforced lint config is bundled, but you can quickly sanity check importability:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python -c "import specify_cli; print('Import OK')"
|
python -c "import specify_cli; print('Import OK')"
|
||||||
```
|
```
|
||||||
@@ -110,6 +114,7 @@ Validate packaging before publishing:
|
|||||||
uv build
|
uv build
|
||||||
ls dist/
|
ls dist/
|
||||||
```
|
```
|
||||||
|
|
||||||
Install the built artifact into a fresh throwaway environment if needed.
|
Install the built artifact into a fresh throwaway environment if needed.
|
||||||
|
|
||||||
## 8. Using a Temporary Workspace
|
## 8. Using a Temporary Workspace
|
||||||
@@ -120,6 +125,7 @@ When testing `init --here` in a dirty directory, create a temp workspace:
|
|||||||
mkdir /tmp/spec-test && cd /tmp/spec-test
|
mkdir /tmp/spec-test && cd /tmp/spec-test
|
||||||
python -m src.specify_cli init --here --ai claude --ignore-agent-tools --script sh # if repo copied here
|
python -m src.specify_cli init --here --ai claude --ignore-agent-tools --script sh # if repo copied here
|
||||||
```
|
```
|
||||||
|
|
||||||
Or copy only the modified CLI portion if you want a lighter sandbox.
|
Or copy only the modified CLI portion if you want a lighter sandbox.
|
||||||
|
|
||||||
## 9. Debug Network / TLS Skips
|
## 9. Debug Network / TLS Skips
|
||||||
@@ -130,6 +136,7 @@ If you need to bypass TLS validation while experimenting:
|
|||||||
specify check --skip-tls
|
specify check --skip-tls
|
||||||
specify init demo --skip-tls --ai gemini --ignore-agent-tools --script ps
|
specify init demo --skip-tls --ai gemini --ignore-agent-tools --script ps
|
||||||
```
|
```
|
||||||
|
|
||||||
(Use only for local experimentation.)
|
(Use only for local experimentation.)
|
||||||
|
|
||||||
## 10. Rapid Edit Loop Summary
|
## 10. Rapid Edit Loop Summary
|
||||||
@@ -146,6 +153,7 @@ specify init demo --skip-tls --ai gemini --ignore-agent-tools --script ps
|
|||||||
## 11. Cleaning Up
|
## 11. Cleaning Up
|
||||||
|
|
||||||
Remove build artifacts / virtual env quickly:
|
Remove build artifacts / virtual env quickly:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
rm -rf .venv dist build *.egg-info
|
rm -rf .venv dist build *.egg-info
|
||||||
```
|
```
|
||||||
@@ -165,4 +173,3 @@ rm -rf .venv dist build *.egg-info
|
|||||||
- Update docs and run through Quick Start using your modified CLI
|
- Update docs and run through Quick Start using your modified CLI
|
||||||
- Open a PR when satisfied
|
- Open a PR when satisfied
|
||||||
- (Optional) Tag a release once changes land in `main`
|
- (Optional) Tag a release once changes land in `main`
|
||||||
|
|
||||||
|
|||||||
@@ -2,49 +2,98 @@
|
|||||||
|
|
||||||
This guide will help you get started with Spec-Driven Development using Spec Kit.
|
This guide will help you get started with Spec-Driven Development using Spec Kit.
|
||||||
|
|
||||||
> NEW: All automation scripts now provide both Bash (`.sh`) and PowerShell (`.ps1`) variants. The `specify` CLI auto-selects based on OS unless you pass `--script sh|ps`.
|
> [!NOTE]
|
||||||
|
> All automation scripts now provide both Bash (`.sh`) and PowerShell (`.ps1`) variants. The `specify` CLI auto-selects based on OS unless you pass `--script sh|ps`.
|
||||||
|
|
||||||
## The 4-Step Process
|
## The 6-Step Process
|
||||||
|
|
||||||
### 1. Install Specify
|
> [!TIP]
|
||||||
|
> **Context Awareness**: Spec Kit commands automatically detect the active feature based on your current Git branch (e.g., `001-feature-name`). To switch between different specifications, simply switch Git branches.
|
||||||
|
|
||||||
Initialize your project depending on the coding agent you're using:
|
### Step 1: Install Specify
|
||||||
|
|
||||||
|
**In your terminal**, run the `specify` CLI command to initialize your project:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# Create a new project directory
|
||||||
uvx --from git+https://github.com/github/spec-kit.git specify init <PROJECT_NAME>
|
uvx --from git+https://github.com/github/spec-kit.git specify init <PROJECT_NAME>
|
||||||
|
|
||||||
|
# OR initialize in the current directory
|
||||||
|
uvx --from git+https://github.com/github/spec-kit.git specify init .
|
||||||
```
|
```
|
||||||
|
|
||||||
Pick script type explicitly (optional):
|
Pick script type explicitly (optional):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
uvx --from git+https://github.com/github/spec-kit.git specify init <PROJECT_NAME> --script ps # Force PowerShell
|
uvx --from git+https://github.com/github/spec-kit.git specify init <PROJECT_NAME> --script ps # Force PowerShell
|
||||||
uvx --from git+https://github.com/github/spec-kit.git specify init <PROJECT_NAME> --script sh # Force POSIX shell
|
uvx --from git+https://github.com/github/spec-kit.git specify init <PROJECT_NAME> --script sh # Force POSIX shell
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Create the Spec
|
### Step 2: Define Your Constitution
|
||||||
|
|
||||||
Use the `/specify` command to describe what you want to build. Focus on the **what** and **why**, not the tech stack.
|
**In your AI Agent's chat interface**, use the `/speckit.constitution` slash command to establish the core rules and principles for your project. You should provide your project's specific principles as arguments.
|
||||||
|
|
||||||
```bash
|
```markdown
|
||||||
/specify Build an application that can help me organize my photos in separate photo albums. Albums are grouped by date and can be re-organized by dragging and dropping on the main page. Albums are never in other nested albums. Within each album, photos are previewed in a tile-like interface.
|
/speckit.constitution This project follows a "Library-First" approach. All features must be implemented as standalone libraries first. We use TDD strictly. We prefer functional programming patterns.
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Create a Technical Implementation Plan
|
### Step 3: Create the Spec
|
||||||
|
|
||||||
Use the `/plan` command to provide your tech stack and architecture choices.
|
**In the chat**, use the `/speckit.specify` slash command to describe what you want to build. Focus on the **what** and **why**, not the tech stack.
|
||||||
|
|
||||||
```bash
|
```markdown
|
||||||
/plan The application uses Vite with minimal number of libraries. Use vanilla HTML, CSS, and JavaScript as much as possible. Images are not uploaded anywhere and metadata is stored in a local SQLite database.
|
/speckit.specify Build an application that can help me organize my photos in separate photo albums. Albums are grouped by date and can be re-organized by dragging and dropping on the main page. Albums are never in other nested albums. Within each album, photos are previewed in a tile-like interface.
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4. Break Down and Implement
|
### Step 4: Refine the Spec
|
||||||
|
|
||||||
Use `/tasks` to create an actionable task list, then ask your agent to implement the feature.
|
**In the chat**, use the `/speckit.clarify` slash command to identify and resolve ambiguities in your specification. You can provide specific focus areas as arguments.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/speckit.clarify Focus on security and performance requirements.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 5: Create a Technical Implementation Plan
|
||||||
|
|
||||||
|
**In the chat**, use the `/speckit.plan` slash command to provide your tech stack and architecture choices.
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
/speckit.plan The application uses Vite with minimal number of libraries. Use vanilla HTML, CSS, and JavaScript as much as possible. Images are not uploaded anywhere and metadata is stored in a local SQLite database.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 6: Break Down and Implement
|
||||||
|
|
||||||
|
**In the chat**, use the `/speckit.tasks` slash command to create an actionable task list.
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
/speckit.tasks
|
||||||
|
```
|
||||||
|
|
||||||
|
Optionally, validate the plan with `/speckit.analyze`:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
/speckit.analyze
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, use the `/speckit.implement` slash command to execute the plan.
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
/speckit.implement
|
||||||
|
```
|
||||||
|
|
||||||
## Detailed Example: Building Taskify
|
## Detailed Example: Building Taskify
|
||||||
|
|
||||||
Here's a complete example of building a team productivity platform:
|
Here's a complete example of building a team productivity platform:
|
||||||
|
|
||||||
### Step 1: Define Requirements with `/specify`
|
### Step 1: Define Constitution
|
||||||
|
|
||||||
|
Initialize the project's constitution to set ground rules:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
/speckit.constitution Taskify is a "Security-First" application. All user inputs must be validated. We use a microservices architecture. Code must be fully documented.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: Define Requirements with `/speckit.specify`
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Develop Taskify, a team productivity platform. It should allow users to create projects, add team members,
|
Develop Taskify, a team productivity platform. It should allow users to create projects, add team members,
|
||||||
@@ -53,58 +102,51 @@ let's call it "Create Taskify," let's have multiple users but the users will be
|
|||||||
I want five users in two different categories, one product manager and four engineers. Let's create three
|
I want five users in two different categories, one product manager and four engineers. Let's create three
|
||||||
different sample projects. Let's have the standard Kanban columns for the status of each task, such as "To Do,"
|
different sample projects. Let's have the standard Kanban columns for the status of each task, such as "To Do,"
|
||||||
"In Progress," "In Review," and "Done." There will be no login for this application as this is just the very
|
"In Progress," "In Review," and "Done." There will be no login for this application as this is just the very
|
||||||
first testing thing to ensure that our basic features are set up. For each task in the UI for a task card,
|
first testing thing to ensure that our basic features are set up.
|
||||||
you should be able to change the current status of the task between the different columns in the Kanban work board.
|
|
||||||
You should be able to leave an unlimited number of comments for a particular card. You should be able to, from that task
|
|
||||||
card, assign one of the valid users. When you first launch Taskify, it's going to give you a list of the five users to pick
|
|
||||||
from. There will be no password required. When you click on a user, you go into the main view, which displays the list of
|
|
||||||
projects. When you click on a project, you open the Kanban board for that project. You're going to see the columns.
|
|
||||||
You'll be able to drag and drop cards back and forth between different columns. You will see any cards that are
|
|
||||||
assigned to you, the currently logged in user, in a different color from all the other ones, so you can quickly
|
|
||||||
see yours. You can edit any comments that you make, but you can't edit comments that other people made. You can
|
|
||||||
delete any comments that you made, but you can't delete comments anybody else made.
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Step 2: Refine the Specification
|
### Step 3: Refine the Specification
|
||||||
|
|
||||||
After the initial specification is created, clarify any missing requirements:
|
Use the `/speckit.clarify` command to interactively resolve any ambiguities in your specification. You can also provide specific details you want to ensure are included.
|
||||||
|
|
||||||
```text
|
```bash
|
||||||
For each sample project or project that you create there should be a variable number of tasks between 5 and 15
|
/speckit.clarify I want to clarify the task card details. For each task in the UI for a task card, you should be able to change the current status of the task between the different columns in the Kanban work board. You should be able to leave an unlimited number of comments for a particular card. You should be able to, from that task card, assign one of the valid users.
|
||||||
tasks for each one randomly distributed into different states of completion. Make sure that there's at least
|
|
||||||
one task in each stage of completion.
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Also validate the specification checklist:
|
You can continue to refine the spec with more details using `/speckit.clarify`:
|
||||||
|
|
||||||
```text
|
```bash
|
||||||
Read the review and acceptance checklist, and check off each item in the checklist if the feature spec meets the criteria. Leave it empty if it does not.
|
/speckit.clarify When you first launch Taskify, it's going to give you a list of the five users to pick from. There will be no password required. When you click on a user, you go into the main view, which displays the list of projects. When you click on a project, you open the Kanban board for that project. You're going to see the columns. You'll be able to drag and drop cards back and forth between different columns. You will see any cards that are assigned to you, the currently logged in user, in a different color from all the other ones, so you can quickly see yours. You can edit any comments that you make, but you can't edit comments that other people made. You can delete any comments that you made, but you can't delete comments anybody else made.
|
||||||
```
|
```
|
||||||
|
|
||||||
### Step 3: Generate Technical Plan with `/plan`
|
### Step 4: Validate the Spec
|
||||||
|
|
||||||
|
Validate the specification checklist using the `/speckit.checklist` command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/speckit.checklist
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 5: Generate Technical Plan with `/speckit.plan`
|
||||||
|
|
||||||
Be specific about your tech stack and technical requirements:
|
Be specific about your tech stack and technical requirements:
|
||||||
|
|
||||||
```text
|
```bash
|
||||||
We are going to generate this using .NET Aspire, using Postgres as the database. The frontend should use
|
/speckit.plan We are going to generate this using .NET Aspire, using Postgres as the database. The frontend should use Blazor server with drag-and-drop task boards, real-time updates. There should be a REST API created with a projects API, tasks API, and a notifications API.
|
||||||
Blazor server with drag-and-drop task boards, real-time updates. There should be a REST API created with a projects API,
|
|
||||||
tasks API, and a notifications API.
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Step 4: Validate and Implement
|
### Step 6: Validate and Implement
|
||||||
|
|
||||||
Have your AI agent audit the implementation plan:
|
Have your AI agent audit the implementation plan using `/speckit.analyze`:
|
||||||
|
|
||||||
```text
|
```bash
|
||||||
Now I want you to go and audit the implementation plan and the implementation detail files.
|
/speckit.analyze
|
||||||
Read through it with an eye on determining whether or not there is a sequence of tasks that you need
|
|
||||||
to be doing that are obvious from reading this. Because I don't know if there's enough here.
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Finally, implement the solution:
|
Finally, implement the solution:
|
||||||
|
|
||||||
```text
|
```bash
|
||||||
implement specs/002-create-taskify/plan.md
|
/speckit.implement
|
||||||
```
|
```
|
||||||
|
|
||||||
## Key Principles
|
## Key Principles
|
||||||
@@ -117,6 +159,6 @@ implement specs/002-create-taskify/plan.md
|
|||||||
|
|
||||||
## Next Steps
|
## Next Steps
|
||||||
|
|
||||||
- Read the complete methodology for in-depth guidance
|
- Read the [complete methodology](../spec-driven.md) for in-depth guidance
|
||||||
- Check out more examples in the repository
|
- Check out [more examples](../templates) in the repository
|
||||||
- Explore the source code on GitHub
|
- Explore the [source code on GitHub](https://github.com/github/spec-kit)
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
href: installation.md
|
href: installation.md
|
||||||
- name: Quick Start
|
- name: Quick Start
|
||||||
href: quickstart.md
|
href: quickstart.md
|
||||||
|
- name: Upgrade
|
||||||
|
href: upgrade.md
|
||||||
|
|
||||||
# Development workflows
|
# Development workflows
|
||||||
- name: Development
|
- name: Development
|
||||||
|
|||||||
444
docs/upgrade.md
Normal file
444
docs/upgrade.md
Normal file
@@ -0,0 +1,444 @@
|
|||||||
|
# Upgrade Guide
|
||||||
|
|
||||||
|
> You have Spec Kit installed and want to upgrade to the latest version to get new features, bug fixes, or updated slash commands. This guide covers both upgrading the CLI tool and updating your project files.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick Reference
|
||||||
|
|
||||||
|
| What to Upgrade | Command | When to Use |
|
||||||
|
|----------------|---------|-------------|
|
||||||
|
| **CLI Tool Only** | `uv tool install specify-cli --force --from git+https://github.com/github/spec-kit.git` | Get latest CLI features without touching project files |
|
||||||
|
| **Project Files** | `specify init --here --force --ai <your-agent>` | Update slash commands, templates, and scripts in your project |
|
||||||
|
| **Both** | Run CLI upgrade, then project update | Recommended for major version updates |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part 1: Upgrade the CLI Tool
|
||||||
|
|
||||||
|
The CLI tool (`specify`) is separate from your project files. Upgrade it to get the latest features and bug fixes.
|
||||||
|
|
||||||
|
### If you installed with `uv tool install`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv tool install specify-cli --force --from git+https://github.com/github/spec-kit.git
|
||||||
|
```
|
||||||
|
|
||||||
|
### If you use one-shot `uvx` commands
|
||||||
|
|
||||||
|
No upgrade needed—`uvx` always fetches the latest version. Just run your commands as normal:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uvx --from git+https://github.com/github/spec-kit.git specify init --here --ai copilot
|
||||||
|
```
|
||||||
|
|
||||||
|
### Verify the upgrade
|
||||||
|
|
||||||
|
```bash
|
||||||
|
specify check
|
||||||
|
```
|
||||||
|
|
||||||
|
This shows installed tools and confirms the CLI is working.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part 2: Updating Project Files
|
||||||
|
|
||||||
|
When Spec Kit releases new features (like new slash commands or updated templates), you need to refresh your project's Spec Kit files.
|
||||||
|
|
||||||
|
### What gets updated?
|
||||||
|
|
||||||
|
Running `specify init --here --force` will update:
|
||||||
|
|
||||||
|
- ✅ **Slash command files** (`.claude/commands/`, `.github/prompts/`, etc.)
|
||||||
|
- ✅ **Script files** (`.specify/scripts/`)
|
||||||
|
- ✅ **Template files** (`.specify/templates/`)
|
||||||
|
- ✅ **Shared memory files** (`.specify/memory/`) - **⚠️ See warnings below**
|
||||||
|
|
||||||
|
### What stays safe?
|
||||||
|
|
||||||
|
These files are **never touched** by the upgrade—the template packages don't even contain them:
|
||||||
|
|
||||||
|
- ✅ **Your specifications** (`specs/001-my-feature/spec.md`, etc.) - **CONFIRMED SAFE**
|
||||||
|
- ✅ **Your implementation plans** (`specs/001-my-feature/plan.md`, `tasks.md`, etc.) - **CONFIRMED SAFE**
|
||||||
|
- ✅ **Your source code** - **CONFIRMED SAFE**
|
||||||
|
- ✅ **Your git history** - **CONFIRMED SAFE**
|
||||||
|
|
||||||
|
The `specs/` directory is completely excluded from template packages and will never be modified during upgrades.
|
||||||
|
|
||||||
|
### Update command
|
||||||
|
|
||||||
|
Run this inside your project directory:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
specify init --here --force --ai <your-agent>
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace `<your-agent>` with your AI assistant. Refer to this list of [Supported AI Agents](../README.md#-supported-ai-agents)
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
specify init --here --force --ai copilot
|
||||||
|
```
|
||||||
|
|
||||||
|
### Understanding the `--force` flag
|
||||||
|
|
||||||
|
Without `--force`, the CLI warns you and asks for confirmation:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Warning: Current directory is not empty (25 items)
|
||||||
|
Template files will be merged with existing content and may overwrite existing files
|
||||||
|
Proceed? [y/N]
|
||||||
|
```
|
||||||
|
|
||||||
|
With `--force`, it skips the confirmation and proceeds immediately.
|
||||||
|
|
||||||
|
**Important: Your `specs/` directory is always safe.** The `--force` flag only affects template files (commands, scripts, templates, memory). Your feature specifications, plans, and tasks in `specs/` are never included in upgrade packages and cannot be overwritten.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚠️ Important Warnings
|
||||||
|
|
||||||
|
### 1. Constitution file will be overwritten
|
||||||
|
|
||||||
|
**Known issue:** `specify init --here --force` currently overwrites `.specify/memory/constitution.md` with the default template, erasing any customizations you made.
|
||||||
|
|
||||||
|
**Workaround:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Back up your constitution before upgrading
|
||||||
|
cp .specify/memory/constitution.md .specify/memory/constitution-backup.md
|
||||||
|
|
||||||
|
# 2. Run the upgrade
|
||||||
|
specify init --here --force --ai copilot
|
||||||
|
|
||||||
|
# 3. Restore your customized constitution
|
||||||
|
mv .specify/memory/constitution-backup.md .specify/memory/constitution.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Or use git to restore it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# After upgrade, restore from git history
|
||||||
|
git restore .specify/memory/constitution.md
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Custom template modifications
|
||||||
|
|
||||||
|
If you customized any templates in `.specify/templates/`, the upgrade will overwrite them. Back them up first:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Back up custom templates
|
||||||
|
cp -r .specify/templates .specify/templates-backup
|
||||||
|
|
||||||
|
# After upgrade, merge your changes back manually
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Duplicate slash commands (IDE-based agents)
|
||||||
|
|
||||||
|
Some IDE-based agents (like Kilo Code, Windsurf) may show **duplicate slash commands** after upgrading—both old and new versions appear.
|
||||||
|
|
||||||
|
**Solution:** Manually delete the old command files from your agent's folder.
|
||||||
|
|
||||||
|
**Example for Kilo Code:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Navigate to the agent's commands folder
|
||||||
|
cd .kilocode/rules/
|
||||||
|
|
||||||
|
# List files and identify duplicates
|
||||||
|
ls -la
|
||||||
|
|
||||||
|
# Delete old versions (example filenames - yours may differ)
|
||||||
|
rm speckit.specify-old.md
|
||||||
|
rm speckit.plan-v1.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Restart your IDE to refresh the command list.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Common Scenarios
|
||||||
|
|
||||||
|
### Scenario 1: "I just want new slash commands"
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Upgrade CLI (if using persistent install)
|
||||||
|
uv tool install specify-cli --force --from git+https://github.com/github/spec-kit.git
|
||||||
|
|
||||||
|
# Update project files to get new commands
|
||||||
|
specify init --here --force --ai copilot
|
||||||
|
|
||||||
|
# Restore your constitution if customized
|
||||||
|
git restore .specify/memory/constitution.md
|
||||||
|
```
|
||||||
|
|
||||||
|
### Scenario 2: "I customized templates and constitution"
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Back up customizations
|
||||||
|
cp .specify/memory/constitution.md /tmp/constitution-backup.md
|
||||||
|
cp -r .specify/templates /tmp/templates-backup
|
||||||
|
|
||||||
|
# 2. Upgrade CLI
|
||||||
|
uv tool install specify-cli --force --from git+https://github.com/github/spec-kit.git
|
||||||
|
|
||||||
|
# 3. Update project
|
||||||
|
specify init --here --force --ai copilot
|
||||||
|
|
||||||
|
# 4. Restore customizations
|
||||||
|
mv /tmp/constitution-backup.md .specify/memory/constitution.md
|
||||||
|
# Manually merge template changes if needed
|
||||||
|
```
|
||||||
|
|
||||||
|
### Scenario 3: "I see duplicate slash commands in my IDE"
|
||||||
|
|
||||||
|
This happens with IDE-based agents (Kilo Code, Windsurf, Roo Code, etc.).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Find the agent folder (example: .kilocode/rules/)
|
||||||
|
cd .kilocode/rules/
|
||||||
|
|
||||||
|
# List all files
|
||||||
|
ls -la
|
||||||
|
|
||||||
|
# Delete old command files
|
||||||
|
rm speckit.old-command-name.md
|
||||||
|
|
||||||
|
# Restart your IDE
|
||||||
|
```
|
||||||
|
|
||||||
|
### Scenario 4: "I'm working on a project without Git"
|
||||||
|
|
||||||
|
If you initialized your project with `--no-git`, you can still upgrade:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Manually back up files you customized
|
||||||
|
cp .specify/memory/constitution.md /tmp/constitution-backup.md
|
||||||
|
|
||||||
|
# Run upgrade
|
||||||
|
specify init --here --force --ai copilot --no-git
|
||||||
|
|
||||||
|
# Restore customizations
|
||||||
|
mv /tmp/constitution-backup.md .specify/memory/constitution.md
|
||||||
|
```
|
||||||
|
|
||||||
|
The `--no-git` flag skips git initialization but doesn't affect file updates.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Using `--no-git` Flag
|
||||||
|
|
||||||
|
The `--no-git` flag tells Spec Kit to **skip git repository initialization**. This is useful when:
|
||||||
|
|
||||||
|
- You manage version control differently (Mercurial, SVN, etc.)
|
||||||
|
- Your project is part of a larger monorepo with existing git setup
|
||||||
|
- You're experimenting and don't want version control yet
|
||||||
|
|
||||||
|
**During initial setup:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
specify init my-project --ai copilot --no-git
|
||||||
|
```
|
||||||
|
|
||||||
|
**During upgrade:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
specify init --here --force --ai copilot --no-git
|
||||||
|
```
|
||||||
|
|
||||||
|
### What `--no-git` does NOT do
|
||||||
|
|
||||||
|
❌ Does NOT prevent file updates
|
||||||
|
❌ Does NOT skip slash command installation
|
||||||
|
❌ Does NOT affect template merging
|
||||||
|
|
||||||
|
It **only** skips running `git init` and creating the initial commit.
|
||||||
|
|
||||||
|
### Working without Git
|
||||||
|
|
||||||
|
If you use `--no-git`, you'll need to manage feature directories manually:
|
||||||
|
|
||||||
|
**Set the `SPECIFY_FEATURE` environment variable** before using planning commands:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Bash/Zsh
|
||||||
|
export SPECIFY_FEATURE="001-my-feature"
|
||||||
|
|
||||||
|
# PowerShell
|
||||||
|
$env:SPECIFY_FEATURE = "001-my-feature"
|
||||||
|
```
|
||||||
|
|
||||||
|
This tells Spec Kit which feature directory to use when creating specs, plans, and tasks.
|
||||||
|
|
||||||
|
**Why this matters:** Without git, Spec Kit can't detect your current branch name to determine the active feature. The environment variable provides that context manually.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### "Slash commands not showing up after upgrade"
|
||||||
|
|
||||||
|
**Cause:** Agent didn't reload the command files.
|
||||||
|
|
||||||
|
**Fix:**
|
||||||
|
|
||||||
|
1. **Restart your IDE/editor** completely (not just reload window)
|
||||||
|
2. **For CLI-based agents**, verify files exist:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ls -la .claude/commands/ # Claude Code
|
||||||
|
ls -la .gemini/commands/ # Gemini
|
||||||
|
ls -la .cursor/commands/ # Cursor
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Check agent-specific setup:**
|
||||||
|
- Codex requires `CODEX_HOME` environment variable
|
||||||
|
- Some agents need workspace restart or cache clearing
|
||||||
|
|
||||||
|
### "I lost my constitution customizations"
|
||||||
|
|
||||||
|
**Fix:** Restore from git or backup:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# If you committed before upgrading
|
||||||
|
git restore .specify/memory/constitution.md
|
||||||
|
|
||||||
|
# If you backed up manually
|
||||||
|
cp /tmp/constitution-backup.md .specify/memory/constitution.md
|
||||||
|
```
|
||||||
|
|
||||||
|
**Prevention:** Always commit or back up `constitution.md` before upgrading.
|
||||||
|
|
||||||
|
### "Warning: Current directory is not empty"
|
||||||
|
|
||||||
|
**Full warning message:**
|
||||||
|
|
||||||
|
```text
|
||||||
|
Warning: Current directory is not empty (25 items)
|
||||||
|
Template files will be merged with existing content and may overwrite existing files
|
||||||
|
Do you want to continue? [y/N]
|
||||||
|
```
|
||||||
|
|
||||||
|
**What this means:**
|
||||||
|
|
||||||
|
This warning appears when you run `specify init --here` (or `specify init .`) in a directory that already has files. It's telling you:
|
||||||
|
|
||||||
|
1. **The directory has existing content** - In the example, 25 files/folders
|
||||||
|
2. **Files will be merged** - New template files will be added alongside your existing files
|
||||||
|
3. **Some files may be overwritten** - If you already have Spec Kit files (`.claude/`, `.specify/`, etc.), they'll be replaced with the new versions
|
||||||
|
|
||||||
|
**What gets overwritten:**
|
||||||
|
|
||||||
|
Only Spec Kit infrastructure files:
|
||||||
|
|
||||||
|
- Agent command files (`.claude/commands/`, `.github/prompts/`, etc.)
|
||||||
|
- Scripts in `.specify/scripts/`
|
||||||
|
- Templates in `.specify/templates/`
|
||||||
|
- Memory files in `.specify/memory/` (including constitution)
|
||||||
|
|
||||||
|
**What stays untouched:**
|
||||||
|
|
||||||
|
- Your `specs/` directory (specifications, plans, tasks)
|
||||||
|
- Your source code files
|
||||||
|
- Your `.git/` directory and git history
|
||||||
|
- Any other files not part of Spec Kit templates
|
||||||
|
|
||||||
|
**How to respond:**
|
||||||
|
|
||||||
|
- **Type `y` and press Enter** - Proceed with the merge (recommended if upgrading)
|
||||||
|
- **Type `n` and press Enter** - Cancel the operation
|
||||||
|
- **Use `--force` flag** - Skip this confirmation entirely:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
specify init --here --force --ai copilot
|
||||||
|
```
|
||||||
|
|
||||||
|
**When you see this warning:**
|
||||||
|
|
||||||
|
- ✅ **Expected** when upgrading an existing Spec Kit project
|
||||||
|
- ✅ **Expected** when adding Spec Kit to an existing codebase
|
||||||
|
- ⚠️ **Unexpected** if you thought you were creating a new project in an empty directory
|
||||||
|
|
||||||
|
**Prevention tip:** Before upgrading, commit or back up your `.specify/memory/constitution.md` if you customized it.
|
||||||
|
|
||||||
|
### "CLI upgrade doesn't seem to work"
|
||||||
|
|
||||||
|
Verify the installation:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check installed tools
|
||||||
|
uv tool list
|
||||||
|
|
||||||
|
# Should show specify-cli
|
||||||
|
|
||||||
|
# Verify path
|
||||||
|
which specify
|
||||||
|
|
||||||
|
# Should point to the uv tool installation directory
|
||||||
|
```
|
||||||
|
|
||||||
|
If not found, reinstall:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv tool uninstall specify-cli
|
||||||
|
uv tool install specify-cli --from git+https://github.com/github/spec-kit.git
|
||||||
|
```
|
||||||
|
|
||||||
|
### "Do I need to run specify every time I open my project?"
|
||||||
|
|
||||||
|
**Short answer:** No, you only run `specify init` once per project (or when upgrading).
|
||||||
|
|
||||||
|
**Explanation:**
|
||||||
|
|
||||||
|
The `specify` CLI tool is used for:
|
||||||
|
|
||||||
|
- **Initial setup:** `specify init` to bootstrap Spec Kit in your project
|
||||||
|
- **Upgrades:** `specify init --here --force` to update templates and commands
|
||||||
|
- **Diagnostics:** `specify check` to verify tool installation
|
||||||
|
|
||||||
|
Once you've run `specify init`, the slash commands (like `/speckit.specify`, `/speckit.plan`, etc.) are **permanently installed** in your project's agent folder (`.claude/`, `.github/prompts/`, etc.). Your AI assistant reads these command files directly—no need to run `specify` again.
|
||||||
|
|
||||||
|
**If your agent isn't recognizing slash commands:**
|
||||||
|
|
||||||
|
1. **Verify command files exist:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# For GitHub Copilot
|
||||||
|
ls -la .github/prompts/
|
||||||
|
|
||||||
|
# For Claude
|
||||||
|
ls -la .claude/commands/
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Restart your IDE/editor completely** (not just reload window)
|
||||||
|
|
||||||
|
3. **Check you're in the correct directory** where you ran `specify init`
|
||||||
|
|
||||||
|
4. **For some agents**, you may need to reload the workspace or clear cache
|
||||||
|
|
||||||
|
**Related issue:** If Copilot can't open local files or uses PowerShell commands unexpectedly, this is typically an IDE context issue, not related to `specify`. Try:
|
||||||
|
|
||||||
|
- Restarting VS Code
|
||||||
|
- Checking file permissions
|
||||||
|
- Ensuring the workspace folder is properly opened
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Version Compatibility
|
||||||
|
|
||||||
|
Spec Kit follows semantic versioning for major releases. The CLI and project files are designed to be compatible within the same major version.
|
||||||
|
|
||||||
|
**Best practice:** Keep both CLI and project files in sync by upgrading both together during major version changes.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
After upgrading:
|
||||||
|
|
||||||
|
- **Test new slash commands:** Run `/speckit.constitution` or another command to verify everything works
|
||||||
|
- **Review release notes:** Check [GitHub Releases](https://github.com/github/spec-kit/releases) for new features and breaking changes
|
||||||
|
- **Update workflows:** If new commands were added, update your team's development workflows
|
||||||
|
- **Check documentation:** Visit [github.io/spec-kit](https://github.github.io/spec-kit/) for updated guides
|
||||||
714
extensions/EXTENSION-API-REFERENCE.md
Normal file
714
extensions/EXTENSION-API-REFERENCE.md
Normal file
@@ -0,0 +1,714 @@
|
|||||||
|
# Extension API Reference
|
||||||
|
|
||||||
|
Technical reference for Spec Kit extension system APIs and manifest schema.
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
1. [Extension Manifest](#extension-manifest)
|
||||||
|
2. [Python API](#python-api)
|
||||||
|
3. [Command File Format](#command-file-format)
|
||||||
|
4. [Configuration Schema](#configuration-schema)
|
||||||
|
5. [Hook System](#hook-system)
|
||||||
|
6. [CLI Commands](#cli-commands)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Extension Manifest
|
||||||
|
|
||||||
|
### Schema Version 1.0
|
||||||
|
|
||||||
|
File: `extension.yml`
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
schema_version: "1.0" # Required
|
||||||
|
|
||||||
|
extension:
|
||||||
|
id: string # Required, pattern: ^[a-z0-9-]+$
|
||||||
|
name: string # Required, human-readable name
|
||||||
|
version: string # Required, semantic version (X.Y.Z)
|
||||||
|
description: string # Required, brief description (<200 chars)
|
||||||
|
author: string # Required
|
||||||
|
repository: string # Required, valid URL
|
||||||
|
license: string # Required (e.g., "MIT", "Apache-2.0")
|
||||||
|
homepage: string # Optional, valid URL
|
||||||
|
|
||||||
|
requires:
|
||||||
|
speckit_version: string # Required, version specifier (>=X.Y.Z)
|
||||||
|
tools: # Optional, array of tool requirements
|
||||||
|
- name: string # Tool name
|
||||||
|
version: string # Optional, version specifier
|
||||||
|
required: boolean # Optional, default: false
|
||||||
|
|
||||||
|
provides:
|
||||||
|
commands: # Required, at least one command
|
||||||
|
- name: string # Required, pattern: ^speckit\.[a-z0-9-]+\.[a-z0-9-]+$
|
||||||
|
file: string # Required, relative path to command file
|
||||||
|
description: string # Required
|
||||||
|
aliases: [string] # Optional, array of alternate names
|
||||||
|
|
||||||
|
config: # Optional, array of config files
|
||||||
|
- name: string # Config file name
|
||||||
|
template: string # Template file path
|
||||||
|
description: string
|
||||||
|
required: boolean # Default: false
|
||||||
|
|
||||||
|
hooks: # Optional, event hooks
|
||||||
|
event_name: # e.g., "after_tasks", "after_implement"
|
||||||
|
command: string # Command to execute
|
||||||
|
optional: boolean # Default: true
|
||||||
|
prompt: string # Prompt text for optional hooks
|
||||||
|
description: string # Hook description
|
||||||
|
condition: string # Optional, condition expression
|
||||||
|
|
||||||
|
tags: # Optional, array of tags (2-10 recommended)
|
||||||
|
- string
|
||||||
|
|
||||||
|
defaults: # Optional, default configuration values
|
||||||
|
key: value # Any YAML structure
|
||||||
|
```
|
||||||
|
|
||||||
|
### Field Specifications
|
||||||
|
|
||||||
|
#### `extension.id`
|
||||||
|
|
||||||
|
- **Type**: string
|
||||||
|
- **Pattern**: `^[a-z0-9-]+$`
|
||||||
|
- **Description**: Unique extension identifier
|
||||||
|
- **Examples**: `jira`, `linear`, `azure-devops`
|
||||||
|
- **Invalid**: `Jira`, `my_extension`, `extension.id`
|
||||||
|
|
||||||
|
#### `extension.version`
|
||||||
|
|
||||||
|
- **Type**: string
|
||||||
|
- **Format**: Semantic versioning (X.Y.Z)
|
||||||
|
- **Description**: Extension version
|
||||||
|
- **Examples**: `1.0.0`, `0.9.5`, `2.1.3`
|
||||||
|
- **Invalid**: `v1.0`, `1.0`, `1.0.0-beta`
|
||||||
|
|
||||||
|
#### `requires.speckit_version`
|
||||||
|
|
||||||
|
- **Type**: string
|
||||||
|
- **Format**: Version specifier
|
||||||
|
- **Description**: Required spec-kit version range
|
||||||
|
- **Examples**:
|
||||||
|
- `>=0.1.0` - Any version 0.1.0 or higher
|
||||||
|
- `>=0.1.0,<2.0.0` - Version 0.1.x or 1.x
|
||||||
|
- `==0.1.0` - Exactly 0.1.0
|
||||||
|
- **Invalid**: `0.1.0`, `>= 0.1.0` (space), `latest`
|
||||||
|
|
||||||
|
#### `provides.commands[].name`
|
||||||
|
|
||||||
|
- **Type**: string
|
||||||
|
- **Pattern**: `^speckit\.[a-z0-9-]+\.[a-z0-9-]+$`
|
||||||
|
- **Description**: Namespaced command name
|
||||||
|
- **Format**: `speckit.{extension-id}.{command-name}`
|
||||||
|
- **Examples**: `speckit.jira.specstoissues`, `speckit.linear.sync`
|
||||||
|
- **Invalid**: `jira.specstoissues`, `speckit.command`, `speckit.jira.CreateIssues`
|
||||||
|
|
||||||
|
#### `hooks`
|
||||||
|
|
||||||
|
- **Type**: object
|
||||||
|
- **Keys**: Event names (e.g., `after_tasks`, `after_implement`, `before_commit`)
|
||||||
|
- **Description**: Hooks that execute at lifecycle events
|
||||||
|
- **Events**: Defined by core spec-kit commands
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Python API
|
||||||
|
|
||||||
|
### ExtensionManifest
|
||||||
|
|
||||||
|
**Module**: `specify_cli.extensions`
|
||||||
|
|
||||||
|
```python
|
||||||
|
from specify_cli.extensions import ExtensionManifest
|
||||||
|
|
||||||
|
manifest = ExtensionManifest(Path("extension.yml"))
|
||||||
|
```
|
||||||
|
|
||||||
|
**Properties**:
|
||||||
|
|
||||||
|
```python
|
||||||
|
manifest.id # str: Extension ID
|
||||||
|
manifest.name # str: Extension name
|
||||||
|
manifest.version # str: Version
|
||||||
|
manifest.description # str: Description
|
||||||
|
manifest.requires_speckit_version # str: Required spec-kit version
|
||||||
|
manifest.commands # List[Dict]: Command definitions
|
||||||
|
manifest.hooks # Dict: Hook definitions
|
||||||
|
```
|
||||||
|
|
||||||
|
**Methods**:
|
||||||
|
|
||||||
|
```python
|
||||||
|
manifest.get_hash() # str: SHA256 hash of manifest file
|
||||||
|
```
|
||||||
|
|
||||||
|
**Exceptions**:
|
||||||
|
|
||||||
|
```python
|
||||||
|
ValidationError # Invalid manifest structure
|
||||||
|
CompatibilityError # Incompatible with current spec-kit version
|
||||||
|
```
|
||||||
|
|
||||||
|
### ExtensionRegistry
|
||||||
|
|
||||||
|
**Module**: `specify_cli.extensions`
|
||||||
|
|
||||||
|
```python
|
||||||
|
from specify_cli.extensions import ExtensionRegistry
|
||||||
|
|
||||||
|
registry = ExtensionRegistry(extensions_dir)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Methods**:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Add extension to registry
|
||||||
|
registry.add(extension_id: str, metadata: dict)
|
||||||
|
|
||||||
|
# Remove extension from registry
|
||||||
|
registry.remove(extension_id: str)
|
||||||
|
|
||||||
|
# Get extension metadata
|
||||||
|
metadata = registry.get(extension_id: str) # Optional[dict]
|
||||||
|
|
||||||
|
# List all extensions
|
||||||
|
extensions = registry.list() # Dict[str, dict]
|
||||||
|
|
||||||
|
# Check if installed
|
||||||
|
is_installed = registry.is_installed(extension_id: str) # bool
|
||||||
|
```
|
||||||
|
|
||||||
|
**Registry Format**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"schema_version": "1.0",
|
||||||
|
"extensions": {
|
||||||
|
"jira": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"source": "catalog",
|
||||||
|
"manifest_hash": "sha256...",
|
||||||
|
"enabled": true,
|
||||||
|
"registered_commands": ["speckit.jira.specstoissues", ...],
|
||||||
|
"installed_at": "2026-01-28T..."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### ExtensionManager
|
||||||
|
|
||||||
|
**Module**: `specify_cli.extensions`
|
||||||
|
|
||||||
|
```python
|
||||||
|
from specify_cli.extensions import ExtensionManager
|
||||||
|
|
||||||
|
manager = ExtensionManager(project_root)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Methods**:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Install from directory
|
||||||
|
manifest = manager.install_from_directory(
|
||||||
|
source_dir: Path,
|
||||||
|
speckit_version: str,
|
||||||
|
register_commands: bool = True
|
||||||
|
) # Returns: ExtensionManifest
|
||||||
|
|
||||||
|
# Install from ZIP
|
||||||
|
manifest = manager.install_from_zip(
|
||||||
|
zip_path: Path,
|
||||||
|
speckit_version: str
|
||||||
|
) # Returns: ExtensionManifest
|
||||||
|
|
||||||
|
# Remove extension
|
||||||
|
success = manager.remove(
|
||||||
|
extension_id: str,
|
||||||
|
keep_config: bool = False
|
||||||
|
) # Returns: bool
|
||||||
|
|
||||||
|
# List installed extensions
|
||||||
|
extensions = manager.list_installed() # List[Dict]
|
||||||
|
|
||||||
|
# Get extension manifest
|
||||||
|
manifest = manager.get_extension(extension_id: str) # Optional[ExtensionManifest]
|
||||||
|
|
||||||
|
# Check compatibility
|
||||||
|
manager.check_compatibility(
|
||||||
|
manifest: ExtensionManifest,
|
||||||
|
speckit_version: str
|
||||||
|
) # Raises: CompatibilityError if incompatible
|
||||||
|
```
|
||||||
|
|
||||||
|
### ExtensionCatalog
|
||||||
|
|
||||||
|
**Module**: `specify_cli.extensions`
|
||||||
|
|
||||||
|
```python
|
||||||
|
from specify_cli.extensions import ExtensionCatalog
|
||||||
|
|
||||||
|
catalog = ExtensionCatalog(project_root)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Methods**:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Fetch catalog
|
||||||
|
catalog_data = catalog.fetch_catalog(force_refresh: bool = False) # Dict
|
||||||
|
|
||||||
|
# Search extensions
|
||||||
|
results = catalog.search(
|
||||||
|
query: Optional[str] = None,
|
||||||
|
tag: Optional[str] = None,
|
||||||
|
author: Optional[str] = None,
|
||||||
|
verified_only: bool = False
|
||||||
|
) # Returns: List[Dict]
|
||||||
|
|
||||||
|
# Get extension info
|
||||||
|
ext_info = catalog.get_extension_info(extension_id: str) # Optional[Dict]
|
||||||
|
|
||||||
|
# Check cache validity
|
||||||
|
is_valid = catalog.is_cache_valid() # bool
|
||||||
|
|
||||||
|
# Clear cache
|
||||||
|
catalog.clear_cache()
|
||||||
|
```
|
||||||
|
|
||||||
|
### HookExecutor
|
||||||
|
|
||||||
|
**Module**: `specify_cli.extensions`
|
||||||
|
|
||||||
|
```python
|
||||||
|
from specify_cli.extensions import HookExecutor
|
||||||
|
|
||||||
|
hook_executor = HookExecutor(project_root)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Methods**:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Get project config
|
||||||
|
config = hook_executor.get_project_config() # Dict
|
||||||
|
|
||||||
|
# Save project config
|
||||||
|
hook_executor.save_project_config(config: Dict)
|
||||||
|
|
||||||
|
# Register hooks
|
||||||
|
hook_executor.register_hooks(manifest: ExtensionManifest)
|
||||||
|
|
||||||
|
# Unregister hooks
|
||||||
|
hook_executor.unregister_hooks(extension_id: str)
|
||||||
|
|
||||||
|
# Get hooks for event
|
||||||
|
hooks = hook_executor.get_hooks_for_event(event_name: str) # List[Dict]
|
||||||
|
|
||||||
|
# Check if hook should execute
|
||||||
|
should_run = hook_executor.should_execute_hook(hook: Dict) # bool
|
||||||
|
|
||||||
|
# Format hook message
|
||||||
|
message = hook_executor.format_hook_message(
|
||||||
|
event_name: str,
|
||||||
|
hooks: List[Dict]
|
||||||
|
) # str
|
||||||
|
```
|
||||||
|
|
||||||
|
### CommandRegistrar
|
||||||
|
|
||||||
|
**Module**: `specify_cli.extensions`
|
||||||
|
|
||||||
|
```python
|
||||||
|
from specify_cli.extensions import CommandRegistrar
|
||||||
|
|
||||||
|
registrar = CommandRegistrar()
|
||||||
|
```
|
||||||
|
|
||||||
|
**Methods**:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Register commands for Claude Code
|
||||||
|
registered = registrar.register_commands_for_claude(
|
||||||
|
manifest: ExtensionManifest,
|
||||||
|
extension_dir: Path,
|
||||||
|
project_root: Path
|
||||||
|
) # Returns: List[str] (command names)
|
||||||
|
|
||||||
|
# Parse frontmatter
|
||||||
|
frontmatter, body = registrar.parse_frontmatter(content: str)
|
||||||
|
|
||||||
|
# Render frontmatter
|
||||||
|
yaml_text = registrar.render_frontmatter(frontmatter: Dict) # str
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Command File Format
|
||||||
|
|
||||||
|
### Universal Command Format
|
||||||
|
|
||||||
|
**File**: `commands/{command-name}.md`
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
description: "Command description"
|
||||||
|
tools:
|
||||||
|
- 'mcp-server/tool_name'
|
||||||
|
- 'other-mcp-server/other_tool'
|
||||||
|
---
|
||||||
|
|
||||||
|
# Command Title
|
||||||
|
|
||||||
|
Command documentation in Markdown.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
1. Requirement 1
|
||||||
|
2. Requirement 2
|
||||||
|
|
||||||
|
## User Input
|
||||||
|
|
||||||
|
$ARGUMENTS
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
### Step 1: Description
|
||||||
|
|
||||||
|
Instruction text...
|
||||||
|
|
||||||
|
\`\`\`bash
|
||||||
|
# Shell commands
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### Step 2: Another Step
|
||||||
|
|
||||||
|
More instructions...
|
||||||
|
|
||||||
|
## Configuration Reference
|
||||||
|
|
||||||
|
Information about configuration options.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
Additional notes and tips.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Frontmatter Fields
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
description: string # Required, brief command description
|
||||||
|
tools: [string] # Optional, MCP tools required
|
||||||
|
```
|
||||||
|
|
||||||
|
### Special Variables
|
||||||
|
|
||||||
|
- `$ARGUMENTS` - Placeholder for user-provided arguments
|
||||||
|
- Extension context automatically injected:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
<!-- Extension: {extension-id} -->
|
||||||
|
<!-- Config: .specify/extensions/{extension-id}/ -->
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Configuration Schema
|
||||||
|
|
||||||
|
### Extension Config File
|
||||||
|
|
||||||
|
**File**: `.specify/extensions/{extension-id}/{extension-id}-config.yml`
|
||||||
|
|
||||||
|
Extensions define their own config schema. Common patterns:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# Connection settings
|
||||||
|
connection:
|
||||||
|
url: string
|
||||||
|
api_key: string
|
||||||
|
|
||||||
|
# Project settings
|
||||||
|
project:
|
||||||
|
key: string
|
||||||
|
workspace: string
|
||||||
|
|
||||||
|
# Feature flags
|
||||||
|
features:
|
||||||
|
enabled: boolean
|
||||||
|
auto_sync: boolean
|
||||||
|
|
||||||
|
# Defaults
|
||||||
|
defaults:
|
||||||
|
labels: [string]
|
||||||
|
assignee: string
|
||||||
|
|
||||||
|
# Custom fields
|
||||||
|
field_mappings:
|
||||||
|
internal_name: "external_field_id"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Config Layers
|
||||||
|
|
||||||
|
1. **Extension Defaults** (from `extension.yml` `defaults` section)
|
||||||
|
2. **Project Config** (`{extension-id}-config.yml`)
|
||||||
|
3. **Local Override** (`{extension-id}-config.local.yml`, gitignored)
|
||||||
|
4. **Environment Variables** (`SPECKIT_{EXTENSION}_*`)
|
||||||
|
|
||||||
|
### Environment Variable Pattern
|
||||||
|
|
||||||
|
Format: `SPECKIT_{EXTENSION}_{KEY}`
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
- `SPECKIT_JIRA_PROJECT_KEY`
|
||||||
|
- `SPECKIT_LINEAR_API_KEY`
|
||||||
|
- `SPECKIT_GITHUB_TOKEN`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Hook System
|
||||||
|
|
||||||
|
### Hook Definition
|
||||||
|
|
||||||
|
**In extension.yml**:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
hooks:
|
||||||
|
after_tasks:
|
||||||
|
command: "speckit.jira.specstoissues"
|
||||||
|
optional: true
|
||||||
|
prompt: "Create Jira issues from tasks?"
|
||||||
|
description: "Automatically create Jira hierarchy"
|
||||||
|
condition: null
|
||||||
|
```
|
||||||
|
|
||||||
|
### Hook Events
|
||||||
|
|
||||||
|
Standard events (defined by core):
|
||||||
|
|
||||||
|
- `after_tasks` - After task generation
|
||||||
|
- `after_implement` - After implementation
|
||||||
|
- `before_commit` - Before git commit
|
||||||
|
- `after_commit` - After git commit
|
||||||
|
|
||||||
|
### Hook Configuration
|
||||||
|
|
||||||
|
**In `.specify/extensions.yml`**:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
hooks:
|
||||||
|
after_tasks:
|
||||||
|
- extension: jira
|
||||||
|
command: speckit.jira.specstoissues
|
||||||
|
enabled: true
|
||||||
|
optional: true
|
||||||
|
prompt: "Create Jira issues from tasks?"
|
||||||
|
description: "..."
|
||||||
|
condition: null
|
||||||
|
```
|
||||||
|
|
||||||
|
### Hook Message Format
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Extension Hooks
|
||||||
|
|
||||||
|
**Optional Hook**: {extension}
|
||||||
|
Command: `/{command}`
|
||||||
|
Description: {description}
|
||||||
|
|
||||||
|
Prompt: {prompt}
|
||||||
|
To execute: `/{command}`
|
||||||
|
```
|
||||||
|
|
||||||
|
Or for mandatory hooks:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
**Automatic Hook**: {extension}
|
||||||
|
Executing: `/{command}`
|
||||||
|
EXECUTE_COMMAND: {command}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CLI Commands
|
||||||
|
|
||||||
|
### extension list
|
||||||
|
|
||||||
|
**Usage**: `specify extension list [OPTIONS]`
|
||||||
|
|
||||||
|
**Options**:
|
||||||
|
|
||||||
|
- `--available` - Show available extensions from catalog
|
||||||
|
- `--all` - Show both installed and available
|
||||||
|
|
||||||
|
**Output**: List of installed extensions with metadata
|
||||||
|
|
||||||
|
### extension add
|
||||||
|
|
||||||
|
**Usage**: `specify extension add EXTENSION [OPTIONS]`
|
||||||
|
|
||||||
|
**Options**:
|
||||||
|
|
||||||
|
- `--from URL` - Install from custom URL
|
||||||
|
- `--dev PATH` - Install from local directory
|
||||||
|
- `--version VERSION` - Install specific version
|
||||||
|
- `--no-register` - Skip command registration
|
||||||
|
|
||||||
|
**Arguments**:
|
||||||
|
|
||||||
|
- `EXTENSION` - Extension name or URL
|
||||||
|
|
||||||
|
### extension remove
|
||||||
|
|
||||||
|
**Usage**: `specify extension remove EXTENSION [OPTIONS]`
|
||||||
|
|
||||||
|
**Options**:
|
||||||
|
|
||||||
|
- `--keep-config` - Preserve config files
|
||||||
|
- `--force` - Skip confirmation
|
||||||
|
|
||||||
|
**Arguments**:
|
||||||
|
|
||||||
|
- `EXTENSION` - Extension ID
|
||||||
|
|
||||||
|
### extension search
|
||||||
|
|
||||||
|
**Usage**: `specify extension search [QUERY] [OPTIONS]`
|
||||||
|
|
||||||
|
**Options**:
|
||||||
|
|
||||||
|
- `--tag TAG` - Filter by tag
|
||||||
|
- `--author AUTHOR` - Filter by author
|
||||||
|
- `--verified` - Show only verified extensions
|
||||||
|
|
||||||
|
**Arguments**:
|
||||||
|
|
||||||
|
- `QUERY` - Optional search query
|
||||||
|
|
||||||
|
### extension info
|
||||||
|
|
||||||
|
**Usage**: `specify extension info EXTENSION`
|
||||||
|
|
||||||
|
**Arguments**:
|
||||||
|
|
||||||
|
- `EXTENSION` - Extension ID
|
||||||
|
|
||||||
|
### extension update
|
||||||
|
|
||||||
|
**Usage**: `specify extension update [EXTENSION]`
|
||||||
|
|
||||||
|
**Arguments**:
|
||||||
|
|
||||||
|
- `EXTENSION` - Optional, extension ID (default: all)
|
||||||
|
|
||||||
|
### extension enable
|
||||||
|
|
||||||
|
**Usage**: `specify extension enable EXTENSION`
|
||||||
|
|
||||||
|
**Arguments**:
|
||||||
|
|
||||||
|
- `EXTENSION` - Extension ID
|
||||||
|
|
||||||
|
### extension disable
|
||||||
|
|
||||||
|
**Usage**: `specify extension disable EXTENSION`
|
||||||
|
|
||||||
|
**Arguments**:
|
||||||
|
|
||||||
|
- `EXTENSION` - Extension ID
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exceptions
|
||||||
|
|
||||||
|
### ValidationError
|
||||||
|
|
||||||
|
Raised when extension manifest validation fails.
|
||||||
|
|
||||||
|
```python
|
||||||
|
from specify_cli.extensions import ValidationError
|
||||||
|
|
||||||
|
try:
|
||||||
|
manifest = ExtensionManifest(path)
|
||||||
|
except ValidationError as e:
|
||||||
|
print(f"Invalid manifest: {e}")
|
||||||
|
```
|
||||||
|
|
||||||
|
### CompatibilityError
|
||||||
|
|
||||||
|
Raised when extension is incompatible with current spec-kit version.
|
||||||
|
|
||||||
|
```python
|
||||||
|
from specify_cli.extensions import CompatibilityError
|
||||||
|
|
||||||
|
try:
|
||||||
|
manager.check_compatibility(manifest, "0.1.0")
|
||||||
|
except CompatibilityError as e:
|
||||||
|
print(f"Incompatible: {e}")
|
||||||
|
```
|
||||||
|
|
||||||
|
### ExtensionError
|
||||||
|
|
||||||
|
Base exception for all extension-related errors.
|
||||||
|
|
||||||
|
```python
|
||||||
|
from specify_cli.extensions import ExtensionError
|
||||||
|
|
||||||
|
try:
|
||||||
|
manager.install_from_directory(path, "0.1.0")
|
||||||
|
except ExtensionError as e:
|
||||||
|
print(f"Extension error: {e}")
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Version Functions
|
||||||
|
|
||||||
|
### version_satisfies
|
||||||
|
|
||||||
|
Check if a version satisfies a specifier.
|
||||||
|
|
||||||
|
```python
|
||||||
|
from specify_cli.extensions import version_satisfies
|
||||||
|
|
||||||
|
# True if 1.2.3 satisfies >=1.0.0,<2.0.0
|
||||||
|
satisfied = version_satisfies("1.2.3", ">=1.0.0,<2.0.0") # bool
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File System Layout
|
||||||
|
|
||||||
|
```text
|
||||||
|
.specify/
|
||||||
|
├── extensions/
|
||||||
|
│ ├── .registry # Extension registry (JSON)
|
||||||
|
│ ├── .cache/ # Catalog cache
|
||||||
|
│ │ ├── catalog.json
|
||||||
|
│ │ └── catalog-metadata.json
|
||||||
|
│ ├── .backup/ # Config backups
|
||||||
|
│ │ └── {ext}-{config}.yml
|
||||||
|
│ ├── {extension-id}/ # Extension directory
|
||||||
|
│ │ ├── extension.yml # Manifest
|
||||||
|
│ │ ├── {ext}-config.yml # User config
|
||||||
|
│ │ ├── {ext}-config.local.yml # Local overrides (gitignored)
|
||||||
|
│ │ ├── {ext}-config.template.yml # Template
|
||||||
|
│ │ ├── commands/ # Command files
|
||||||
|
│ │ │ └── *.md
|
||||||
|
│ │ ├── scripts/ # Helper scripts
|
||||||
|
│ │ │ └── *.sh
|
||||||
|
│ │ ├── docs/ # Documentation
|
||||||
|
│ │ └── README.md
|
||||||
|
│ └── extensions.yml # Project extension config
|
||||||
|
└── scripts/ # (existing spec-kit)
|
||||||
|
|
||||||
|
.claude/
|
||||||
|
└── commands/
|
||||||
|
└── speckit.{ext}.{cmd}.md # Registered commands
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Last Updated: 2026-01-28*
|
||||||
|
*API Version: 1.0*
|
||||||
|
*Spec Kit Version: 0.1.0*
|
||||||
649
extensions/EXTENSION-DEVELOPMENT-GUIDE.md
Normal file
649
extensions/EXTENSION-DEVELOPMENT-GUIDE.md
Normal file
@@ -0,0 +1,649 @@
|
|||||||
|
# Extension Development Guide
|
||||||
|
|
||||||
|
A guide for creating Spec Kit extensions.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### 1. Create Extension Directory
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir my-extension
|
||||||
|
cd my-extension
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Create `extension.yml` Manifest
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
schema_version: "1.0"
|
||||||
|
|
||||||
|
extension:
|
||||||
|
id: "my-ext" # Lowercase, alphanumeric + hyphens only
|
||||||
|
name: "My Extension"
|
||||||
|
version: "1.0.0" # Semantic versioning
|
||||||
|
description: "My custom extension"
|
||||||
|
author: "Your Name"
|
||||||
|
repository: "https://github.com/you/spec-kit-my-ext"
|
||||||
|
license: "MIT"
|
||||||
|
|
||||||
|
requires:
|
||||||
|
speckit_version: ">=0.1.0" # Minimum spec-kit version
|
||||||
|
tools: # Optional: External tools required
|
||||||
|
- name: "my-tool"
|
||||||
|
required: true
|
||||||
|
version: ">=1.0.0"
|
||||||
|
commands: # Optional: Core commands needed
|
||||||
|
- "speckit.tasks"
|
||||||
|
|
||||||
|
provides:
|
||||||
|
commands:
|
||||||
|
- name: "speckit.my-ext.hello" # Must follow pattern: speckit.{ext-id}.{cmd}
|
||||||
|
file: "commands/hello.md"
|
||||||
|
description: "Say hello"
|
||||||
|
aliases: ["speckit.hello"] # Optional aliases
|
||||||
|
|
||||||
|
config: # Optional: Config files
|
||||||
|
- name: "my-ext-config.yml"
|
||||||
|
template: "my-ext-config.template.yml"
|
||||||
|
description: "Extension configuration"
|
||||||
|
required: false
|
||||||
|
|
||||||
|
hooks: # Optional: Integration hooks
|
||||||
|
after_tasks:
|
||||||
|
command: "speckit.my-ext.hello"
|
||||||
|
optional: true
|
||||||
|
prompt: "Run hello command?"
|
||||||
|
|
||||||
|
tags: # Optional: For catalog search
|
||||||
|
- "example"
|
||||||
|
- "utility"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Create Commands Directory
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir commands
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Create Command File
|
||||||
|
|
||||||
|
**File**: `commands/hello.md`
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
description: "Say hello command"
|
||||||
|
tools: # Optional: AI tools this command uses
|
||||||
|
- 'some-tool/function'
|
||||||
|
scripts: # Optional: Helper scripts
|
||||||
|
sh: ../../scripts/bash/helper.sh
|
||||||
|
ps: ../../scripts/powershell/helper.ps1
|
||||||
|
---
|
||||||
|
|
||||||
|
# Hello Command
|
||||||
|
|
||||||
|
This command says hello!
|
||||||
|
|
||||||
|
## User Input
|
||||||
|
|
||||||
|
$ARGUMENTS
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
1. Greet the user
|
||||||
|
2. Show extension is working
|
||||||
|
|
||||||
|
```bash
|
||||||
|
echo "Hello from my extension!"
|
||||||
|
echo "Arguments: $ARGUMENTS"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Extension Configuration
|
||||||
|
|
||||||
|
Load extension config from `.specify/extensions/my-ext/my-ext-config.yml`.
|
||||||
|
|
||||||
|
### 5. Test Locally
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /path/to/spec-kit-project
|
||||||
|
specify extension add --dev /path/to/my-extension
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Verify Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
specify extension list
|
||||||
|
|
||||||
|
# Should show:
|
||||||
|
# ✓ My Extension (v1.0.0)
|
||||||
|
# My custom extension
|
||||||
|
# Commands: 1 | Hooks: 1 | Status: Enabled
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. Test Command
|
||||||
|
|
||||||
|
If using Claude:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
claude
|
||||||
|
> /speckit.my-ext.hello world
|
||||||
|
```
|
||||||
|
|
||||||
|
The command will be available in `.claude/commands/speckit.my-ext.hello.md`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Manifest Schema Reference
|
||||||
|
|
||||||
|
### Required Fields
|
||||||
|
|
||||||
|
#### `schema_version`
|
||||||
|
|
||||||
|
Extension manifest schema version. Currently: `"1.0"`
|
||||||
|
|
||||||
|
#### `extension`
|
||||||
|
|
||||||
|
Extension metadata block.
|
||||||
|
|
||||||
|
**Required sub-fields**:
|
||||||
|
|
||||||
|
- `id`: Extension identifier (lowercase, alphanumeric, hyphens)
|
||||||
|
- `name`: Human-readable name
|
||||||
|
- `version`: Semantic version (e.g., "1.0.0")
|
||||||
|
- `description`: Short description
|
||||||
|
|
||||||
|
**Optional sub-fields**:
|
||||||
|
|
||||||
|
- `author`: Extension author
|
||||||
|
- `repository`: Source code URL
|
||||||
|
- `license`: SPDX license identifier
|
||||||
|
- `homepage`: Extension homepage URL
|
||||||
|
|
||||||
|
#### `requires`
|
||||||
|
|
||||||
|
Compatibility requirements.
|
||||||
|
|
||||||
|
**Required sub-fields**:
|
||||||
|
|
||||||
|
- `speckit_version`: Semantic version specifier (e.g., ">=0.1.0,<2.0.0")
|
||||||
|
|
||||||
|
**Optional sub-fields**:
|
||||||
|
|
||||||
|
- `tools`: External tools required (array of tool objects)
|
||||||
|
- `commands`: Core spec-kit commands needed (array of command names)
|
||||||
|
- `scripts`: Core scripts required (array of script names)
|
||||||
|
|
||||||
|
#### `provides`
|
||||||
|
|
||||||
|
What the extension provides.
|
||||||
|
|
||||||
|
**Required sub-fields**:
|
||||||
|
|
||||||
|
- `commands`: Array of command objects (must have at least one)
|
||||||
|
|
||||||
|
**Command object**:
|
||||||
|
|
||||||
|
- `name`: Command name (must match `speckit.{ext-id}.{command}`)
|
||||||
|
- `file`: Path to command file (relative to extension root)
|
||||||
|
- `description`: Command description (optional)
|
||||||
|
- `aliases`: Alternative command names (optional, array)
|
||||||
|
|
||||||
|
### Optional Fields
|
||||||
|
|
||||||
|
#### `hooks`
|
||||||
|
|
||||||
|
Integration hooks for automatic execution.
|
||||||
|
|
||||||
|
Available hook points:
|
||||||
|
|
||||||
|
- `after_tasks`: After `/speckit.tasks` completes
|
||||||
|
- `after_implement`: After `/speckit.implement` completes (future)
|
||||||
|
|
||||||
|
Hook object:
|
||||||
|
|
||||||
|
- `command`: Command to execute (must be in `provides.commands`)
|
||||||
|
- `optional`: If true, prompt user before executing
|
||||||
|
- `prompt`: Prompt text for optional hooks
|
||||||
|
- `description`: Hook description
|
||||||
|
- `condition`: Execution condition (future)
|
||||||
|
|
||||||
|
#### `tags`
|
||||||
|
|
||||||
|
Array of tags for catalog discovery.
|
||||||
|
|
||||||
|
#### `defaults`
|
||||||
|
|
||||||
|
Default extension configuration values.
|
||||||
|
|
||||||
|
#### `config_schema`
|
||||||
|
|
||||||
|
JSON Schema for validating extension configuration.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Command File Format
|
||||||
|
|
||||||
|
### Frontmatter (YAML)
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
description: "Command description" # Required
|
||||||
|
tools: # Optional
|
||||||
|
- 'tool-name/function'
|
||||||
|
scripts: # Optional
|
||||||
|
sh: ../../scripts/bash/helper.sh
|
||||||
|
ps: ../../scripts/powershell/helper.ps1
|
||||||
|
---
|
||||||
|
```
|
||||||
|
|
||||||
|
### Body (Markdown)
|
||||||
|
|
||||||
|
Use standard Markdown with special placeholders:
|
||||||
|
|
||||||
|
- `$ARGUMENTS`: User-provided arguments
|
||||||
|
- `{SCRIPT}`: Replaced with script path during registration
|
||||||
|
|
||||||
|
**Example**:
|
||||||
|
|
||||||
|
````markdown
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
1. Parse arguments
|
||||||
|
2. Execute logic
|
||||||
|
|
||||||
|
```bash
|
||||||
|
args="$ARGUMENTS"
|
||||||
|
echo "Running with args: $args"
|
||||||
|
```
|
||||||
|
````
|
||||||
|
|
||||||
|
### Script Path Rewriting
|
||||||
|
|
||||||
|
Extension commands use relative paths that get rewritten during registration:
|
||||||
|
|
||||||
|
**In extension**:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
scripts:
|
||||||
|
sh: ../../scripts/bash/helper.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
**After registration**:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
scripts:
|
||||||
|
sh: .specify/scripts/bash/helper.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
This allows scripts to reference core spec-kit scripts.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Configuration Files
|
||||||
|
|
||||||
|
### Config Template
|
||||||
|
|
||||||
|
**File**: `my-ext-config.template.yml`
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# My Extension Configuration
|
||||||
|
# Copy this to my-ext-config.yml and customize
|
||||||
|
|
||||||
|
# Example configuration
|
||||||
|
api:
|
||||||
|
endpoint: "https://api.example.com"
|
||||||
|
timeout: 30
|
||||||
|
|
||||||
|
features:
|
||||||
|
feature_a: true
|
||||||
|
feature_b: false
|
||||||
|
|
||||||
|
credentials:
|
||||||
|
# DO NOT commit credentials!
|
||||||
|
# Use environment variables instead
|
||||||
|
api_key: "${MY_EXT_API_KEY}"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Config Loading
|
||||||
|
|
||||||
|
In your command, load config with layered precedence:
|
||||||
|
|
||||||
|
1. Extension defaults (`extension.yml` → `defaults`)
|
||||||
|
2. Project config (`.specify/extensions/my-ext/my-ext-config.yml`)
|
||||||
|
3. Local overrides (`.specify/extensions/my-ext/my-ext-config.local.yml` - gitignored)
|
||||||
|
4. Environment variables (`SPECKIT_MY_EXT_*`)
|
||||||
|
|
||||||
|
**Example loading script**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
EXT_DIR=".specify/extensions/my-ext"
|
||||||
|
|
||||||
|
# Load and merge config
|
||||||
|
config=$(yq eval '.' "$EXT_DIR/my-ext-config.yml" -o=json)
|
||||||
|
|
||||||
|
# Apply env overrides
|
||||||
|
if [ -n "${SPECKIT_MY_EXT_API_KEY:-}" ]; then
|
||||||
|
config=$(echo "$config" | jq ".api.api_key = \"$SPECKIT_MY_EXT_API_KEY\"")
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$config"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Validation Rules
|
||||||
|
|
||||||
|
### Extension ID
|
||||||
|
|
||||||
|
- **Pattern**: `^[a-z0-9-]+$`
|
||||||
|
- **Valid**: `my-ext`, `tool-123`, `awesome-plugin`
|
||||||
|
- **Invalid**: `MyExt` (uppercase), `my_ext` (underscore), `my ext` (space)
|
||||||
|
|
||||||
|
### Extension Version
|
||||||
|
|
||||||
|
- **Format**: Semantic versioning (MAJOR.MINOR.PATCH)
|
||||||
|
- **Valid**: `1.0.0`, `0.1.0`, `2.5.3`
|
||||||
|
- **Invalid**: `1.0`, `v1.0.0`, `1.0.0-beta`
|
||||||
|
|
||||||
|
### Command Name
|
||||||
|
|
||||||
|
- **Pattern**: `^speckit\.[a-z0-9-]+\.[a-z0-9-]+$`
|
||||||
|
- **Valid**: `speckit.my-ext.hello`, `speckit.tool.cmd`
|
||||||
|
- **Invalid**: `my-ext.hello` (missing prefix), `speckit.hello` (no extension namespace)
|
||||||
|
|
||||||
|
### Command File Path
|
||||||
|
|
||||||
|
- **Must be** relative to extension root
|
||||||
|
- **Valid**: `commands/hello.md`, `commands/subdir/cmd.md`
|
||||||
|
- **Invalid**: `/absolute/path.md`, `../outside.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Testing Extensions
|
||||||
|
|
||||||
|
### Manual Testing
|
||||||
|
|
||||||
|
1. **Create test extension**
|
||||||
|
2. **Install locally**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
specify extension add --dev /path/to/extension
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Verify installation**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
specify extension list
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Test commands** with your AI agent
|
||||||
|
5. **Check command registration**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ls .claude/commands/speckit.my-ext.*
|
||||||
|
```
|
||||||
|
|
||||||
|
6. **Remove extension**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
specify extension remove my-ext
|
||||||
|
```
|
||||||
|
|
||||||
|
### Automated Testing
|
||||||
|
|
||||||
|
Create tests for your extension:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# tests/test_my_extension.py
|
||||||
|
import pytest
|
||||||
|
from pathlib import Path
|
||||||
|
from specify_cli.extensions import ExtensionManifest
|
||||||
|
|
||||||
|
def test_manifest_valid():
|
||||||
|
"""Test extension manifest is valid."""
|
||||||
|
manifest = ExtensionManifest(Path("extension.yml"))
|
||||||
|
assert manifest.id == "my-ext"
|
||||||
|
assert len(manifest.commands) >= 1
|
||||||
|
|
||||||
|
def test_command_files_exist():
|
||||||
|
"""Test all command files exist."""
|
||||||
|
manifest = ExtensionManifest(Path("extension.yml"))
|
||||||
|
for cmd in manifest.commands:
|
||||||
|
cmd_file = Path(cmd["file"])
|
||||||
|
assert cmd_file.exists(), f"Command file not found: {cmd_file}"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Distribution
|
||||||
|
|
||||||
|
### Option 1: GitHub Repository
|
||||||
|
|
||||||
|
1. **Create repository**: `spec-kit-my-ext`
|
||||||
|
2. **Add files**:
|
||||||
|
|
||||||
|
```text
|
||||||
|
spec-kit-my-ext/
|
||||||
|
├── extension.yml
|
||||||
|
├── commands/
|
||||||
|
├── scripts/
|
||||||
|
├── docs/
|
||||||
|
├── README.md
|
||||||
|
├── LICENSE
|
||||||
|
└── CHANGELOG.md
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Create release**: Tag with version (e.g., `v1.0.0`)
|
||||||
|
4. **Install from repo**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/you/spec-kit-my-ext
|
||||||
|
specify extension add --dev spec-kit-my-ext/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option 2: ZIP Archive (Future)
|
||||||
|
|
||||||
|
Create ZIP archive and host on GitHub Releases:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
zip -r spec-kit-my-ext-1.0.0.zip extension.yml commands/ scripts/ docs/
|
||||||
|
```
|
||||||
|
|
||||||
|
Users install with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
specify extension add --from https://github.com/.../spec-kit-my-ext-1.0.0.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option 3: Extension Catalog (Future)
|
||||||
|
|
||||||
|
Submit to official catalog:
|
||||||
|
|
||||||
|
1. **Fork** spec-kit repository
|
||||||
|
2. **Add entry** to `extensions/catalog.json`
|
||||||
|
3. **Create PR**
|
||||||
|
4. **After merge**, users can install with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
specify extension add my-ext # No URL needed!
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
### Naming Conventions
|
||||||
|
|
||||||
|
- **Extension ID**: Use descriptive, hyphenated names (`jira-integration`, not `ji`)
|
||||||
|
- **Commands**: Use verb-noun pattern (`create-issue`, `sync-status`)
|
||||||
|
- **Config files**: Match extension ID (`jira-config.yml`)
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- **README.md**: Overview, installation, usage
|
||||||
|
- **CHANGELOG.md**: Version history
|
||||||
|
- **docs/**: Detailed guides
|
||||||
|
- **Command descriptions**: Clear, concise
|
||||||
|
|
||||||
|
### Versioning
|
||||||
|
|
||||||
|
- **Follow SemVer**: `MAJOR.MINOR.PATCH`
|
||||||
|
- **MAJOR**: Breaking changes
|
||||||
|
- **MINOR**: New features
|
||||||
|
- **PATCH**: Bug fixes
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
- **Never commit secrets**: Use environment variables
|
||||||
|
- **Validate input**: Sanitize user arguments
|
||||||
|
- **Document permissions**: What files/APIs are accessed
|
||||||
|
|
||||||
|
### Compatibility
|
||||||
|
|
||||||
|
- **Specify version range**: Don't require exact version
|
||||||
|
- **Test with multiple versions**: Ensure compatibility
|
||||||
|
- **Graceful degradation**: Handle missing features
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Example Extensions
|
||||||
|
|
||||||
|
### Minimal Extension
|
||||||
|
|
||||||
|
Smallest possible extension:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# extension.yml
|
||||||
|
schema_version: "1.0"
|
||||||
|
extension:
|
||||||
|
id: "minimal"
|
||||||
|
name: "Minimal Extension"
|
||||||
|
version: "1.0.0"
|
||||||
|
description: "Minimal example"
|
||||||
|
requires:
|
||||||
|
speckit_version: ">=0.1.0"
|
||||||
|
provides:
|
||||||
|
commands:
|
||||||
|
- name: "speckit.minimal.hello"
|
||||||
|
file: "commands/hello.md"
|
||||||
|
```
|
||||||
|
|
||||||
|
````markdown
|
||||||
|
<!-- commands/hello.md -->
|
||||||
|
---
|
||||||
|
description: "Hello command"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Hello World
|
||||||
|
|
||||||
|
```bash
|
||||||
|
echo "Hello, $ARGUMENTS!"
|
||||||
|
```
|
||||||
|
````
|
||||||
|
|
||||||
|
### Extension with Config
|
||||||
|
|
||||||
|
Extension using configuration:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# extension.yml
|
||||||
|
# ... metadata ...
|
||||||
|
provides:
|
||||||
|
config:
|
||||||
|
- name: "tool-config.yml"
|
||||||
|
template: "tool-config.template.yml"
|
||||||
|
required: true
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# tool-config.template.yml
|
||||||
|
api_endpoint: "https://api.example.com"
|
||||||
|
timeout: 30
|
||||||
|
```
|
||||||
|
|
||||||
|
````markdown
|
||||||
|
<!-- commands/use-config.md -->
|
||||||
|
# Use Config
|
||||||
|
|
||||||
|
Load config:
|
||||||
|
```bash
|
||||||
|
config_file=".specify/extensions/tool/tool-config.yml"
|
||||||
|
endpoint=$(yq eval '.api_endpoint' "$config_file")
|
||||||
|
echo "Using endpoint: $endpoint"
|
||||||
|
```
|
||||||
|
````
|
||||||
|
|
||||||
|
### Extension with Hooks
|
||||||
|
|
||||||
|
Extension that runs automatically:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# extension.yml
|
||||||
|
hooks:
|
||||||
|
after_tasks:
|
||||||
|
command: "speckit.auto.analyze"
|
||||||
|
optional: false # Always run
|
||||||
|
description: "Analyze tasks after generation"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Extension won't install
|
||||||
|
|
||||||
|
**Error**: `Invalid extension ID`
|
||||||
|
|
||||||
|
- **Fix**: Use lowercase, alphanumeric + hyphens only
|
||||||
|
|
||||||
|
**Error**: `Extension requires spec-kit >=0.2.0`
|
||||||
|
|
||||||
|
- **Fix**: Update spec-kit with `uv tool install specify-cli --force`
|
||||||
|
|
||||||
|
**Error**: `Command file not found`
|
||||||
|
|
||||||
|
- **Fix**: Ensure command files exist at paths specified in manifest
|
||||||
|
|
||||||
|
### Commands not registered
|
||||||
|
|
||||||
|
**Symptom**: Commands don't appear in AI agent
|
||||||
|
|
||||||
|
**Check**:
|
||||||
|
|
||||||
|
1. `.claude/commands/` directory exists
|
||||||
|
2. Extension installed successfully
|
||||||
|
3. Commands registered in registry:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cat .specify/extensions/.registry
|
||||||
|
```
|
||||||
|
|
||||||
|
**Fix**: Reinstall extension to trigger registration
|
||||||
|
|
||||||
|
### Config not loading
|
||||||
|
|
||||||
|
**Check**:
|
||||||
|
|
||||||
|
1. Config file exists: `.specify/extensions/{ext-id}/{ext-id}-config.yml`
|
||||||
|
2. YAML syntax is valid: `yq eval '.' config.yml`
|
||||||
|
3. Environment variables set correctly
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Getting Help
|
||||||
|
|
||||||
|
- **Issues**: Report bugs at GitHub repository
|
||||||
|
- **Discussions**: Ask questions in GitHub Discussions
|
||||||
|
- **Examples**: See `spec-kit-jira` for full-featured example (Phase B)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. **Create your extension** following this guide
|
||||||
|
2. **Test locally** with `--dev` flag
|
||||||
|
3. **Share with community** (GitHub, catalog)
|
||||||
|
4. **Iterate** based on feedback
|
||||||
|
|
||||||
|
Happy extending! 🚀
|
||||||
530
extensions/EXTENSION-PUBLISHING-GUIDE.md
Normal file
530
extensions/EXTENSION-PUBLISHING-GUIDE.md
Normal file
@@ -0,0 +1,530 @@
|
|||||||
|
# Extension Publishing Guide
|
||||||
|
|
||||||
|
This guide explains how to publish your extension to the Spec Kit extension catalog, making it discoverable by `specify extension search`.
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
1. [Prerequisites](#prerequisites)
|
||||||
|
2. [Prepare Your Extension](#prepare-your-extension)
|
||||||
|
3. [Submit to Catalog](#submit-to-catalog)
|
||||||
|
4. [Verification Process](#verification-process)
|
||||||
|
5. [Release Workflow](#release-workflow)
|
||||||
|
6. [Best Practices](#best-practices)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
Before publishing an extension, ensure you have:
|
||||||
|
|
||||||
|
1. **Valid Extension**: A working extension with a valid `extension.yml` manifest
|
||||||
|
2. **Git Repository**: Extension hosted on GitHub (or other public git hosting)
|
||||||
|
3. **Documentation**: README.md with installation and usage instructions
|
||||||
|
4. **License**: Open source license file (MIT, Apache 2.0, etc.)
|
||||||
|
5. **Versioning**: Semantic versioning (e.g., 1.0.0)
|
||||||
|
6. **Testing**: Extension tested on real projects
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prepare Your Extension
|
||||||
|
|
||||||
|
### 1. Extension Structure
|
||||||
|
|
||||||
|
Ensure your extension follows the standard structure:
|
||||||
|
|
||||||
|
```text
|
||||||
|
your-extension/
|
||||||
|
├── extension.yml # Required: Extension manifest
|
||||||
|
├── README.md # Required: Documentation
|
||||||
|
├── LICENSE # Required: License file
|
||||||
|
├── CHANGELOG.md # Recommended: Version history
|
||||||
|
├── .gitignore # Recommended: Git ignore rules
|
||||||
|
│
|
||||||
|
├── commands/ # Extension commands
|
||||||
|
│ ├── command1.md
|
||||||
|
│ └── command2.md
|
||||||
|
│
|
||||||
|
├── config-template.yml # Config template (if needed)
|
||||||
|
│
|
||||||
|
└── docs/ # Additional documentation
|
||||||
|
├── usage.md
|
||||||
|
└── examples/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. extension.yml Validation
|
||||||
|
|
||||||
|
Verify your manifest is valid:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
schema_version: "1.0"
|
||||||
|
|
||||||
|
extension:
|
||||||
|
id: "your-extension" # Unique lowercase-hyphenated ID
|
||||||
|
name: "Your Extension Name" # Human-readable name
|
||||||
|
version: "1.0.0" # Semantic version
|
||||||
|
description: "Brief description (one sentence)"
|
||||||
|
author: "Your Name or Organization"
|
||||||
|
repository: "https://github.com/your-org/spec-kit-your-extension"
|
||||||
|
license: "MIT"
|
||||||
|
homepage: "https://github.com/your-org/spec-kit-your-extension"
|
||||||
|
|
||||||
|
requires:
|
||||||
|
speckit_version: ">=0.1.0" # Required spec-kit version
|
||||||
|
|
||||||
|
provides:
|
||||||
|
commands: # List all commands
|
||||||
|
- name: "speckit.your-extension.command"
|
||||||
|
file: "commands/command.md"
|
||||||
|
description: "Command description"
|
||||||
|
|
||||||
|
tags: # 2-5 relevant tags
|
||||||
|
- "category"
|
||||||
|
- "tool-name"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Validation Checklist**:
|
||||||
|
|
||||||
|
- ✅ `id` is lowercase with hyphens only (no underscores, spaces, or special characters)
|
||||||
|
- ✅ `version` follows semantic versioning (X.Y.Z)
|
||||||
|
- ✅ `description` is concise (under 100 characters)
|
||||||
|
- ✅ `repository` URL is valid and public
|
||||||
|
- ✅ All command files exist in the extension directory
|
||||||
|
- ✅ Tags are lowercase and descriptive
|
||||||
|
|
||||||
|
### 3. Create GitHub Release
|
||||||
|
|
||||||
|
Create a GitHub release for your extension version:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Tag the release
|
||||||
|
git tag v1.0.0
|
||||||
|
git push origin v1.0.0
|
||||||
|
|
||||||
|
# Create release on GitHub
|
||||||
|
# Go to: https://github.com/your-org/spec-kit-your-extension/releases/new
|
||||||
|
# - Tag: v1.0.0
|
||||||
|
# - Title: v1.0.0 - Release Name
|
||||||
|
# - Description: Changelog/release notes
|
||||||
|
```
|
||||||
|
|
||||||
|
The release archive URL will be:
|
||||||
|
|
||||||
|
```text
|
||||||
|
https://github.com/your-org/spec-kit-your-extension/archive/refs/tags/v1.0.0.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Test Installation
|
||||||
|
|
||||||
|
Test that users can install from your release:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Test dev installation
|
||||||
|
specify extension add --dev /path/to/your-extension
|
||||||
|
|
||||||
|
# Test from GitHub archive
|
||||||
|
specify extension add --from https://github.com/your-org/spec-kit-your-extension/archive/refs/tags/v1.0.0.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Submit to Catalog
|
||||||
|
|
||||||
|
### 1. Fork the spec-kit Repository
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Fork on GitHub
|
||||||
|
# https://github.com/statsperform/spec-kit/fork
|
||||||
|
|
||||||
|
# Clone your fork
|
||||||
|
git clone https://github.com/YOUR-USERNAME/spec-kit.git
|
||||||
|
cd spec-kit
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Add Extension to Catalog
|
||||||
|
|
||||||
|
Edit `extensions/catalog.json` and add your extension:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"schema_version": "1.0",
|
||||||
|
"updated_at": "2026-01-28T15:54:00Z",
|
||||||
|
"catalog_url": "https://raw.githubusercontent.com/statsperform/spec-kit/main/extensions/catalog.json",
|
||||||
|
"extensions": {
|
||||||
|
"your-extension": {
|
||||||
|
"name": "Your Extension Name",
|
||||||
|
"id": "your-extension",
|
||||||
|
"description": "Brief description of your extension",
|
||||||
|
"author": "Your Name",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"download_url": "https://github.com/your-org/spec-kit-your-extension/archive/refs/tags/v1.0.0.zip",
|
||||||
|
"repository": "https://github.com/your-org/spec-kit-your-extension",
|
||||||
|
"homepage": "https://github.com/your-org/spec-kit-your-extension",
|
||||||
|
"documentation": "https://github.com/your-org/spec-kit-your-extension/blob/main/docs/",
|
||||||
|
"changelog": "https://github.com/your-org/spec-kit-your-extension/blob/main/CHANGELOG.md",
|
||||||
|
"license": "MIT",
|
||||||
|
"requires": {
|
||||||
|
"speckit_version": ">=0.1.0",
|
||||||
|
"tools": [
|
||||||
|
{
|
||||||
|
"name": "required-mcp-tool",
|
||||||
|
"version": ">=1.0.0",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"provides": {
|
||||||
|
"commands": 3,
|
||||||
|
"hooks": 1
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"category",
|
||||||
|
"tool-name",
|
||||||
|
"feature"
|
||||||
|
],
|
||||||
|
"verified": false,
|
||||||
|
"downloads": 0,
|
||||||
|
"stars": 0,
|
||||||
|
"created_at": "2026-01-28T00:00:00Z",
|
||||||
|
"updated_at": "2026-01-28T00:00:00Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Important**:
|
||||||
|
|
||||||
|
- Set `verified: false` (maintainers will verify)
|
||||||
|
- Set `downloads: 0` and `stars: 0` (auto-updated later)
|
||||||
|
- Use current timestamp for `created_at` and `updated_at`
|
||||||
|
- Update the top-level `updated_at` to current time
|
||||||
|
|
||||||
|
### 3. Submit Pull Request
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create a branch
|
||||||
|
git checkout -b add-your-extension
|
||||||
|
|
||||||
|
# Commit your changes
|
||||||
|
git add extensions/catalog.json
|
||||||
|
git commit -m "Add your-extension to catalog
|
||||||
|
|
||||||
|
- Extension ID: your-extension
|
||||||
|
- Version: 1.0.0
|
||||||
|
- Author: Your Name
|
||||||
|
- Description: Brief description
|
||||||
|
"
|
||||||
|
|
||||||
|
# Push to your fork
|
||||||
|
git push origin add-your-extension
|
||||||
|
|
||||||
|
# Create Pull Request on GitHub
|
||||||
|
# https://github.com/statsperform/spec-kit/compare
|
||||||
|
```
|
||||||
|
|
||||||
|
**Pull Request Template**:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Extension Submission
|
||||||
|
|
||||||
|
**Extension Name**: Your Extension Name
|
||||||
|
**Extension ID**: your-extension
|
||||||
|
**Version**: 1.0.0
|
||||||
|
**Author**: Your Name
|
||||||
|
**Repository**: https://github.com/your-org/spec-kit-your-extension
|
||||||
|
|
||||||
|
### Description
|
||||||
|
Brief description of what your extension does.
|
||||||
|
|
||||||
|
### Checklist
|
||||||
|
- [x] Valid extension.yml manifest
|
||||||
|
- [x] README.md with installation and usage docs
|
||||||
|
- [x] LICENSE file included
|
||||||
|
- [x] GitHub release created (v1.0.0)
|
||||||
|
- [x] Extension tested on real project
|
||||||
|
- [x] All commands working
|
||||||
|
- [x] No security vulnerabilities
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
Tested on:
|
||||||
|
- macOS 13.0+ with spec-kit 0.1.0
|
||||||
|
- Project: [Your test project]
|
||||||
|
|
||||||
|
### Additional Notes
|
||||||
|
Any additional context or notes for reviewers.
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Verification Process
|
||||||
|
|
||||||
|
### What Happens After Submission
|
||||||
|
|
||||||
|
1. **Automated Checks** (if available):
|
||||||
|
- Manifest validation
|
||||||
|
- Download URL accessibility
|
||||||
|
- Repository existence
|
||||||
|
- License file presence
|
||||||
|
|
||||||
|
2. **Manual Review**:
|
||||||
|
- Code quality review
|
||||||
|
- Security audit
|
||||||
|
- Functionality testing
|
||||||
|
- Documentation review
|
||||||
|
|
||||||
|
3. **Verification**:
|
||||||
|
- If approved, `verified: true` is set
|
||||||
|
- Extension appears in `specify extension search --verified`
|
||||||
|
|
||||||
|
### Verification Criteria
|
||||||
|
|
||||||
|
To be verified, your extension must:
|
||||||
|
|
||||||
|
✅ **Functionality**:
|
||||||
|
|
||||||
|
- Works as described in documentation
|
||||||
|
- All commands execute without errors
|
||||||
|
- No breaking changes to user workflows
|
||||||
|
|
||||||
|
✅ **Security**:
|
||||||
|
|
||||||
|
- No known vulnerabilities
|
||||||
|
- No malicious code
|
||||||
|
- Safe handling of user data
|
||||||
|
- Proper validation of inputs
|
||||||
|
|
||||||
|
✅ **Code Quality**:
|
||||||
|
|
||||||
|
- Clean, readable code
|
||||||
|
- Follows extension best practices
|
||||||
|
- Proper error handling
|
||||||
|
- Helpful error messages
|
||||||
|
|
||||||
|
✅ **Documentation**:
|
||||||
|
|
||||||
|
- Clear installation instructions
|
||||||
|
- Usage examples
|
||||||
|
- Troubleshooting section
|
||||||
|
- Accurate description
|
||||||
|
|
||||||
|
✅ **Maintenance**:
|
||||||
|
|
||||||
|
- Active repository
|
||||||
|
- Responsive to issues
|
||||||
|
- Regular updates
|
||||||
|
- Semantic versioning followed
|
||||||
|
|
||||||
|
### Typical Review Timeline
|
||||||
|
|
||||||
|
- **Automated checks**: Immediate (if implemented)
|
||||||
|
- **Manual review**: 3-7 business days
|
||||||
|
- **Verification**: After successful review
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Release Workflow
|
||||||
|
|
||||||
|
### Publishing New Versions
|
||||||
|
|
||||||
|
When releasing a new version:
|
||||||
|
|
||||||
|
1. **Update version** in `extension.yml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
extension:
|
||||||
|
version: "1.1.0" # Updated version
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Update CHANGELOG.md**:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## [1.1.0] - 2026-02-15
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- New feature X
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Bug fix Y
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Create GitHub release**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git tag v1.1.0
|
||||||
|
git push origin v1.1.0
|
||||||
|
# Create release on GitHub
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Update catalog**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Fork spec-kit repo (or update existing fork)
|
||||||
|
cd spec-kit
|
||||||
|
|
||||||
|
# Update extensions/catalog.json
|
||||||
|
jq '.extensions["your-extension"].version = "1.1.0"' extensions/catalog.json > tmp.json && mv tmp.json extensions/catalog.json
|
||||||
|
jq '.extensions["your-extension"].download_url = "https://github.com/your-org/spec-kit-your-extension/archive/refs/tags/v1.1.0.zip"' extensions/catalog.json > tmp.json && mv tmp.json extensions/catalog.json
|
||||||
|
jq '.extensions["your-extension"].updated_at = "2026-02-15T00:00:00Z"' extensions/catalog.json > tmp.json && mv tmp.json extensions/catalog.json
|
||||||
|
jq '.updated_at = "2026-02-15T00:00:00Z"' extensions/catalog.json > tmp.json && mv tmp.json extensions/catalog.json
|
||||||
|
|
||||||
|
# Submit PR
|
||||||
|
git checkout -b update-your-extension-v1.1.0
|
||||||
|
git add extensions/catalog.json
|
||||||
|
git commit -m "Update your-extension to v1.1.0"
|
||||||
|
git push origin update-your-extension-v1.1.0
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Submit update PR** with changelog in description
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
### Extension Design
|
||||||
|
|
||||||
|
1. **Single Responsibility**: Each extension should focus on one tool/integration
|
||||||
|
2. **Clear Naming**: Use descriptive, unambiguous names
|
||||||
|
3. **Minimal Dependencies**: Avoid unnecessary dependencies
|
||||||
|
4. **Backward Compatibility**: Follow semantic versioning strictly
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
1. **README.md Structure**:
|
||||||
|
- Overview and features
|
||||||
|
- Installation instructions
|
||||||
|
- Configuration guide
|
||||||
|
- Usage examples
|
||||||
|
- Troubleshooting
|
||||||
|
- Contributing guidelines
|
||||||
|
|
||||||
|
2. **Command Documentation**:
|
||||||
|
- Clear description
|
||||||
|
- Prerequisites listed
|
||||||
|
- Step-by-step instructions
|
||||||
|
- Error handling guidance
|
||||||
|
- Examples
|
||||||
|
|
||||||
|
3. **Configuration**:
|
||||||
|
- Provide template file
|
||||||
|
- Document all options
|
||||||
|
- Include examples
|
||||||
|
- Explain defaults
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
1. **Input Validation**: Validate all user inputs
|
||||||
|
2. **No Hardcoded Secrets**: Never include credentials
|
||||||
|
3. **Safe Dependencies**: Only use trusted dependencies
|
||||||
|
4. **Audit Regularly**: Check for vulnerabilities
|
||||||
|
|
||||||
|
### Maintenance
|
||||||
|
|
||||||
|
1. **Respond to Issues**: Address issues within 1-2 weeks
|
||||||
|
2. **Regular Updates**: Keep dependencies updated
|
||||||
|
3. **Changelog**: Maintain detailed changelog
|
||||||
|
4. **Deprecation**: Give advance notice for breaking changes
|
||||||
|
|
||||||
|
### Community
|
||||||
|
|
||||||
|
1. **License**: Use permissive open-source license (MIT, Apache 2.0)
|
||||||
|
2. **Contributing**: Welcome contributions
|
||||||
|
3. **Code of Conduct**: Be respectful and inclusive
|
||||||
|
4. **Support**: Provide ways to get help (issues, discussions, email)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## FAQ
|
||||||
|
|
||||||
|
### Q: Can I publish private/proprietary extensions?
|
||||||
|
|
||||||
|
A: The main catalog is for public extensions only. For private extensions:
|
||||||
|
|
||||||
|
- Host your own catalog.json file
|
||||||
|
- Users add your catalog: `specify extension add-catalog https://your-domain.com/catalog.json`
|
||||||
|
- Not yet implemented - coming in Phase 4
|
||||||
|
|
||||||
|
### Q: How long does verification take?
|
||||||
|
|
||||||
|
A: Typically 3-7 business days for initial review. Updates to verified extensions are usually faster.
|
||||||
|
|
||||||
|
### Q: What if my extension is rejected?
|
||||||
|
|
||||||
|
A: You'll receive feedback on what needs to be fixed. Make the changes and resubmit.
|
||||||
|
|
||||||
|
### Q: Can I update my extension anytime?
|
||||||
|
|
||||||
|
A: Yes, submit a PR to update the catalog with your new version. Verified status may be re-evaluated for major changes.
|
||||||
|
|
||||||
|
### Q: Do I need to be verified to be in the catalog?
|
||||||
|
|
||||||
|
A: No, unverified extensions are still searchable. Verification just adds trust and visibility.
|
||||||
|
|
||||||
|
### Q: Can extensions have paid features?
|
||||||
|
|
||||||
|
A: Extensions should be free and open-source. Commercial support/services are allowed, but core functionality must be free.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
- **Catalog Issues**: <https://github.com/statsperform/spec-kit/issues>
|
||||||
|
- **Extension Template**: <https://github.com/statsperform/spec-kit-extension-template> (coming soon)
|
||||||
|
- **Development Guide**: See EXTENSION-DEVELOPMENT-GUIDE.md
|
||||||
|
- **Community**: Discussions and Q&A
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Appendix: Catalog Schema
|
||||||
|
|
||||||
|
### Complete Catalog Entry Schema
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "string (required)",
|
||||||
|
"id": "string (required, unique)",
|
||||||
|
"description": "string (required, <200 chars)",
|
||||||
|
"author": "string (required)",
|
||||||
|
"version": "string (required, semver)",
|
||||||
|
"download_url": "string (required, valid URL)",
|
||||||
|
"repository": "string (required, valid URL)",
|
||||||
|
"homepage": "string (optional, valid URL)",
|
||||||
|
"documentation": "string (optional, valid URL)",
|
||||||
|
"changelog": "string (optional, valid URL)",
|
||||||
|
"license": "string (required)",
|
||||||
|
"requires": {
|
||||||
|
"speckit_version": "string (required, version specifier)",
|
||||||
|
"tools": [
|
||||||
|
{
|
||||||
|
"name": "string (required)",
|
||||||
|
"version": "string (optional, version specifier)",
|
||||||
|
"required": "boolean (default: false)"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"provides": {
|
||||||
|
"commands": "integer (optional)",
|
||||||
|
"hooks": "integer (optional)"
|
||||||
|
},
|
||||||
|
"tags": ["array of strings (2-10 tags)"],
|
||||||
|
"verified": "boolean (default: false)",
|
||||||
|
"downloads": "integer (auto-updated)",
|
||||||
|
"stars": "integer (auto-updated)",
|
||||||
|
"created_at": "string (ISO 8601 datetime)",
|
||||||
|
"updated_at": "string (ISO 8601 datetime)"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Valid Tags
|
||||||
|
|
||||||
|
Recommended tag categories:
|
||||||
|
|
||||||
|
- **Integration**: jira, linear, github, gitlab, azure-devops
|
||||||
|
- **Category**: issue-tracking, vcs, ci-cd, documentation, testing
|
||||||
|
- **Platform**: atlassian, microsoft, google
|
||||||
|
- **Feature**: automation, reporting, deployment, monitoring
|
||||||
|
|
||||||
|
Use 2-5 tags that best describe your extension.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Last Updated: 2026-01-28*
|
||||||
|
*Catalog Format Version: 1.0*
|
||||||
885
extensions/EXTENSION-USER-GUIDE.md
Normal file
885
extensions/EXTENSION-USER-GUIDE.md
Normal file
@@ -0,0 +1,885 @@
|
|||||||
|
# Extension User Guide
|
||||||
|
|
||||||
|
Complete guide for using Spec Kit extensions to enhance your workflow.
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
1. [Introduction](#introduction)
|
||||||
|
2. [Getting Started](#getting-started)
|
||||||
|
3. [Finding Extensions](#finding-extensions)
|
||||||
|
4. [Installing Extensions](#installing-extensions)
|
||||||
|
5. [Using Extensions](#using-extensions)
|
||||||
|
6. [Managing Extensions](#managing-extensions)
|
||||||
|
7. [Configuration](#configuration)
|
||||||
|
8. [Troubleshooting](#troubleshooting)
|
||||||
|
9. [Best Practices](#best-practices)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
### What are Extensions?
|
||||||
|
|
||||||
|
Extensions are modular packages that add new commands and functionality to Spec Kit without bloating the core framework. They allow you to:
|
||||||
|
|
||||||
|
- **Integrate** with external tools (Jira, Linear, GitHub, etc.)
|
||||||
|
- **Automate** repetitive tasks with hooks
|
||||||
|
- **Customize** workflows for your team
|
||||||
|
- **Share** solutions across projects
|
||||||
|
|
||||||
|
### Why Use Extensions?
|
||||||
|
|
||||||
|
- **Clean Core**: Keeps spec-kit lightweight and focused
|
||||||
|
- **Optional Features**: Only install what you need
|
||||||
|
- **Community Driven**: Anyone can create and share extensions
|
||||||
|
- **Version Controlled**: Extensions are versioned independently
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- Spec Kit version 0.1.0 or higher
|
||||||
|
- A spec-kit project (directory with `.specify/` folder)
|
||||||
|
|
||||||
|
### Check Your Version
|
||||||
|
|
||||||
|
```bash
|
||||||
|
specify --version
|
||||||
|
# Should show 0.1.0 or higher
|
||||||
|
```
|
||||||
|
|
||||||
|
### First Extension
|
||||||
|
|
||||||
|
Let's install the Jira extension as an example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Search for the extension
|
||||||
|
specify extension search jira
|
||||||
|
|
||||||
|
# 2. Get detailed information
|
||||||
|
specify extension info jira
|
||||||
|
|
||||||
|
# 3. Install it
|
||||||
|
specify extension add jira
|
||||||
|
|
||||||
|
# 4. Configure it
|
||||||
|
vim .specify/extensions/jira/jira-config.yml
|
||||||
|
|
||||||
|
# 5. Use it
|
||||||
|
# (Commands are now available in Claude Code)
|
||||||
|
/speckit.jira.specstoissues
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Finding Extensions
|
||||||
|
|
||||||
|
### Browse All Extensions
|
||||||
|
|
||||||
|
```bash
|
||||||
|
specify extension search
|
||||||
|
```
|
||||||
|
|
||||||
|
Shows all available extensions in the catalog.
|
||||||
|
|
||||||
|
### Search by Keyword
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Search for "jira"
|
||||||
|
specify extension search jira
|
||||||
|
|
||||||
|
# Search for "issue tracking"
|
||||||
|
specify extension search issue
|
||||||
|
```
|
||||||
|
|
||||||
|
### Filter by Tag
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Find all issue-tracking extensions
|
||||||
|
specify extension search --tag issue-tracking
|
||||||
|
|
||||||
|
# Find all Atlassian tools
|
||||||
|
specify extension search --tag atlassian
|
||||||
|
```
|
||||||
|
|
||||||
|
### Filter by Author
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Extensions by Stats Perform
|
||||||
|
specify extension search --author "Stats Perform"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Show Verified Only
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Only show verified extensions
|
||||||
|
specify extension search --verified
|
||||||
|
```
|
||||||
|
|
||||||
|
### Get Extension Details
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Detailed information
|
||||||
|
specify extension info jira
|
||||||
|
```
|
||||||
|
|
||||||
|
Shows:
|
||||||
|
|
||||||
|
- Description
|
||||||
|
- Requirements
|
||||||
|
- Commands provided
|
||||||
|
- Hooks available
|
||||||
|
- Links (documentation, repository, changelog)
|
||||||
|
- Installation status
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Installing Extensions
|
||||||
|
|
||||||
|
### Install from Catalog
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# By name (from catalog)
|
||||||
|
specify extension add jira
|
||||||
|
```
|
||||||
|
|
||||||
|
This will:
|
||||||
|
|
||||||
|
1. Download the extension from GitHub
|
||||||
|
2. Validate the manifest
|
||||||
|
3. Check compatibility with your spec-kit version
|
||||||
|
4. Install to `.specify/extensions/jira/`
|
||||||
|
5. Register commands with your AI agent
|
||||||
|
6. Create config template
|
||||||
|
|
||||||
|
### Install from URL
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# From GitHub release
|
||||||
|
specify extension add --from https://github.com/org/spec-kit-ext/archive/refs/tags/v1.0.0.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
### Install from Local Directory (Development)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# For testing or development
|
||||||
|
specify extension add --dev /path/to/extension
|
||||||
|
```
|
||||||
|
|
||||||
|
### Installation Output
|
||||||
|
|
||||||
|
```text
|
||||||
|
✓ Extension installed successfully!
|
||||||
|
|
||||||
|
Jira Integration (v1.0.0)
|
||||||
|
Create Jira Epics, Stories, and Issues from spec-kit artifacts
|
||||||
|
|
||||||
|
Provided commands:
|
||||||
|
• speckit.jira.specstoissues - Create Jira hierarchy from spec and tasks
|
||||||
|
• speckit.jira.discover-fields - Discover Jira custom fields for configuration
|
||||||
|
• speckit.jira.sync-status - Sync task completion status to Jira
|
||||||
|
|
||||||
|
⚠ Configuration may be required
|
||||||
|
Check: .specify/extensions/jira/
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Using Extensions
|
||||||
|
|
||||||
|
### Using Extension Commands
|
||||||
|
|
||||||
|
Extensions add commands that appear in your AI agent (Claude Code):
|
||||||
|
|
||||||
|
```text
|
||||||
|
# In Claude Code
|
||||||
|
> /speckit.jira.specstoissues
|
||||||
|
|
||||||
|
# Or use short alias (if provided)
|
||||||
|
> /speckit.specstoissues
|
||||||
|
```
|
||||||
|
|
||||||
|
### Extension Configuration
|
||||||
|
|
||||||
|
Most extensions require configuration:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Find the config file
|
||||||
|
ls .specify/extensions/jira/
|
||||||
|
|
||||||
|
# 2. Copy template to config
|
||||||
|
cp .specify/extensions/jira/jira-config.template.yml \
|
||||||
|
.specify/extensions/jira/jira-config.yml
|
||||||
|
|
||||||
|
# 3. Edit configuration
|
||||||
|
vim .specify/extensions/jira/jira-config.yml
|
||||||
|
|
||||||
|
# 4. Use the extension
|
||||||
|
# (Commands will now work with your config)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Extension Hooks
|
||||||
|
|
||||||
|
Some extensions provide hooks that execute after core commands:
|
||||||
|
|
||||||
|
**Example**: Jira extension hooks into `/speckit.tasks`
|
||||||
|
|
||||||
|
```text
|
||||||
|
# Run core command
|
||||||
|
> /speckit.tasks
|
||||||
|
|
||||||
|
# Output includes:
|
||||||
|
## Extension Hooks
|
||||||
|
|
||||||
|
**Optional Hook**: jira
|
||||||
|
Command: `/speckit.jira.specstoissues`
|
||||||
|
Description: Automatically create Jira hierarchy after task generation
|
||||||
|
|
||||||
|
Prompt: Create Jira issues from tasks?
|
||||||
|
To execute: `/speckit.jira.specstoissues`
|
||||||
|
```
|
||||||
|
|
||||||
|
You can then choose to run the hook or skip it.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Managing Extensions
|
||||||
|
|
||||||
|
### List Installed Extensions
|
||||||
|
|
||||||
|
```bash
|
||||||
|
specify extension list
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Installed Extensions:
|
||||||
|
|
||||||
|
✓ Jira Integration (v1.0.0)
|
||||||
|
Create Jira Epics, Stories, and Issues from spec-kit artifacts
|
||||||
|
Commands: 3 | Hooks: 1 | Status: Enabled
|
||||||
|
```
|
||||||
|
|
||||||
|
### Update Extensions
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check for updates (all extensions)
|
||||||
|
specify extension update
|
||||||
|
|
||||||
|
# Update specific extension
|
||||||
|
specify extension update jira
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```text
|
||||||
|
🔄 Checking for updates...
|
||||||
|
|
||||||
|
Updates available:
|
||||||
|
|
||||||
|
• jira: 1.0.0 → 1.1.0
|
||||||
|
|
||||||
|
Update these extensions? [y/N]:
|
||||||
|
```
|
||||||
|
|
||||||
|
### Disable Extension Temporarily
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Disable without removing
|
||||||
|
specify extension disable jira
|
||||||
|
|
||||||
|
✓ Extension 'jira' disabled
|
||||||
|
|
||||||
|
Commands will no longer be available. Hooks will not execute.
|
||||||
|
To re-enable: specify extension enable jira
|
||||||
|
```
|
||||||
|
|
||||||
|
### Re-enable Extension
|
||||||
|
|
||||||
|
```bash
|
||||||
|
specify extension enable jira
|
||||||
|
|
||||||
|
✓ Extension 'jira' enabled
|
||||||
|
```
|
||||||
|
|
||||||
|
### Remove Extension
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Remove extension (with confirmation)
|
||||||
|
specify extension remove jira
|
||||||
|
|
||||||
|
# Keep configuration when removing
|
||||||
|
specify extension remove jira --keep-config
|
||||||
|
|
||||||
|
# Force removal (no confirmation)
|
||||||
|
specify extension remove jira --force
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Configuration Files
|
||||||
|
|
||||||
|
Extensions can have multiple configuration files:
|
||||||
|
|
||||||
|
```text
|
||||||
|
.specify/extensions/jira/
|
||||||
|
├── jira-config.yml # Main config (version controlled)
|
||||||
|
├── jira-config.local.yml # Local overrides (gitignored)
|
||||||
|
└── jira-config.template.yml # Template (reference)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configuration Layers
|
||||||
|
|
||||||
|
Configuration is merged in this order (highest priority last):
|
||||||
|
|
||||||
|
1. **Extension defaults** (from `extension.yml`)
|
||||||
|
2. **Project config** (`jira-config.yml`)
|
||||||
|
3. **Local overrides** (`jira-config.local.yml`)
|
||||||
|
4. **Environment variables** (`SPECKIT_JIRA_*`)
|
||||||
|
|
||||||
|
### Example: Jira Configuration
|
||||||
|
|
||||||
|
**Project config** (`.specify/extensions/jira/jira-config.yml`):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
project:
|
||||||
|
key: "MSATS"
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
epic:
|
||||||
|
labels: ["spec-driven"]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Local override** (`.specify/extensions/jira/jira-config.local.yml`):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
project:
|
||||||
|
key: "MYTEST" # Override for local development
|
||||||
|
```
|
||||||
|
|
||||||
|
**Environment variable**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export SPECKIT_JIRA_PROJECT_KEY="DEVTEST"
|
||||||
|
```
|
||||||
|
|
||||||
|
Final resolved config uses `DEVTEST` from environment variable.
|
||||||
|
|
||||||
|
### Project-Wide Extension Settings
|
||||||
|
|
||||||
|
File: `.specify/extensions.yml`
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# Extensions installed in this project
|
||||||
|
installed:
|
||||||
|
- jira
|
||||||
|
- linear
|
||||||
|
|
||||||
|
# Global settings
|
||||||
|
settings:
|
||||||
|
auto_execute_hooks: true
|
||||||
|
|
||||||
|
# Hook configuration
|
||||||
|
hooks:
|
||||||
|
after_tasks:
|
||||||
|
- extension: jira
|
||||||
|
command: speckit.jira.specstoissues
|
||||||
|
enabled: true
|
||||||
|
optional: true
|
||||||
|
prompt: "Create Jira issues from tasks?"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Core Environment Variables
|
||||||
|
|
||||||
|
In addition to extension-specific environment variables (`SPECKIT_{EXT_ID}_*`), spec-kit supports core environment variables:
|
||||||
|
|
||||||
|
| Variable | Description | Default |
|
||||||
|
|----------|-------------|---------|
|
||||||
|
| `SPECKIT_CATALOG_URL` | Override the extension catalog URL | GitHub-hosted catalog |
|
||||||
|
| `GH_TOKEN` / `GITHUB_TOKEN` | GitHub API token for downloads | None |
|
||||||
|
|
||||||
|
#### Example: Using a custom catalog for testing
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Point to a local or alternative catalog
|
||||||
|
export SPECKIT_CATALOG_URL="http://localhost:8000/catalog.json"
|
||||||
|
|
||||||
|
# Or use a staging catalog
|
||||||
|
export SPECKIT_CATALOG_URL="https://example.com/staging/catalog.json"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Organization Catalog Customization
|
||||||
|
|
||||||
|
### Why the Default Catalog is Empty
|
||||||
|
|
||||||
|
The default spec-kit catalog ships empty by design. This allows organizations to:
|
||||||
|
|
||||||
|
- **Control available extensions** - Curate which extensions your team can install
|
||||||
|
- **Host private extensions** - Internal tools that shouldn't be public
|
||||||
|
- **Customize for compliance** - Meet security/audit requirements
|
||||||
|
- **Support air-gapped environments** - Work without internet access
|
||||||
|
|
||||||
|
### Setting Up a Custom Catalog
|
||||||
|
|
||||||
|
#### 1. Create Your Catalog File
|
||||||
|
|
||||||
|
Create a `catalog.json` file with your extensions:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"schema_version": "1.0",
|
||||||
|
"updated_at": "2026-02-03T00:00:00Z",
|
||||||
|
"catalog_url": "https://your-org.com/spec-kit/catalog.json",
|
||||||
|
"extensions": {
|
||||||
|
"jira": {
|
||||||
|
"name": "Jira Integration",
|
||||||
|
"id": "jira",
|
||||||
|
"description": "Create Jira issues from spec-kit artifacts",
|
||||||
|
"author": "Your Organization",
|
||||||
|
"version": "2.1.0",
|
||||||
|
"download_url": "https://github.com/your-org/spec-kit-jira/archive/refs/tags/v2.1.0.zip",
|
||||||
|
"repository": "https://github.com/your-org/spec-kit-jira",
|
||||||
|
"license": "MIT",
|
||||||
|
"requires": {
|
||||||
|
"speckit_version": ">=0.1.0",
|
||||||
|
"tools": [
|
||||||
|
{"name": "atlassian-mcp-server", "required": true}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"provides": {
|
||||||
|
"commands": 3,
|
||||||
|
"hooks": 1
|
||||||
|
},
|
||||||
|
"tags": ["jira", "atlassian", "issue-tracking"],
|
||||||
|
"verified": true
|
||||||
|
},
|
||||||
|
"internal-tool": {
|
||||||
|
"name": "Internal Tool Integration",
|
||||||
|
"id": "internal-tool",
|
||||||
|
"description": "Connect to internal company systems",
|
||||||
|
"author": "Your Organization",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"download_url": "https://internal.your-org.com/extensions/internal-tool-1.0.0.zip",
|
||||||
|
"repository": "https://github.internal.your-org.com/spec-kit-internal",
|
||||||
|
"license": "Proprietary",
|
||||||
|
"requires": {
|
||||||
|
"speckit_version": ">=0.1.0"
|
||||||
|
},
|
||||||
|
"provides": {
|
||||||
|
"commands": 2
|
||||||
|
},
|
||||||
|
"tags": ["internal", "proprietary"],
|
||||||
|
"verified": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. Host the Catalog
|
||||||
|
|
||||||
|
Options for hosting your catalog:
|
||||||
|
|
||||||
|
| Method | URL Example | Use Case |
|
||||||
|
| ------ | ----------- | -------- |
|
||||||
|
| GitHub Pages | `https://your-org.github.io/spec-kit-catalog/catalog.json` | Public or org-visible |
|
||||||
|
| Internal web server | `https://internal.company.com/spec-kit/catalog.json` | Corporate network |
|
||||||
|
| S3/Cloud storage | `https://s3.amazonaws.com/your-bucket/catalog.json` | Cloud-hosted teams |
|
||||||
|
| Local file server | `http://localhost:8000/catalog.json` | Development/testing |
|
||||||
|
|
||||||
|
**Security requirement**: URLs must use HTTPS (except `localhost` for testing).
|
||||||
|
|
||||||
|
#### 3. Configure Your Environment
|
||||||
|
|
||||||
|
##### Option A: Environment variable (recommended for CI/CD)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# In ~/.bashrc, ~/.zshrc, or CI pipeline
|
||||||
|
export SPECKIT_CATALOG_URL="https://your-org.com/spec-kit/catalog.json"
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Option B: Per-project configuration
|
||||||
|
|
||||||
|
Create `.env` or set in your shell before running spec-kit commands:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
SPECKIT_CATALOG_URL="https://your-org.com/spec-kit/catalog.json" specify extension search
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4. Verify Configuration
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Search should now show your catalog's extensions
|
||||||
|
specify extension search
|
||||||
|
|
||||||
|
# Install from your catalog
|
||||||
|
specify extension add jira
|
||||||
|
```
|
||||||
|
|
||||||
|
### Catalog JSON Schema
|
||||||
|
|
||||||
|
Required fields for each extension entry:
|
||||||
|
|
||||||
|
| Field | Type | Required | Description |
|
||||||
|
| ----- | ---- | -------- | ----------- |
|
||||||
|
| `name` | string | Yes | Human-readable name |
|
||||||
|
| `id` | string | Yes | Unique identifier (lowercase, hyphens) |
|
||||||
|
| `version` | string | Yes | Semantic version (X.Y.Z) |
|
||||||
|
| `download_url` | string | Yes | URL to ZIP archive |
|
||||||
|
| `repository` | string | Yes | Source code URL |
|
||||||
|
| `description` | string | No | Brief description |
|
||||||
|
| `author` | string | No | Author/organization |
|
||||||
|
| `license` | string | No | SPDX license identifier |
|
||||||
|
| `requires.speckit_version` | string | No | Version constraint |
|
||||||
|
| `requires.tools` | array | No | Required external tools |
|
||||||
|
| `provides.commands` | number | No | Number of commands |
|
||||||
|
| `provides.hooks` | number | No | Number of hooks |
|
||||||
|
| `tags` | array | No | Search tags |
|
||||||
|
| `verified` | boolean | No | Verification status |
|
||||||
|
|
||||||
|
### Use Cases
|
||||||
|
|
||||||
|
#### Private/Internal Extensions
|
||||||
|
|
||||||
|
Host proprietary extensions that integrate with internal systems:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"internal-auth": {
|
||||||
|
"name": "Internal SSO Integration",
|
||||||
|
"download_url": "https://artifactory.company.com/spec-kit/internal-auth-1.0.0.zip",
|
||||||
|
"verified": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Curated Team Catalog
|
||||||
|
|
||||||
|
Limit which extensions your team can install:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"extensions": {
|
||||||
|
"jira": { "..." },
|
||||||
|
"github": { "..." }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Only `jira` and `github` will appear in `specify extension search`.
|
||||||
|
|
||||||
|
#### Air-Gapped Environments
|
||||||
|
|
||||||
|
For networks without internet access:
|
||||||
|
|
||||||
|
1. Download extension ZIPs to internal file server
|
||||||
|
2. Create catalog pointing to internal URLs
|
||||||
|
3. Host catalog on internal web server
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"jira": {
|
||||||
|
"download_url": "https://files.internal/spec-kit/jira-2.1.0.zip"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Development/Testing
|
||||||
|
|
||||||
|
Test new extensions before publishing:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Start local server
|
||||||
|
python -m http.server 8000 --directory ./my-catalog/
|
||||||
|
|
||||||
|
# Point spec-kit to local catalog
|
||||||
|
export SPECKIT_CATALOG_URL="http://localhost:8000/catalog.json"
|
||||||
|
|
||||||
|
# Test installation
|
||||||
|
specify extension add my-new-extension
|
||||||
|
```
|
||||||
|
|
||||||
|
### Combining with Direct Installation
|
||||||
|
|
||||||
|
You can still install extensions not in your catalog using `--from`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# From catalog
|
||||||
|
specify extension add jira
|
||||||
|
|
||||||
|
# Direct URL (bypasses catalog)
|
||||||
|
specify extension add --from https://github.com/someone/spec-kit-ext/archive/v1.0.0.zip
|
||||||
|
|
||||||
|
# Local development
|
||||||
|
specify extension add --dev /path/to/extension
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note**: Direct URL installation shows a security warning since the extension isn't from your configured catalog.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Extension Not Found
|
||||||
|
|
||||||
|
**Error**: `Extension 'jira' not found in catalog
|
||||||
|
|
||||||
|
**Solutions**:
|
||||||
|
|
||||||
|
1. Check spelling: `specify extension search jira`
|
||||||
|
2. Refresh catalog: `specify extension search --help`
|
||||||
|
3. Check internet connection
|
||||||
|
4. Extension may not be published yet
|
||||||
|
|
||||||
|
### Configuration Not Found
|
||||||
|
|
||||||
|
**Error**: `Jira configuration not found`
|
||||||
|
|
||||||
|
**Solutions**:
|
||||||
|
|
||||||
|
1. Check if extension is installed: `specify extension list`
|
||||||
|
2. Create config from template:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .specify/extensions/jira/jira-config.template.yml \
|
||||||
|
.specify/extensions/jira/jira-config.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Reinstall extension: `specify extension remove jira && specify extension add jira`
|
||||||
|
|
||||||
|
### Command Not Available
|
||||||
|
|
||||||
|
**Issue**: Extension command not appearing in AI agent
|
||||||
|
|
||||||
|
**Solutions**:
|
||||||
|
|
||||||
|
1. Check extension is enabled: `specify extension list`
|
||||||
|
2. Restart AI agent (Claude Code)
|
||||||
|
3. Check command file exists:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ls .claude/commands/speckit.jira.*.md
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Reinstall extension
|
||||||
|
|
||||||
|
### Incompatible Version
|
||||||
|
|
||||||
|
**Error**: `Extension requires spec-kit >=0.2.0, but you have 0.1.0`
|
||||||
|
|
||||||
|
**Solutions**:
|
||||||
|
|
||||||
|
1. Upgrade spec-kit:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv tool upgrade specify-cli
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Install older version of extension:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
specify extension add --from https://github.com/org/ext/archive/v1.0.0.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
### MCP Tool Not Available
|
||||||
|
|
||||||
|
**Error**: `Tool 'jira-mcp-server/epic_create' not found`
|
||||||
|
|
||||||
|
**Solutions**:
|
||||||
|
|
||||||
|
1. Check MCP server is installed
|
||||||
|
2. Check AI agent MCP configuration
|
||||||
|
3. Restart AI agent
|
||||||
|
4. Check extension requirements: `specify extension info jira`
|
||||||
|
|
||||||
|
### Permission Denied
|
||||||
|
|
||||||
|
**Error**: `Permission denied` when accessing Jira
|
||||||
|
|
||||||
|
**Solutions**:
|
||||||
|
|
||||||
|
1. Check Jira credentials in MCP server config
|
||||||
|
2. Verify project permissions in Jira
|
||||||
|
3. Test MCP server connection independently
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
### 1. Version Control
|
||||||
|
|
||||||
|
**Do commit**:
|
||||||
|
|
||||||
|
- `.specify/extensions.yml` (project extension config)
|
||||||
|
- `.specify/extensions/*/jira-config.yml` (project config)
|
||||||
|
|
||||||
|
**Don't commit**:
|
||||||
|
|
||||||
|
- `.specify/extensions/.cache/` (catalog cache)
|
||||||
|
- `.specify/extensions/.backup/` (config backups)
|
||||||
|
- `.specify/extensions/*/*.local.yml` (local overrides)
|
||||||
|
- `.specify/extensions/.registry` (installation state)
|
||||||
|
|
||||||
|
Add to `.gitignore`:
|
||||||
|
|
||||||
|
```gitignore
|
||||||
|
.specify/extensions/.cache/
|
||||||
|
.specify/extensions/.backup/
|
||||||
|
.specify/extensions/*/*.local.yml
|
||||||
|
.specify/extensions/.registry
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Team Workflows
|
||||||
|
|
||||||
|
**For teams**:
|
||||||
|
|
||||||
|
1. Agree on which extensions to use
|
||||||
|
2. Commit extension configuration
|
||||||
|
3. Document extension usage in README
|
||||||
|
4. Keep extensions updated together
|
||||||
|
|
||||||
|
**Example README section**:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Extensions
|
||||||
|
|
||||||
|
This project uses:
|
||||||
|
- **jira** (v1.0.0) - Jira integration
|
||||||
|
- Config: `.specify/extensions/jira/jira-config.yml`
|
||||||
|
- Requires: jira-mcp-server
|
||||||
|
|
||||||
|
To install: `specify extension add jira`
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Local Development
|
||||||
|
|
||||||
|
Use local config for development:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# .specify/extensions/jira/jira-config.local.yml
|
||||||
|
project:
|
||||||
|
key: "DEVTEST" # Your test project
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
task:
|
||||||
|
custom_fields:
|
||||||
|
customfield_10002: 1 # Lower story points for testing
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Environment-Specific Config
|
||||||
|
|
||||||
|
Use environment variables for CI/CD:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# .github/workflows/deploy.yml
|
||||||
|
env:
|
||||||
|
SPECKIT_JIRA_PROJECT_KEY: ${{ secrets.JIRA_PROJECT }}
|
||||||
|
|
||||||
|
- name: Create Jira Issues
|
||||||
|
run: specify extension add jira && ...
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Extension Updates
|
||||||
|
|
||||||
|
**Check for updates regularly**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Weekly or before major releases
|
||||||
|
specify extension update
|
||||||
|
```
|
||||||
|
|
||||||
|
**Pin versions for stability**:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# .specify/extensions.yml
|
||||||
|
installed:
|
||||||
|
- id: jira
|
||||||
|
version: "1.0.0" # Pin to specific version
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Minimal Extensions
|
||||||
|
|
||||||
|
Only install extensions you actively use:
|
||||||
|
|
||||||
|
- Reduces complexity
|
||||||
|
- Faster command loading
|
||||||
|
- Less configuration
|
||||||
|
|
||||||
|
### 7. Documentation
|
||||||
|
|
||||||
|
Document extension usage in your project:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# PROJECT.md
|
||||||
|
|
||||||
|
## Working with Jira
|
||||||
|
|
||||||
|
After creating tasks, sync to Jira:
|
||||||
|
1. Run `/speckit.tasks` to generate tasks
|
||||||
|
2. Run `/speckit.jira.specstoissues` to create Jira issues
|
||||||
|
3. Run `/speckit.jira.sync-status` to update status
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## FAQ
|
||||||
|
|
||||||
|
### Q: Can I use multiple extensions at once?
|
||||||
|
|
||||||
|
**A**: Yes! Extensions are designed to work together. Install as many as you need.
|
||||||
|
|
||||||
|
### Q: Do extensions slow down spec-kit?
|
||||||
|
|
||||||
|
**A**: No. Extensions are loaded on-demand and only when their commands are used.
|
||||||
|
|
||||||
|
### Q: Can I create private extensions?
|
||||||
|
|
||||||
|
**A**: Yes. Install with `--dev` or `--from` and keep private. Public catalog submission is optional.
|
||||||
|
|
||||||
|
### Q: How do I know if an extension is safe?
|
||||||
|
|
||||||
|
**A**: Look for the ✓ Verified badge. Verified extensions are reviewed by maintainers. Always review extension code before installing.
|
||||||
|
|
||||||
|
### Q: Can extensions modify spec-kit core?
|
||||||
|
|
||||||
|
**A**: No. Extensions can only add commands and hooks. They cannot modify core functionality.
|
||||||
|
|
||||||
|
### Q: What happens if two extensions have the same command name?
|
||||||
|
|
||||||
|
**A**: Extensions use namespaced commands (`speckit.{extension}.{command}`), so conflicts are very rare. The extension system will warn you if conflicts occur.
|
||||||
|
|
||||||
|
### Q: Can I contribute to existing extensions?
|
||||||
|
|
||||||
|
**A**: Yes! Most extensions are open source. Check the repository link in `specify extension info {extension}`.
|
||||||
|
|
||||||
|
### Q: How do I report extension bugs?
|
||||||
|
|
||||||
|
**A**: Go to the extension's repository (shown in `specify extension info`) and create an issue.
|
||||||
|
|
||||||
|
### Q: Can extensions work offline?
|
||||||
|
|
||||||
|
**A**: Once installed, extensions work offline. However, some extensions may require internet for their functionality (e.g., Jira requires Jira API access).
|
||||||
|
|
||||||
|
### Q: How do I backup my extension configuration?
|
||||||
|
|
||||||
|
**A**: Extension configs are in `.specify/extensions/{extension}/`. Back up this directory or commit configs to git.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
- **Extension Issues**: Report to extension repository (see `specify extension info`)
|
||||||
|
- **Spec Kit Issues**: <https://github.com/statsperform/spec-kit/issues>
|
||||||
|
- **Extension Catalog**: <https://github.com/statsperform/spec-kit/tree/main/extensions>
|
||||||
|
- **Documentation**: See EXTENSION-DEVELOPMENT-GUIDE.md and EXTENSION-PUBLISHING-GUIDE.md
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Last Updated: 2026-01-28*
|
||||||
|
*Spec Kit Version: 0.1.0*
|
||||||
1791
extensions/RFC-EXTENSION-SYSTEM.md
Normal file
1791
extensions/RFC-EXTENSION-SYSTEM.md
Normal file
File diff suppressed because it is too large
Load Diff
60
extensions/catalog.example.json
Normal file
60
extensions/catalog.example.json
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
{
|
||||||
|
"schema_version": "1.0",
|
||||||
|
"updated_at": "2026-02-03T00:00:00Z",
|
||||||
|
"catalog_url": "https://your-org.example.com/speckit/catalog.json",
|
||||||
|
"extensions": {
|
||||||
|
"jira": {
|
||||||
|
"name": "Jira Integration",
|
||||||
|
"id": "jira",
|
||||||
|
"description": "Create Jira Epics, Stories, and Issues from spec-kit artifacts",
|
||||||
|
"author": "Your Organization",
|
||||||
|
"version": "2.1.0",
|
||||||
|
"download_url": "https://github.com/your-org/spec-kit-jira/archive/refs/tags/v2.1.0.zip",
|
||||||
|
"repository": "https://github.com/your-org/spec-kit-jira",
|
||||||
|
"homepage": "https://github.com/your-org/spec-kit-jira",
|
||||||
|
"documentation": "https://github.com/your-org/spec-kit-jira/blob/main/README.md",
|
||||||
|
"changelog": "https://github.com/your-org/spec-kit-jira/blob/main/CHANGELOG.md",
|
||||||
|
"license": "MIT",
|
||||||
|
"requires": {
|
||||||
|
"speckit_version": ">=0.1.0",
|
||||||
|
"tools": [
|
||||||
|
{
|
||||||
|
"name": "atlassian",
|
||||||
|
"version": ">=1.0.0",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"provides": {
|
||||||
|
"commands": 3,
|
||||||
|
"hooks": 1
|
||||||
|
},
|
||||||
|
"tags": ["jira", "atlassian", "issue-tracking"],
|
||||||
|
"verified": true,
|
||||||
|
"downloads": 0,
|
||||||
|
"stars": 0,
|
||||||
|
"created_at": "2026-01-28T00:00:00Z",
|
||||||
|
"updated_at": "2026-02-03T00:00:00Z"
|
||||||
|
},
|
||||||
|
"linear": {
|
||||||
|
"name": "Linear Integration",
|
||||||
|
"id": "linear",
|
||||||
|
"description": "Sync specs and tasks with Linear issues",
|
||||||
|
"author": "Your Organization",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"download_url": "https://github.com/your-org/spec-kit-linear/archive/refs/tags/v1.0.0.zip",
|
||||||
|
"repository": "https://github.com/your-org/spec-kit-linear",
|
||||||
|
"license": "MIT",
|
||||||
|
"requires": {
|
||||||
|
"speckit_version": ">=0.1.0"
|
||||||
|
},
|
||||||
|
"provides": {
|
||||||
|
"commands": 2
|
||||||
|
},
|
||||||
|
"tags": ["linear", "issue-tracking"],
|
||||||
|
"verified": false,
|
||||||
|
"created_at": "2026-01-30T00:00:00Z",
|
||||||
|
"updated_at": "2026-01-30T00:00:00Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
6
extensions/catalog.json
Normal file
6
extensions/catalog.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"schema_version": "1.0",
|
||||||
|
"updated_at": "2026-02-03T00:00:00Z",
|
||||||
|
"catalog_url": "https://raw.githubusercontent.com/github/spec-kit/main/extensions/catalog.json",
|
||||||
|
"extensions": {}
|
||||||
|
}
|
||||||
39
extensions/template/.gitignore
vendored
Normal file
39
extensions/template/.gitignore
vendored
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# Local configuration overrides
|
||||||
|
*-config.local.yml
|
||||||
|
|
||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.so
|
||||||
|
.Python
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
.pytest_cache/
|
||||||
|
.coverage
|
||||||
|
htmlcov/
|
||||||
|
|
||||||
|
# IDEs
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Build artifacts
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
*.egg-info/
|
||||||
|
|
||||||
|
# Temporary files
|
||||||
|
*.tmp
|
||||||
|
.cache/
|
||||||
39
extensions/template/CHANGELOG.md
Normal file
39
extensions/template/CHANGELOG.md
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to this extension will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Planned
|
||||||
|
|
||||||
|
- Feature ideas for future versions
|
||||||
|
- Enhancements
|
||||||
|
- Bug fixes
|
||||||
|
|
||||||
|
## [1.0.0] - YYYY-MM-DD
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Initial release of extension
|
||||||
|
- Command: `/speckit.my-extension.example` - Example command functionality
|
||||||
|
- Configuration system with template
|
||||||
|
- Documentation and examples
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- Feature 1 description
|
||||||
|
- Feature 2 description
|
||||||
|
- Feature 3 description
|
||||||
|
|
||||||
|
### Requirements
|
||||||
|
|
||||||
|
- Spec Kit: >=0.1.0
|
||||||
|
- External dependencies (if any)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
[Unreleased]: https://github.com/your-org/spec-kit-my-extension/compare/v1.0.0...HEAD
|
||||||
|
[1.0.0]: https://github.com/your-org/spec-kit-my-extension/releases/tag/v1.0.0
|
||||||
158
extensions/template/EXAMPLE-README.md
Normal file
158
extensions/template/EXAMPLE-README.md
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
# EXAMPLE: Extension README
|
||||||
|
|
||||||
|
This is an example of what your extension README should look like after customization.
|
||||||
|
**Delete this file and replace README.md with content similar to this.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# My Extension
|
||||||
|
|
||||||
|
<!-- CUSTOMIZE: Replace with your extension description -->
|
||||||
|
|
||||||
|
Brief description of what your extension does and why it's useful.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
<!-- CUSTOMIZE: List key features -->
|
||||||
|
|
||||||
|
- Feature 1: Description
|
||||||
|
- Feature 2: Description
|
||||||
|
- Feature 3: Description
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install from catalog
|
||||||
|
specify extension add my-extension
|
||||||
|
|
||||||
|
# Or install from local development directory
|
||||||
|
specify extension add --dev /path/to/my-extension
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
1. Create configuration file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .specify/extensions/my-extension/config-template.yml \
|
||||||
|
.specify/extensions/my-extension/my-extension-config.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Edit configuration:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
vim .specify/extensions/my-extension/my-extension-config.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Set required values:
|
||||||
|
<!-- CUSTOMIZE: List required configuration -->
|
||||||
|
```yaml
|
||||||
|
connection:
|
||||||
|
url: "https://api.example.com"
|
||||||
|
api_key: "your-api-key"
|
||||||
|
|
||||||
|
project:
|
||||||
|
id: "your-project-id"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
<!-- CUSTOMIZE: Add usage examples -->
|
||||||
|
|
||||||
|
### Command: example
|
||||||
|
|
||||||
|
Description of what this command does.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# In Claude Code
|
||||||
|
> /speckit.my-extension.example
|
||||||
|
```
|
||||||
|
|
||||||
|
**Prerequisites**:
|
||||||
|
|
||||||
|
- Prerequisite 1
|
||||||
|
- Prerequisite 2
|
||||||
|
|
||||||
|
**Output**:
|
||||||
|
|
||||||
|
- What this command produces
|
||||||
|
- Where results are saved
|
||||||
|
|
||||||
|
## Configuration Reference
|
||||||
|
|
||||||
|
<!-- CUSTOMIZE: Document all configuration options -->
|
||||||
|
|
||||||
|
### Connection Settings
|
||||||
|
|
||||||
|
| Setting | Type | Required | Description |
|
||||||
|
|---------|------|----------|-------------|
|
||||||
|
| `connection.url` | string | Yes | API endpoint URL |
|
||||||
|
| `connection.api_key` | string | Yes | API authentication key |
|
||||||
|
|
||||||
|
### Project Settings
|
||||||
|
|
||||||
|
| Setting | Type | Required | Description |
|
||||||
|
|---------|------|----------|-------------|
|
||||||
|
| `project.id` | string | Yes | Project identifier |
|
||||||
|
| `project.workspace` | string | No | Workspace or organization |
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
Override configuration with environment variables:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Override connection settings
|
||||||
|
export SPECKIT_MY_EXTENSION_CONNECTION_URL="https://custom-api.com"
|
||||||
|
export SPECKIT_MY_EXTENSION_CONNECTION_API_KEY="custom-key"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
<!-- CUSTOMIZE: Add real-world examples -->
|
||||||
|
|
||||||
|
### Example 1: Basic Workflow
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Step 1: Create specification
|
||||||
|
> /speckit.spec
|
||||||
|
|
||||||
|
# Step 2: Generate tasks
|
||||||
|
> /speckit.tasks
|
||||||
|
|
||||||
|
# Step 3: Use extension
|
||||||
|
> /speckit.my-extension.example
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
<!-- CUSTOMIZE: Add common issues -->
|
||||||
|
|
||||||
|
### Issue: Configuration not found
|
||||||
|
|
||||||
|
**Solution**: Create config from template (see Configuration section)
|
||||||
|
|
||||||
|
### Issue: Command not available
|
||||||
|
|
||||||
|
**Solutions**:
|
||||||
|
|
||||||
|
1. Check extension is installed: `specify extension list`
|
||||||
|
2. Restart AI agent
|
||||||
|
3. Reinstall extension
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT License - see LICENSE file
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
- **Issues**: <https://github.com/your-org/spec-kit-my-extension/issues>
|
||||||
|
- **Spec Kit Docs**: <https://github.com/statsperform/spec-kit>
|
||||||
|
|
||||||
|
## Changelog
|
||||||
|
|
||||||
|
See [CHANGELOG.md](CHANGELOG.md) for version history.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Extension Version: 1.0.0*
|
||||||
|
*Spec Kit: >=0.1.0*
|
||||||
21
extensions/template/LICENSE
Normal file
21
extensions/template/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 [Your Name or Organization]
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
79
extensions/template/README.md
Normal file
79
extensions/template/README.md
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
# Extension Template
|
||||||
|
|
||||||
|
Starter template for creating a Spec Kit extension.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
1. **Copy this template**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp -r extensions/template my-extension
|
||||||
|
cd my-extension
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Customize `extension.yml`**:
|
||||||
|
- Change extension ID, name, description
|
||||||
|
- Update author and repository
|
||||||
|
- Define your commands
|
||||||
|
|
||||||
|
3. **Create commands**:
|
||||||
|
- Add command files in `commands/` directory
|
||||||
|
- Use Markdown format with YAML frontmatter
|
||||||
|
|
||||||
|
4. **Create config template**:
|
||||||
|
- Define configuration options
|
||||||
|
- Document all settings
|
||||||
|
|
||||||
|
5. **Write documentation**:
|
||||||
|
- Update README.md with usage instructions
|
||||||
|
- Add examples
|
||||||
|
|
||||||
|
6. **Test locally**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /path/to/spec-kit-project
|
||||||
|
specify extension add --dev /path/to/my-extension
|
||||||
|
```
|
||||||
|
|
||||||
|
7. **Publish** (optional):
|
||||||
|
- Create GitHub repository
|
||||||
|
- Create release
|
||||||
|
- Submit to catalog (see EXTENSION-PUBLISHING-GUIDE.md)
|
||||||
|
|
||||||
|
## Files in This Template
|
||||||
|
|
||||||
|
- `extension.yml` - Extension manifest (CUSTOMIZE THIS)
|
||||||
|
- `config-template.yml` - Configuration template (CUSTOMIZE THIS)
|
||||||
|
- `commands/example.md` - Example command (REPLACE THIS)
|
||||||
|
- `README.md` - Extension documentation (REPLACE THIS)
|
||||||
|
- `LICENSE` - MIT License (REVIEW THIS)
|
||||||
|
- `CHANGELOG.md` - Version history (UPDATE THIS)
|
||||||
|
- `.gitignore` - Git ignore rules
|
||||||
|
|
||||||
|
## Customization Checklist
|
||||||
|
|
||||||
|
- [ ] Update `extension.yml` with your extension details
|
||||||
|
- [ ] Change extension ID to your extension name
|
||||||
|
- [ ] Update author information
|
||||||
|
- [ ] Define your commands
|
||||||
|
- [ ] Create command files in `commands/`
|
||||||
|
- [ ] Update config template
|
||||||
|
- [ ] Write README with usage instructions
|
||||||
|
- [ ] Add examples
|
||||||
|
- [ ] Update LICENSE if needed
|
||||||
|
- [ ] Test extension locally
|
||||||
|
- [ ] Create git repository
|
||||||
|
- [ ] Create first release
|
||||||
|
|
||||||
|
## Need Help?
|
||||||
|
|
||||||
|
- **Development Guide**: See EXTENSION-DEVELOPMENT-GUIDE.md
|
||||||
|
- **API Reference**: See EXTENSION-API-REFERENCE.md
|
||||||
|
- **Publishing Guide**: See EXTENSION-PUBLISHING-GUIDE.md
|
||||||
|
- **User Guide**: See EXTENSION-USER-GUIDE.md
|
||||||
|
|
||||||
|
## Template Version
|
||||||
|
|
||||||
|
- Version: 1.0.0
|
||||||
|
- Last Updated: 2026-01-28
|
||||||
|
- Compatible with Spec Kit: >=0.1.0
|
||||||
210
extensions/template/commands/example.md
Normal file
210
extensions/template/commands/example.md
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
---
|
||||||
|
description: "Example command that demonstrates extension functionality"
|
||||||
|
# CUSTOMIZE: List MCP tools this command uses
|
||||||
|
tools:
|
||||||
|
- 'example-mcp-server/example_tool'
|
||||||
|
---
|
||||||
|
|
||||||
|
# Example Command
|
||||||
|
|
||||||
|
<!-- CUSTOMIZE: Replace this entire file with your command documentation -->
|
||||||
|
|
||||||
|
This is an example command that demonstrates how to create commands for Spec Kit extensions.
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
Describe what this command does and when to use it.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
List requirements before using this command:
|
||||||
|
|
||||||
|
1. Prerequisite 1 (e.g., "MCP server configured")
|
||||||
|
2. Prerequisite 2 (e.g., "Configuration file exists")
|
||||||
|
3. Prerequisite 3 (e.g., "Valid API credentials")
|
||||||
|
|
||||||
|
## User Input
|
||||||
|
|
||||||
|
$ARGUMENTS
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
### Step 1: Load Configuration
|
||||||
|
|
||||||
|
<!-- CUSTOMIZE: Replace with your actual steps -->
|
||||||
|
|
||||||
|
Load extension configuration from the project:
|
||||||
|
|
||||||
|
``bash
|
||||||
|
config_file=".specify/extensions/my-extension/my-extension-config.yml"
|
||||||
|
|
||||||
|
if [ ! -f "$config_file" ]; then
|
||||||
|
echo "❌ Error: Configuration not found at $config_file"
|
||||||
|
echo "Run 'specify extension add my-extension' to install and configure"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Read configuration values
|
||||||
|
|
||||||
|
setting_value=$(yq eval '.settings.key' "$config_file")
|
||||||
|
|
||||||
|
# Apply environment variable overrides
|
||||||
|
|
||||||
|
setting_value="${SPECKIT_MY_EXTENSION_KEY:-$setting_value}"
|
||||||
|
|
||||||
|
# Validate configuration
|
||||||
|
|
||||||
|
if [ -z "$setting_value" ]; then
|
||||||
|
echo "❌ Error: Configuration value not set"
|
||||||
|
echo "Edit $config_file and set 'settings.key'"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "📋 Configuration loaded: $setting_value"
|
||||||
|
``
|
||||||
|
|
||||||
|
### Step 2: Perform Main Action
|
||||||
|
|
||||||
|
<!-- CUSTOMIZE: Replace with your command logic -->
|
||||||
|
|
||||||
|
Describe what this step does:
|
||||||
|
|
||||||
|
``markdown
|
||||||
|
Use MCP tools to perform the main action:
|
||||||
|
|
||||||
|
- Tool: example-mcp-server example_tool
|
||||||
|
- Parameters: { "key": "$setting_value" }
|
||||||
|
|
||||||
|
This calls the MCP server tool to execute the operation.
|
||||||
|
``
|
||||||
|
|
||||||
|
### Step 3: Process Results
|
||||||
|
|
||||||
|
<!-- CUSTOMIZE: Add more steps as needed -->
|
||||||
|
|
||||||
|
Process the results and provide output:
|
||||||
|
|
||||||
|
`` bash
|
||||||
|
echo ""
|
||||||
|
echo "✅ Command completed successfully!"
|
||||||
|
echo ""
|
||||||
|
echo "Results:"
|
||||||
|
echo " • Item 1: Value"
|
||||||
|
echo " • Item 2: Value"
|
||||||
|
echo ""
|
||||||
|
``
|
||||||
|
|
||||||
|
### Step 4: Save Output (Optional)
|
||||||
|
|
||||||
|
Save results to a file if needed:
|
||||||
|
|
||||||
|
``bash
|
||||||
|
output_file=".specify/my-extension-output.json"
|
||||||
|
|
||||||
|
cat > "$output_file" <<EOF
|
||||||
|
{
|
||||||
|
"timestamp": "$(date -u +"%Y-%m-%dT%H:%M:%SZ")",
|
||||||
|
"setting": "$setting_value",
|
||||||
|
"results": []
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "💾 Output saved to $output_file"
|
||||||
|
``
|
||||||
|
|
||||||
|
## Configuration Reference
|
||||||
|
|
||||||
|
<!-- CUSTOMIZE: Document configuration options -->
|
||||||
|
|
||||||
|
This command uses the following configuration from `my-extension-config.yml`:
|
||||||
|
|
||||||
|
- **settings.key**: Description of what this setting does
|
||||||
|
- Type: string
|
||||||
|
- Required: Yes
|
||||||
|
- Example: `"example-value"`
|
||||||
|
|
||||||
|
- **settings.another_key**: Description of another setting
|
||||||
|
- Type: boolean
|
||||||
|
- Required: No
|
||||||
|
- Default: `false`
|
||||||
|
- Example: `true`
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
<!-- CUSTOMIZE: Document environment variable overrides -->
|
||||||
|
|
||||||
|
Configuration can be overridden with environment variables:
|
||||||
|
|
||||||
|
- `SPECKIT_MY_EXTENSION_KEY` - Overrides `settings.key`
|
||||||
|
- `SPECKIT_MY_EXTENSION_ANOTHER_KEY` - Overrides `settings.another_key`
|
||||||
|
|
||||||
|
Example:
|
||||||
|
``bash
|
||||||
|
export SPECKIT_MY_EXTENSION_KEY="override-value"
|
||||||
|
``
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
<!-- CUSTOMIZE: Add common issues and solutions -->
|
||||||
|
|
||||||
|
### "Configuration not found"
|
||||||
|
|
||||||
|
**Solution**: Install the extension and create configuration:
|
||||||
|
``bash
|
||||||
|
specify extension add my-extension
|
||||||
|
cp .specify/extensions/my-extension/config-template.yml \
|
||||||
|
.specify/extensions/my-extension/my-extension-config.yml
|
||||||
|
``
|
||||||
|
|
||||||
|
### "MCP tool not available"
|
||||||
|
|
||||||
|
**Solution**: Ensure MCP server is configured in your AI agent settings.
|
||||||
|
|
||||||
|
### "Permission denied"
|
||||||
|
|
||||||
|
**Solution**: Check credentials and permissions in the external service.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
<!-- CUSTOMIZE: Add helpful notes and tips -->
|
||||||
|
|
||||||
|
- This command requires an active connection to the external service
|
||||||
|
- Results are cached for performance
|
||||||
|
- Re-run the command to refresh data
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
<!-- CUSTOMIZE: Add usage examples -->
|
||||||
|
|
||||||
|
### Example 1: Basic Usage
|
||||||
|
|
||||||
|
``bash
|
||||||
|
|
||||||
|
# Run with default configuration
|
||||||
|
>
|
||||||
|
> /speckit.my-extension.example
|
||||||
|
``
|
||||||
|
|
||||||
|
### Example 2: With Environment Override
|
||||||
|
|
||||||
|
``bash
|
||||||
|
|
||||||
|
# Override configuration with environment variable
|
||||||
|
|
||||||
|
export SPECKIT_MY_EXTENSION_KEY="custom-value"
|
||||||
|
> /speckit.my-extension.example
|
||||||
|
``
|
||||||
|
|
||||||
|
### Example 3: After Core Command
|
||||||
|
|
||||||
|
``bash
|
||||||
|
|
||||||
|
# Use as part of a workflow
|
||||||
|
>
|
||||||
|
> /speckit.tasks
|
||||||
|
> /speckit.my-extension.example
|
||||||
|
``
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*For more information, see the extension README or run `specify extension info my-extension`*
|
||||||
75
extensions/template/config-template.yml
Normal file
75
extensions/template/config-template.yml
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
# Extension Configuration Template
|
||||||
|
# Copy this to my-extension-config.yml and customize for your project
|
||||||
|
|
||||||
|
# CUSTOMIZE: Add your configuration sections below
|
||||||
|
|
||||||
|
# Example: Connection settings
|
||||||
|
connection:
|
||||||
|
# URL to external service
|
||||||
|
url: "" # REQUIRED: e.g., "https://api.example.com"
|
||||||
|
|
||||||
|
# API key or token
|
||||||
|
api_key: "" # REQUIRED: Your API key
|
||||||
|
|
||||||
|
# Example: Project settings
|
||||||
|
project:
|
||||||
|
# Project identifier
|
||||||
|
id: "" # REQUIRED: e.g., "my-project"
|
||||||
|
|
||||||
|
# Workspace or organization
|
||||||
|
workspace: "" # OPTIONAL: e.g., "my-org"
|
||||||
|
|
||||||
|
# Example: Feature flags
|
||||||
|
features:
|
||||||
|
# Enable/disable main functionality
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
# Automatic synchronization
|
||||||
|
auto_sync: false
|
||||||
|
|
||||||
|
# Verbose logging
|
||||||
|
verbose: false
|
||||||
|
|
||||||
|
# Example: Default values
|
||||||
|
defaults:
|
||||||
|
# Labels to apply
|
||||||
|
labels: [] # e.g., ["automated", "spec-kit"]
|
||||||
|
|
||||||
|
# Priority level
|
||||||
|
priority: "medium" # Options: "low", "medium", "high"
|
||||||
|
|
||||||
|
# Assignee
|
||||||
|
assignee: "" # OPTIONAL: Default assignee
|
||||||
|
|
||||||
|
# Example: Field mappings
|
||||||
|
# Map internal names to external field IDs
|
||||||
|
field_mappings:
|
||||||
|
# Example mappings
|
||||||
|
# internal_field: "external_field_id"
|
||||||
|
# status: "customfield_10001"
|
||||||
|
|
||||||
|
# Example: Advanced settings
|
||||||
|
advanced:
|
||||||
|
# Timeout in seconds
|
||||||
|
timeout: 30
|
||||||
|
|
||||||
|
# Retry attempts
|
||||||
|
retry_count: 3
|
||||||
|
|
||||||
|
# Cache duration in seconds
|
||||||
|
cache_duration: 3600
|
||||||
|
|
||||||
|
# Environment Variable Overrides:
|
||||||
|
# You can override any setting with environment variables using this pattern:
|
||||||
|
# SPECKIT_MY_EXTENSION_{SECTION}_{KEY}
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
# - SPECKIT_MY_EXTENSION_CONNECTION_API_KEY: Override connection.api_key
|
||||||
|
# - SPECKIT_MY_EXTENSION_PROJECT_ID: Override project.id
|
||||||
|
# - SPECKIT_MY_EXTENSION_FEATURES_ENABLED: Override features.enabled
|
||||||
|
#
|
||||||
|
# Note: Use uppercase and replace dots with underscores
|
||||||
|
|
||||||
|
# Local Overrides:
|
||||||
|
# For local development, create my-extension-config.local.yml (gitignored)
|
||||||
|
# to override settings without affecting the team configuration
|
||||||
97
extensions/template/extension.yml
Normal file
97
extensions/template/extension.yml
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
schema_version: "1.0"
|
||||||
|
|
||||||
|
extension:
|
||||||
|
# CUSTOMIZE: Change 'my-extension' to your extension ID (lowercase, hyphen-separated)
|
||||||
|
id: "my-extension"
|
||||||
|
|
||||||
|
# CUSTOMIZE: Human-readable name for your extension
|
||||||
|
name: "My Extension"
|
||||||
|
|
||||||
|
# CUSTOMIZE: Update version when releasing (semantic versioning: X.Y.Z)
|
||||||
|
version: "1.0.0"
|
||||||
|
|
||||||
|
# CUSTOMIZE: Brief description (under 200 characters)
|
||||||
|
description: "Brief description of what your extension does"
|
||||||
|
|
||||||
|
# CUSTOMIZE: Your name or organization name
|
||||||
|
author: "Your Name"
|
||||||
|
|
||||||
|
# CUSTOMIZE: GitHub repository URL (create before publishing)
|
||||||
|
repository: "https://github.com/your-org/spec-kit-my-extension"
|
||||||
|
|
||||||
|
# REVIEW: License (MIT is recommended for open source)
|
||||||
|
license: "MIT"
|
||||||
|
|
||||||
|
# CUSTOMIZE: Extension homepage (can be same as repository)
|
||||||
|
homepage: "https://github.com/your-org/spec-kit-my-extension"
|
||||||
|
|
||||||
|
# Requirements for this extension
|
||||||
|
requires:
|
||||||
|
# CUSTOMIZE: Minimum spec-kit version required
|
||||||
|
# Use >=X.Y.Z for minimum version
|
||||||
|
# Use >=X.Y.Z,<Y.0.0 for version range
|
||||||
|
speckit_version: ">=0.1.0"
|
||||||
|
|
||||||
|
# CUSTOMIZE: Add MCP tools or other dependencies
|
||||||
|
# Remove if no external tools required
|
||||||
|
tools:
|
||||||
|
- name: "example-mcp-server"
|
||||||
|
version: ">=1.0.0"
|
||||||
|
required: true
|
||||||
|
|
||||||
|
# Commands provided by this extension
|
||||||
|
provides:
|
||||||
|
commands:
|
||||||
|
# CUSTOMIZE: Define your commands
|
||||||
|
# Pattern: speckit.{extension-id}.{command-name}
|
||||||
|
- name: "speckit.my-extension.example"
|
||||||
|
file: "commands/example.md"
|
||||||
|
description: "Example command that demonstrates functionality"
|
||||||
|
# Optional: Add aliases for shorter command names
|
||||||
|
aliases: ["speckit.example"]
|
||||||
|
|
||||||
|
# ADD MORE COMMANDS: Copy this block for each command
|
||||||
|
# - name: "speckit.my-extension.another-command"
|
||||||
|
# file: "commands/another-command.md"
|
||||||
|
# description: "Another command"
|
||||||
|
|
||||||
|
# CUSTOMIZE: Define configuration files
|
||||||
|
config:
|
||||||
|
- name: "my-extension-config.yml"
|
||||||
|
template: "config-template.yml"
|
||||||
|
description: "Extension configuration"
|
||||||
|
required: true # Set to false if config is optional
|
||||||
|
|
||||||
|
# CUSTOMIZE: Define hooks (optional)
|
||||||
|
# Remove if no hooks needed
|
||||||
|
hooks:
|
||||||
|
# Hook that runs after /speckit.tasks
|
||||||
|
after_tasks:
|
||||||
|
command: "speckit.my-extension.example"
|
||||||
|
optional: true # User will be prompted
|
||||||
|
prompt: "Run example command?"
|
||||||
|
description: "Demonstrates hook functionality"
|
||||||
|
condition: null # Future: conditional execution
|
||||||
|
|
||||||
|
# ADD MORE HOOKS: Copy this block for other events
|
||||||
|
# after_implement:
|
||||||
|
# command: "speckit.my-extension.another"
|
||||||
|
# optional: false # Auto-execute without prompting
|
||||||
|
# description: "Runs automatically after implementation"
|
||||||
|
|
||||||
|
# CUSTOMIZE: Add relevant tags (2-5 recommended)
|
||||||
|
# Used for discovery in catalog
|
||||||
|
tags:
|
||||||
|
- "example"
|
||||||
|
- "template"
|
||||||
|
# ADD MORE: "category", "tool-name", etc.
|
||||||
|
|
||||||
|
# CUSTOMIZE: Default configuration values (optional)
|
||||||
|
# These are merged with user config
|
||||||
|
defaults:
|
||||||
|
# Example default values
|
||||||
|
feature:
|
||||||
|
enabled: true
|
||||||
|
auto_sync: false
|
||||||
|
|
||||||
|
# ADD MORE: Any default settings for your extension
|
||||||
@@ -1,15 +1,18 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "specify-cli"
|
name = "specify-cli"
|
||||||
version = "0.0.7"
|
version = "0.1.2"
|
||||||
description = "Specify CLI, part of GitHub Spec Kit. A tool to bootstrap your projects for Spec-Driven Development (SDD)."
|
description = "Specify CLI, part of GitHub Spec Kit. A tool to bootstrap your projects for Spec-Driven Development (SDD)."
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.11"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"typer",
|
"typer",
|
||||||
|
"click>=8.1",
|
||||||
"rich",
|
"rich",
|
||||||
"httpx[socks]",
|
"httpx[socks]",
|
||||||
"platformdirs",
|
"platformdirs",
|
||||||
"readchar",
|
"readchar",
|
||||||
"truststore>=0.10.4",
|
"truststore>=0.10.4",
|
||||||
|
"pyyaml>=6.0",
|
||||||
|
"packaging>=23.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
@@ -21,3 +24,31 @@ build-backend = "hatchling.build"
|
|||||||
|
|
||||||
[tool.hatch.build.targets.wheel]
|
[tool.hatch.build.targets.wheel]
|
||||||
packages = ["src/specify_cli"]
|
packages = ["src/specify_cli"]
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
test = [
|
||||||
|
"pytest>=7.0",
|
||||||
|
"pytest-cov>=4.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
testpaths = ["tests"]
|
||||||
|
python_files = ["test_*.py"]
|
||||||
|
python_classes = ["Test*"]
|
||||||
|
python_functions = ["test_*"]
|
||||||
|
addopts = [
|
||||||
|
"-v",
|
||||||
|
"--strict-markers",
|
||||||
|
"--tb=short",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.coverage.run]
|
||||||
|
source = ["src"]
|
||||||
|
omit = ["*/tests/*", "*/__pycache__/*"]
|
||||||
|
|
||||||
|
[tool.coverage.report]
|
||||||
|
precision = 2
|
||||||
|
show_missing = true
|
||||||
|
skip_covered = false
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -e
|
|
||||||
JSON_MODE=false
|
|
||||||
for arg in "$@"; do case "$arg" in --json) JSON_MODE=true ;; --help|-h) echo "Usage: $0 [--json]"; exit 0 ;; esac; done
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
||||||
source "$SCRIPT_DIR/common.sh"
|
|
||||||
eval $(get_feature_paths)
|
|
||||||
check_feature_branch "$CURRENT_BRANCH" || exit 1
|
|
||||||
if [[ ! -d "$FEATURE_DIR" ]]; then echo "ERROR: Feature directory not found: $FEATURE_DIR"; echo "Run /specify first."; exit 1; fi
|
|
||||||
if [[ ! -f "$IMPL_PLAN" ]]; then echo "ERROR: plan.md not found in $FEATURE_DIR"; echo "Run /plan first."; exit 1; fi
|
|
||||||
if [[ ! -f "$TASKS" ]]; then echo "ERROR: tasks.md not found in $FEATURE_DIR"; echo "Run /tasks first."; exit 1; fi
|
|
||||||
if $JSON_MODE; then
|
|
||||||
docs=(); [[ -f "$RESEARCH" ]] && docs+=("research.md"); [[ -f "$DATA_MODEL" ]] && docs+=("data-model.md"); ([[ -d "$CONTRACTS_DIR" ]] && [[ -n "$(ls -A "$CONTRACTS_DIR" 2>/dev/null)" ]]) && docs+=("contracts/"); [[ -f "$QUICKSTART" ]] && docs+=("quickstart.md"); [[ -f "$TASKS" ]] && docs+=("tasks.md");
|
|
||||||
json_docs=$(printf '"%s",' "${docs[@]}"); json_docs="[${json_docs%,}]"; printf '{"FEATURE_DIR":"%s","AVAILABLE_DOCS":%s}\n' "$FEATURE_DIR" "$json_docs"
|
|
||||||
else
|
|
||||||
echo "FEATURE_DIR:$FEATURE_DIR"; echo "AVAILABLE_DOCS:"; check_file "$RESEARCH" "research.md"; check_file "$DATA_MODEL" "data-model.md"; check_dir "$CONTRACTS_DIR" "contracts/"; check_file "$QUICKSTART" "quickstart.md"; check_file "$TASKS" "tasks.md"; fi
|
|
||||||
166
scripts/bash/check-prerequisites.sh
Normal file
166
scripts/bash/check-prerequisites.sh
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Consolidated prerequisite checking script
|
||||||
|
#
|
||||||
|
# This script provides unified prerequisite checking for Spec-Driven Development workflow.
|
||||||
|
# It replaces the functionality previously spread across multiple scripts.
|
||||||
|
#
|
||||||
|
# Usage: ./check-prerequisites.sh [OPTIONS]
|
||||||
|
#
|
||||||
|
# OPTIONS:
|
||||||
|
# --json Output in JSON format
|
||||||
|
# --require-tasks Require tasks.md to exist (for implementation phase)
|
||||||
|
# --include-tasks Include tasks.md in AVAILABLE_DOCS list
|
||||||
|
# --paths-only Only output path variables (no validation)
|
||||||
|
# --help, -h Show help message
|
||||||
|
#
|
||||||
|
# OUTPUTS:
|
||||||
|
# JSON mode: {"FEATURE_DIR":"...", "AVAILABLE_DOCS":["..."]}
|
||||||
|
# Text mode: FEATURE_DIR:... \n AVAILABLE_DOCS: \n ✓/✗ file.md
|
||||||
|
# Paths only: REPO_ROOT: ... \n BRANCH: ... \n FEATURE_DIR: ... etc.
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Parse command line arguments
|
||||||
|
JSON_MODE=false
|
||||||
|
REQUIRE_TASKS=false
|
||||||
|
INCLUDE_TASKS=false
|
||||||
|
PATHS_ONLY=false
|
||||||
|
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--json)
|
||||||
|
JSON_MODE=true
|
||||||
|
;;
|
||||||
|
--require-tasks)
|
||||||
|
REQUIRE_TASKS=true
|
||||||
|
;;
|
||||||
|
--include-tasks)
|
||||||
|
INCLUDE_TASKS=true
|
||||||
|
;;
|
||||||
|
--paths-only)
|
||||||
|
PATHS_ONLY=true
|
||||||
|
;;
|
||||||
|
--help|-h)
|
||||||
|
cat << 'EOF'
|
||||||
|
Usage: check-prerequisites.sh [OPTIONS]
|
||||||
|
|
||||||
|
Consolidated prerequisite checking for Spec-Driven Development workflow.
|
||||||
|
|
||||||
|
OPTIONS:
|
||||||
|
--json Output in JSON format
|
||||||
|
--require-tasks Require tasks.md to exist (for implementation phase)
|
||||||
|
--include-tasks Include tasks.md in AVAILABLE_DOCS list
|
||||||
|
--paths-only Only output path variables (no prerequisite validation)
|
||||||
|
--help, -h Show this help message
|
||||||
|
|
||||||
|
EXAMPLES:
|
||||||
|
# Check task prerequisites (plan.md required)
|
||||||
|
./check-prerequisites.sh --json
|
||||||
|
|
||||||
|
# Check implementation prerequisites (plan.md + tasks.md required)
|
||||||
|
./check-prerequisites.sh --json --require-tasks --include-tasks
|
||||||
|
|
||||||
|
# Get feature paths only (no validation)
|
||||||
|
./check-prerequisites.sh --paths-only
|
||||||
|
|
||||||
|
EOF
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "ERROR: Unknown option '$arg'. Use --help for usage information." >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Source common functions
|
||||||
|
SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
source "$SCRIPT_DIR/common.sh"
|
||||||
|
|
||||||
|
# Get feature paths and validate branch
|
||||||
|
eval $(get_feature_paths)
|
||||||
|
check_feature_branch "$CURRENT_BRANCH" "$HAS_GIT" || exit 1
|
||||||
|
|
||||||
|
# If paths-only mode, output paths and exit (support JSON + paths-only combined)
|
||||||
|
if $PATHS_ONLY; then
|
||||||
|
if $JSON_MODE; then
|
||||||
|
# Minimal JSON paths payload (no validation performed)
|
||||||
|
printf '{"REPO_ROOT":"%s","BRANCH":"%s","FEATURE_DIR":"%s","FEATURE_SPEC":"%s","IMPL_PLAN":"%s","TASKS":"%s"}\n' \
|
||||||
|
"$REPO_ROOT" "$CURRENT_BRANCH" "$FEATURE_DIR" "$FEATURE_SPEC" "$IMPL_PLAN" "$TASKS"
|
||||||
|
else
|
||||||
|
echo "REPO_ROOT: $REPO_ROOT"
|
||||||
|
echo "BRANCH: $CURRENT_BRANCH"
|
||||||
|
echo "FEATURE_DIR: $FEATURE_DIR"
|
||||||
|
echo "FEATURE_SPEC: $FEATURE_SPEC"
|
||||||
|
echo "IMPL_PLAN: $IMPL_PLAN"
|
||||||
|
echo "TASKS: $TASKS"
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Validate required directories and files
|
||||||
|
if [[ ! -d "$FEATURE_DIR" ]]; then
|
||||||
|
echo "ERROR: Feature directory not found: $FEATURE_DIR" >&2
|
||||||
|
echo "Run /speckit.specify first to create the feature structure." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -f "$IMPL_PLAN" ]]; then
|
||||||
|
echo "ERROR: plan.md not found in $FEATURE_DIR" >&2
|
||||||
|
echo "Run /speckit.plan first to create the implementation plan." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for tasks.md if required
|
||||||
|
if $REQUIRE_TASKS && [[ ! -f "$TASKS" ]]; then
|
||||||
|
echo "ERROR: tasks.md not found in $FEATURE_DIR" >&2
|
||||||
|
echo "Run /speckit.tasks first to create the task list." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Build list of available documents
|
||||||
|
docs=()
|
||||||
|
|
||||||
|
# Always check these optional docs
|
||||||
|
[[ -f "$RESEARCH" ]] && docs+=("research.md")
|
||||||
|
[[ -f "$DATA_MODEL" ]] && docs+=("data-model.md")
|
||||||
|
|
||||||
|
# Check contracts directory (only if it exists and has files)
|
||||||
|
if [[ -d "$CONTRACTS_DIR" ]] && [[ -n "$(ls -A "$CONTRACTS_DIR" 2>/dev/null)" ]]; then
|
||||||
|
docs+=("contracts/")
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ -f "$QUICKSTART" ]] && docs+=("quickstart.md")
|
||||||
|
|
||||||
|
# Include tasks.md if requested and it exists
|
||||||
|
if $INCLUDE_TASKS && [[ -f "$TASKS" ]]; then
|
||||||
|
docs+=("tasks.md")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Output results
|
||||||
|
if $JSON_MODE; then
|
||||||
|
# Build JSON array of documents
|
||||||
|
if [[ ${#docs[@]} -eq 0 ]]; then
|
||||||
|
json_docs="[]"
|
||||||
|
else
|
||||||
|
json_docs=$(printf '"%s",' "${docs[@]}")
|
||||||
|
json_docs="[${json_docs%,}]"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '{"FEATURE_DIR":"%s","AVAILABLE_DOCS":%s}\n' "$FEATURE_DIR" "$json_docs"
|
||||||
|
else
|
||||||
|
# Text output
|
||||||
|
echo "FEATURE_DIR:$FEATURE_DIR"
|
||||||
|
echo "AVAILABLE_DOCS:"
|
||||||
|
|
||||||
|
# Show status of each potential document
|
||||||
|
check_file "$RESEARCH" "research.md"
|
||||||
|
check_file "$DATA_MODEL" "data-model.md"
|
||||||
|
check_dir "$CONTRACTS_DIR" "contracts/"
|
||||||
|
check_file "$QUICKSTART" "quickstart.md"
|
||||||
|
|
||||||
|
if $INCLUDE_TASKS; then
|
||||||
|
check_file "$TASKS" "tasks.md"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -e
|
|
||||||
JSON_MODE=false
|
|
||||||
for arg in "$@"; do case "$arg" in --json) JSON_MODE=true ;; --help|-h) echo "Usage: $0 [--json]"; exit 0 ;; esac; done
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
||||||
source "$SCRIPT_DIR/common.sh"
|
|
||||||
eval $(get_feature_paths)
|
|
||||||
check_feature_branch "$CURRENT_BRANCH" || exit 1
|
|
||||||
if [[ ! -d "$FEATURE_DIR" ]]; then echo "ERROR: Feature directory not found: $FEATURE_DIR"; echo "Run /specify first."; exit 1; fi
|
|
||||||
if [[ ! -f "$IMPL_PLAN" ]]; then echo "ERROR: plan.md not found in $FEATURE_DIR"; echo "Run /plan first."; exit 1; fi
|
|
||||||
if $JSON_MODE; then
|
|
||||||
docs=(); [[ -f "$RESEARCH" ]] && docs+=("research.md"); [[ -f "$DATA_MODEL" ]] && docs+=("data-model.md"); ([[ -d "$CONTRACTS_DIR" ]] && [[ -n "$(ls -A "$CONTRACTS_DIR" 2>/dev/null)" ]]) && docs+=("contracts/"); [[ -f "$QUICKSTART" ]] && docs+=("quickstart.md");
|
|
||||||
json_docs=$(printf '"%s",' "${docs[@]}"); json_docs="[${json_docs%,}]"; printf '{"FEATURE_DIR":"%s","AVAILABLE_DOCS":%s}\n' "$FEATURE_DIR" "$json_docs"
|
|
||||||
else
|
|
||||||
echo "FEATURE_DIR:$FEATURE_DIR"; echo "AVAILABLE_DOCS:"; check_file "$RESEARCH" "research.md"; check_file "$DATA_MODEL" "data-model.md"; check_dir "$CONTRACTS_DIR" "contracts/"; check_file "$QUICKSTART" "quickstart.md"; fi
|
|
||||||
@@ -1,27 +1,145 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# (Moved to scripts/bash/) Common functions and variables for all scripts
|
# Common functions and variables for all scripts
|
||||||
|
|
||||||
get_repo_root() { git rev-parse --show-toplevel; }
|
# Get repository root, with fallback for non-git repositories
|
||||||
get_current_branch() { git rev-parse --abbrev-ref HEAD; }
|
get_repo_root() {
|
||||||
|
if git rev-parse --show-toplevel >/dev/null 2>&1; then
|
||||||
|
git rev-parse --show-toplevel
|
||||||
|
else
|
||||||
|
# Fall back to script location for non-git repos
|
||||||
|
local script_dir="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
(cd "$script_dir/../../.." && pwd)
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get current branch, with fallback for non-git repositories
|
||||||
|
get_current_branch() {
|
||||||
|
# First check if SPECIFY_FEATURE environment variable is set
|
||||||
|
if [[ -n "${SPECIFY_FEATURE:-}" ]]; then
|
||||||
|
echo "$SPECIFY_FEATURE"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Then check git if available
|
||||||
|
if git rev-parse --abbrev-ref HEAD >/dev/null 2>&1; then
|
||||||
|
git rev-parse --abbrev-ref HEAD
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For non-git repos, try to find the latest feature directory
|
||||||
|
local repo_root=$(get_repo_root)
|
||||||
|
local specs_dir="$repo_root/specs"
|
||||||
|
|
||||||
|
if [[ -d "$specs_dir" ]]; then
|
||||||
|
local latest_feature=""
|
||||||
|
local highest=0
|
||||||
|
|
||||||
|
for dir in "$specs_dir"/*; do
|
||||||
|
if [[ -d "$dir" ]]; then
|
||||||
|
local dirname=$(basename "$dir")
|
||||||
|
if [[ "$dirname" =~ ^([0-9]{3})- ]]; then
|
||||||
|
local number=${BASH_REMATCH[1]}
|
||||||
|
number=$((10#$number))
|
||||||
|
if [[ "$number" -gt "$highest" ]]; then
|
||||||
|
highest=$number
|
||||||
|
latest_feature=$dirname
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -n "$latest_feature" ]]; then
|
||||||
|
echo "$latest_feature"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "main" # Final fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if we have git available
|
||||||
|
has_git() {
|
||||||
|
git rev-parse --show-toplevel >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
check_feature_branch() {
|
check_feature_branch() {
|
||||||
local branch="$1"
|
local branch="$1"
|
||||||
|
local has_git_repo="$2"
|
||||||
|
|
||||||
|
# For non-git repos, we can't enforce branch naming but still provide output
|
||||||
|
if [[ "$has_git_repo" != "true" ]]; then
|
||||||
|
echo "[specify] Warning: Git repository not detected; skipped branch validation" >&2
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ ! "$branch" =~ ^[0-9]{3}- ]]; then
|
if [[ ! "$branch" =~ ^[0-9]{3}- ]]; then
|
||||||
echo "ERROR: Not on a feature branch. Current branch: $branch" >&2
|
echo "ERROR: Not on a feature branch. Current branch: $branch" >&2
|
||||||
echo "Feature branches should be named like: 001-feature-name" >&2
|
echo "Feature branches should be named like: 001-feature-name" >&2
|
||||||
return 1
|
return 1
|
||||||
fi; return 0
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
get_feature_dir() { echo "$1/specs/$2"; }
|
get_feature_dir() { echo "$1/specs/$2"; }
|
||||||
|
|
||||||
|
# Find feature directory by numeric prefix instead of exact branch match
|
||||||
|
# This allows multiple branches to work on the same spec (e.g., 004-fix-bug, 004-add-feature)
|
||||||
|
find_feature_dir_by_prefix() {
|
||||||
|
local repo_root="$1"
|
||||||
|
local branch_name="$2"
|
||||||
|
local specs_dir="$repo_root/specs"
|
||||||
|
|
||||||
|
# Extract numeric prefix from branch (e.g., "004" from "004-whatever")
|
||||||
|
if [[ ! "$branch_name" =~ ^([0-9]{3})- ]]; then
|
||||||
|
# If branch doesn't have numeric prefix, fall back to exact match
|
||||||
|
echo "$specs_dir/$branch_name"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local prefix="${BASH_REMATCH[1]}"
|
||||||
|
|
||||||
|
# Search for directories in specs/ that start with this prefix
|
||||||
|
local matches=()
|
||||||
|
if [[ -d "$specs_dir" ]]; then
|
||||||
|
for dir in "$specs_dir"/"$prefix"-*; do
|
||||||
|
if [[ -d "$dir" ]]; then
|
||||||
|
matches+=("$(basename "$dir")")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Handle results
|
||||||
|
if [[ ${#matches[@]} -eq 0 ]]; then
|
||||||
|
# No match found - return the branch name path (will fail later with clear error)
|
||||||
|
echo "$specs_dir/$branch_name"
|
||||||
|
elif [[ ${#matches[@]} -eq 1 ]]; then
|
||||||
|
# Exactly one match - perfect!
|
||||||
|
echo "$specs_dir/${matches[0]}"
|
||||||
|
else
|
||||||
|
# Multiple matches - this shouldn't happen with proper naming convention
|
||||||
|
echo "ERROR: Multiple spec directories found with prefix '$prefix': ${matches[*]}" >&2
|
||||||
|
echo "Please ensure only one spec directory exists per numeric prefix." >&2
|
||||||
|
echo "$specs_dir/$branch_name" # Return something to avoid breaking the script
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
get_feature_paths() {
|
get_feature_paths() {
|
||||||
local repo_root=$(get_repo_root)
|
local repo_root=$(get_repo_root)
|
||||||
local current_branch=$(get_current_branch)
|
local current_branch=$(get_current_branch)
|
||||||
local feature_dir=$(get_feature_dir "$repo_root" "$current_branch")
|
local has_git_repo="false"
|
||||||
|
|
||||||
|
if has_git; then
|
||||||
|
has_git_repo="true"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Use prefix-based lookup to support multiple branches per spec
|
||||||
|
local feature_dir=$(find_feature_dir_by_prefix "$repo_root" "$current_branch")
|
||||||
|
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
REPO_ROOT='$repo_root'
|
REPO_ROOT='$repo_root'
|
||||||
CURRENT_BRANCH='$current_branch'
|
CURRENT_BRANCH='$current_branch'
|
||||||
|
HAS_GIT='$has_git_repo'
|
||||||
FEATURE_DIR='$feature_dir'
|
FEATURE_DIR='$feature_dir'
|
||||||
FEATURE_SPEC='$feature_dir/spec.md'
|
FEATURE_SPEC='$feature_dir/spec.md'
|
||||||
IMPL_PLAN='$feature_dir/plan.md'
|
IMPL_PLAN='$feature_dir/plan.md'
|
||||||
@@ -35,3 +153,4 @@ EOF
|
|||||||
|
|
||||||
check_file() { [[ -f "$1" ]] && echo " ✓ $2" || echo " ✗ $2"; }
|
check_file() { [[ -f "$1" ]] && echo " ✓ $2" || echo " ✗ $2"; }
|
||||||
check_dir() { [[ -d "$1" && -n $(ls -A "$1" 2>/dev/null) ]] && echo " ✓ $2" || echo " ✗ $2"; }
|
check_dir() { [[ -d "$1" && -n $(ls -A "$1" 2>/dev/null) ]] && echo " ✓ $2" || echo " ✗ $2"; }
|
||||||
|
|
||||||
|
|||||||
@@ -1,58 +1,297 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# (Moved to scripts/bash/) Create a new feature with branch, directory structure, and template
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
JSON_MODE=false
|
JSON_MODE=false
|
||||||
|
SHORT_NAME=""
|
||||||
|
BRANCH_NUMBER=""
|
||||||
ARGS=()
|
ARGS=()
|
||||||
for arg in "$@"; do
|
i=1
|
||||||
|
while [ $i -le $# ]; do
|
||||||
|
arg="${!i}"
|
||||||
case "$arg" in
|
case "$arg" in
|
||||||
--json) JSON_MODE=true ;;
|
--json)
|
||||||
--help|-h) echo "Usage: $0 [--json] <feature_description>"; exit 0 ;;
|
JSON_MODE=true
|
||||||
*) ARGS+=("$arg") ;;
|
;;
|
||||||
|
--short-name)
|
||||||
|
if [ $((i + 1)) -gt $# ]; then
|
||||||
|
echo 'Error: --short-name requires a value' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
i=$((i + 1))
|
||||||
|
next_arg="${!i}"
|
||||||
|
# Check if the next argument is another option (starts with --)
|
||||||
|
if [[ "$next_arg" == --* ]]; then
|
||||||
|
echo 'Error: --short-name requires a value' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
SHORT_NAME="$next_arg"
|
||||||
|
;;
|
||||||
|
--number)
|
||||||
|
if [ $((i + 1)) -gt $# ]; then
|
||||||
|
echo 'Error: --number requires a value' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
i=$((i + 1))
|
||||||
|
next_arg="${!i}"
|
||||||
|
if [[ "$next_arg" == --* ]]; then
|
||||||
|
echo 'Error: --number requires a value' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
BRANCH_NUMBER="$next_arg"
|
||||||
|
;;
|
||||||
|
--help|-h)
|
||||||
|
echo "Usage: $0 [--json] [--short-name <name>] [--number N] <feature_description>"
|
||||||
|
echo ""
|
||||||
|
echo "Options:"
|
||||||
|
echo " --json Output in JSON format"
|
||||||
|
echo " --short-name <name> Provide a custom short name (2-4 words) for the branch"
|
||||||
|
echo " --number N Specify branch number manually (overrides auto-detection)"
|
||||||
|
echo " --help, -h Show this help message"
|
||||||
|
echo ""
|
||||||
|
echo "Examples:"
|
||||||
|
echo " $0 'Add user authentication system' --short-name 'user-auth'"
|
||||||
|
echo " $0 'Implement OAuth2 integration for API' --number 5"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
ARGS+=("$arg")
|
||||||
|
;;
|
||||||
esac
|
esac
|
||||||
|
i=$((i + 1))
|
||||||
done
|
done
|
||||||
|
|
||||||
FEATURE_DESCRIPTION="${ARGS[*]}"
|
FEATURE_DESCRIPTION="${ARGS[*]}"
|
||||||
if [ -z "$FEATURE_DESCRIPTION" ]; then
|
if [ -z "$FEATURE_DESCRIPTION" ]; then
|
||||||
echo "Usage: $0 [--json] <feature_description>" >&2
|
echo "Usage: $0 [--json] [--short-name <name>] [--number N] <feature_description>" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
REPO_ROOT=$(git rev-parse --show-toplevel)
|
# Function to find the repository root by searching for existing project markers
|
||||||
|
find_repo_root() {
|
||||||
|
local dir="$1"
|
||||||
|
while [ "$dir" != "/" ]; do
|
||||||
|
if [ -d "$dir/.git" ] || [ -d "$dir/.specify" ]; then
|
||||||
|
echo "$dir"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
dir="$(dirname "$dir")"
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to get highest number from specs directory
|
||||||
|
get_highest_from_specs() {
|
||||||
|
local specs_dir="$1"
|
||||||
|
local highest=0
|
||||||
|
|
||||||
|
if [ -d "$specs_dir" ]; then
|
||||||
|
for dir in "$specs_dir"/*; do
|
||||||
|
[ -d "$dir" ] || continue
|
||||||
|
dirname=$(basename "$dir")
|
||||||
|
number=$(echo "$dirname" | grep -o '^[0-9]\+' || echo "0")
|
||||||
|
number=$((10#$number))
|
||||||
|
if [ "$number" -gt "$highest" ]; then
|
||||||
|
highest=$number
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$highest"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to get highest number from git branches
|
||||||
|
get_highest_from_branches() {
|
||||||
|
local highest=0
|
||||||
|
|
||||||
|
# Get all branches (local and remote)
|
||||||
|
branches=$(git branch -a 2>/dev/null || echo "")
|
||||||
|
|
||||||
|
if [ -n "$branches" ]; then
|
||||||
|
while IFS= read -r branch; do
|
||||||
|
# Clean branch name: remove leading markers and remote prefixes
|
||||||
|
clean_branch=$(echo "$branch" | sed 's/^[* ]*//; s|^remotes/[^/]*/||')
|
||||||
|
|
||||||
|
# Extract feature number if branch matches pattern ###-*
|
||||||
|
if echo "$clean_branch" | grep -q '^[0-9]\{3\}-'; then
|
||||||
|
number=$(echo "$clean_branch" | grep -o '^[0-9]\{3\}' || echo "0")
|
||||||
|
number=$((10#$number))
|
||||||
|
if [ "$number" -gt "$highest" ]; then
|
||||||
|
highest=$number
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done <<< "$branches"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$highest"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to check existing branches (local and remote) and return next available number
|
||||||
|
check_existing_branches() {
|
||||||
|
local specs_dir="$1"
|
||||||
|
|
||||||
|
# Fetch all remotes to get latest branch info (suppress errors if no remotes)
|
||||||
|
git fetch --all --prune 2>/dev/null || true
|
||||||
|
|
||||||
|
# Get highest number from ALL branches (not just matching short name)
|
||||||
|
local highest_branch=$(get_highest_from_branches)
|
||||||
|
|
||||||
|
# Get highest number from ALL specs (not just matching short name)
|
||||||
|
local highest_spec=$(get_highest_from_specs "$specs_dir")
|
||||||
|
|
||||||
|
# Take the maximum of both
|
||||||
|
local max_num=$highest_branch
|
||||||
|
if [ "$highest_spec" -gt "$max_num" ]; then
|
||||||
|
max_num=$highest_spec
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Return next number
|
||||||
|
echo $((max_num + 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to clean and format a branch name
|
||||||
|
clean_branch_name() {
|
||||||
|
local name="$1"
|
||||||
|
echo "$name" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/-\+/-/g' | sed 's/^-//' | sed 's/-$//'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Resolve repository root. Prefer git information when available, but fall back
|
||||||
|
# to searching for repository markers so the workflow still functions in repositories that
|
||||||
|
# were initialised with --no-git.
|
||||||
|
SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
|
if git rev-parse --show-toplevel >/dev/null 2>&1; then
|
||||||
|
REPO_ROOT=$(git rev-parse --show-toplevel)
|
||||||
|
HAS_GIT=true
|
||||||
|
else
|
||||||
|
REPO_ROOT="$(find_repo_root "$SCRIPT_DIR")"
|
||||||
|
if [ -z "$REPO_ROOT" ]; then
|
||||||
|
echo "Error: Could not determine repository root. Please run this script from within the repository." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
HAS_GIT=false
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd "$REPO_ROOT"
|
||||||
|
|
||||||
SPECS_DIR="$REPO_ROOT/specs"
|
SPECS_DIR="$REPO_ROOT/specs"
|
||||||
mkdir -p "$SPECS_DIR"
|
mkdir -p "$SPECS_DIR"
|
||||||
|
|
||||||
HIGHEST=0
|
# Function to generate branch name with stop word filtering and length filtering
|
||||||
if [ -d "$SPECS_DIR" ]; then
|
generate_branch_name() {
|
||||||
for dir in "$SPECS_DIR"/*; do
|
local description="$1"
|
||||||
[ -d "$dir" ] || continue
|
|
||||||
dirname=$(basename "$dir")
|
# Common stop words to filter out
|
||||||
number=$(echo "$dirname" | grep -o '^[0-9]\+' || echo "0")
|
local stop_words="^(i|a|an|the|to|for|of|in|on|at|by|with|from|is|are|was|were|be|been|being|have|has|had|do|does|did|will|would|should|could|can|may|might|must|shall|this|that|these|those|my|your|our|their|want|need|add|get|set)$"
|
||||||
number=$((10#$number))
|
|
||||||
if [ "$number" -gt "$HIGHEST" ]; then HIGHEST=$number; fi
|
# Convert to lowercase and split into words
|
||||||
|
local clean_name=$(echo "$description" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/ /g')
|
||||||
|
|
||||||
|
# Filter words: remove stop words and words shorter than 3 chars (unless they're uppercase acronyms in original)
|
||||||
|
local meaningful_words=()
|
||||||
|
for word in $clean_name; do
|
||||||
|
# Skip empty words
|
||||||
|
[ -z "$word" ] && continue
|
||||||
|
|
||||||
|
# Keep words that are NOT stop words AND (length >= 3 OR are potential acronyms)
|
||||||
|
if ! echo "$word" | grep -qiE "$stop_words"; then
|
||||||
|
if [ ${#word} -ge 3 ]; then
|
||||||
|
meaningful_words+=("$word")
|
||||||
|
elif echo "$description" | grep -q "\b${word^^}\b"; then
|
||||||
|
# Keep short words if they appear as uppercase in original (likely acronyms)
|
||||||
|
meaningful_words+=("$word")
|
||||||
|
fi
|
||||||
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# If we have meaningful words, use first 3-4 of them
|
||||||
|
if [ ${#meaningful_words[@]} -gt 0 ]; then
|
||||||
|
local max_words=3
|
||||||
|
if [ ${#meaningful_words[@]} -eq 4 ]; then max_words=4; fi
|
||||||
|
|
||||||
|
local result=""
|
||||||
|
local count=0
|
||||||
|
for word in "${meaningful_words[@]}"; do
|
||||||
|
if [ $count -ge $max_words ]; then break; fi
|
||||||
|
if [ -n "$result" ]; then result="$result-"; fi
|
||||||
|
result="$result$word"
|
||||||
|
count=$((count + 1))
|
||||||
|
done
|
||||||
|
echo "$result"
|
||||||
|
else
|
||||||
|
# Fallback to original logic if no meaningful words found
|
||||||
|
local cleaned=$(clean_branch_name "$description")
|
||||||
|
echo "$cleaned" | tr '-' '\n' | grep -v '^$' | head -3 | tr '\n' '-' | sed 's/-$//'
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Generate branch name
|
||||||
|
if [ -n "$SHORT_NAME" ]; then
|
||||||
|
# Use provided short name, just clean it up
|
||||||
|
BRANCH_SUFFIX=$(clean_branch_name "$SHORT_NAME")
|
||||||
|
else
|
||||||
|
# Generate from description with smart filtering
|
||||||
|
BRANCH_SUFFIX=$(generate_branch_name "$FEATURE_DESCRIPTION")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
NEXT=$((HIGHEST + 1))
|
# Determine branch number
|
||||||
FEATURE_NUM=$(printf "%03d" "$NEXT")
|
if [ -z "$BRANCH_NUMBER" ]; then
|
||||||
|
if [ "$HAS_GIT" = true ]; then
|
||||||
|
# Check existing branches on remotes
|
||||||
|
BRANCH_NUMBER=$(check_existing_branches "$SPECS_DIR")
|
||||||
|
else
|
||||||
|
# Fall back to local directory check
|
||||||
|
HIGHEST=$(get_highest_from_specs "$SPECS_DIR")
|
||||||
|
BRANCH_NUMBER=$((HIGHEST + 1))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
BRANCH_NAME=$(echo "$FEATURE_DESCRIPTION" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/-\+/-/g' | sed 's/^-//' | sed 's/-$//')
|
# Force base-10 interpretation to prevent octal conversion (e.g., 010 → 8 in octal, but should be 10 in decimal)
|
||||||
WORDS=$(echo "$BRANCH_NAME" | tr '-' '\n' | grep -v '^$' | head -3 | tr '\n' '-' | sed 's/-$//')
|
FEATURE_NUM=$(printf "%03d" "$((10#$BRANCH_NUMBER))")
|
||||||
BRANCH_NAME="${FEATURE_NUM}-${WORDS}"
|
BRANCH_NAME="${FEATURE_NUM}-${BRANCH_SUFFIX}"
|
||||||
|
|
||||||
git checkout -b "$BRANCH_NAME"
|
# GitHub enforces a 244-byte limit on branch names
|
||||||
|
# Validate and truncate if necessary
|
||||||
|
MAX_BRANCH_LENGTH=244
|
||||||
|
if [ ${#BRANCH_NAME} -gt $MAX_BRANCH_LENGTH ]; then
|
||||||
|
# Calculate how much we need to trim from suffix
|
||||||
|
# Account for: feature number (3) + hyphen (1) = 4 chars
|
||||||
|
MAX_SUFFIX_LENGTH=$((MAX_BRANCH_LENGTH - 4))
|
||||||
|
|
||||||
|
# Truncate suffix at word boundary if possible
|
||||||
|
TRUNCATED_SUFFIX=$(echo "$BRANCH_SUFFIX" | cut -c1-$MAX_SUFFIX_LENGTH)
|
||||||
|
# Remove trailing hyphen if truncation created one
|
||||||
|
TRUNCATED_SUFFIX=$(echo "$TRUNCATED_SUFFIX" | sed 's/-$//')
|
||||||
|
|
||||||
|
ORIGINAL_BRANCH_NAME="$BRANCH_NAME"
|
||||||
|
BRANCH_NAME="${FEATURE_NUM}-${TRUNCATED_SUFFIX}"
|
||||||
|
|
||||||
|
>&2 echo "[specify] Warning: Branch name exceeded GitHub's 244-byte limit"
|
||||||
|
>&2 echo "[specify] Original: $ORIGINAL_BRANCH_NAME (${#ORIGINAL_BRANCH_NAME} bytes)"
|
||||||
|
>&2 echo "[specify] Truncated to: $BRANCH_NAME (${#BRANCH_NAME} bytes)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$HAS_GIT" = true ]; then
|
||||||
|
git checkout -b "$BRANCH_NAME"
|
||||||
|
else
|
||||||
|
>&2 echo "[specify] Warning: Git repository not detected; skipped branch creation for $BRANCH_NAME"
|
||||||
|
fi
|
||||||
|
|
||||||
FEATURE_DIR="$SPECS_DIR/$BRANCH_NAME"
|
FEATURE_DIR="$SPECS_DIR/$BRANCH_NAME"
|
||||||
mkdir -p "$FEATURE_DIR"
|
mkdir -p "$FEATURE_DIR"
|
||||||
|
|
||||||
TEMPLATE="$REPO_ROOT/templates/spec-template.md"
|
TEMPLATE="$REPO_ROOT/.specify/templates/spec-template.md"
|
||||||
SPEC_FILE="$FEATURE_DIR/spec.md"
|
SPEC_FILE="$FEATURE_DIR/spec.md"
|
||||||
if [ -f "$TEMPLATE" ]; then cp "$TEMPLATE" "$SPEC_FILE"; else touch "$SPEC_FILE"; fi
|
if [ -f "$TEMPLATE" ]; then cp "$TEMPLATE" "$SPEC_FILE"; else touch "$SPEC_FILE"; fi
|
||||||
|
|
||||||
|
# Set the SPECIFY_FEATURE environment variable for the current session
|
||||||
|
export SPECIFY_FEATURE="$BRANCH_NAME"
|
||||||
|
|
||||||
if $JSON_MODE; then
|
if $JSON_MODE; then
|
||||||
printf '{"BRANCH_NAME":"%s","SPEC_FILE":"%s","FEATURE_NUM":"%s"}\n' "$BRANCH_NAME" "$SPEC_FILE" "$FEATURE_NUM"
|
printf '{"BRANCH_NAME":"%s","SPEC_FILE":"%s","FEATURE_NUM":"%s"}\n' "$BRANCH_NAME" "$SPEC_FILE" "$FEATURE_NUM"
|
||||||
else
|
else
|
||||||
echo "BRANCH_NAME: $BRANCH_NAME"
|
echo "BRANCH_NAME: $BRANCH_NAME"
|
||||||
echo "SPEC_FILE: $SPEC_FILE"
|
echo "SPEC_FILE: $SPEC_FILE"
|
||||||
echo "FEATURE_NUM: $FEATURE_NUM"
|
echo "FEATURE_NUM: $FEATURE_NUM"
|
||||||
|
echo "SPECIFY_FEATURE environment variable set to: $BRANCH_NAME"
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -e
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
||||||
source "$SCRIPT_DIR/common.sh"
|
|
||||||
eval $(get_feature_paths)
|
|
||||||
check_feature_branch "$CURRENT_BRANCH" || exit 1
|
|
||||||
echo "REPO_ROOT: $REPO_ROOT"; echo "BRANCH: $CURRENT_BRANCH"; echo "FEATURE_DIR: $FEATURE_DIR"; echo "FEATURE_SPEC: $FEATURE_SPEC"; echo "IMPL_PLAN: $IMPL_PLAN"; echo "TASKS: $TASKS"
|
|
||||||
@@ -1,17 +1,61 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
|
# Parse command line arguments
|
||||||
JSON_MODE=false
|
JSON_MODE=false
|
||||||
for arg in "$@"; do case "$arg" in --json) JSON_MODE=true ;; --help|-h) echo "Usage: $0 [--json]"; exit 0 ;; esac; done
|
ARGS=()
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--json)
|
||||||
|
JSON_MODE=true
|
||||||
|
;;
|
||||||
|
--help|-h)
|
||||||
|
echo "Usage: $0 [--json]"
|
||||||
|
echo " --json Output results in JSON format"
|
||||||
|
echo " --help Show this help message"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
ARGS+=("$arg")
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Get script directory and load common functions
|
||||||
|
SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
source "$SCRIPT_DIR/common.sh"
|
source "$SCRIPT_DIR/common.sh"
|
||||||
|
|
||||||
|
# Get all paths and variables from common functions
|
||||||
eval $(get_feature_paths)
|
eval $(get_feature_paths)
|
||||||
check_feature_branch "$CURRENT_BRANCH" || exit 1
|
|
||||||
|
# Check if we're on a proper feature branch (only for git repos)
|
||||||
|
check_feature_branch "$CURRENT_BRANCH" "$HAS_GIT" || exit 1
|
||||||
|
|
||||||
|
# Ensure the feature directory exists
|
||||||
mkdir -p "$FEATURE_DIR"
|
mkdir -p "$FEATURE_DIR"
|
||||||
|
|
||||||
|
# Copy plan template if it exists
|
||||||
TEMPLATE="$REPO_ROOT/.specify/templates/plan-template.md"
|
TEMPLATE="$REPO_ROOT/.specify/templates/plan-template.md"
|
||||||
[[ -f "$TEMPLATE" ]] && cp "$TEMPLATE" "$IMPL_PLAN"
|
if [[ -f "$TEMPLATE" ]]; then
|
||||||
if $JSON_MODE; then
|
cp "$TEMPLATE" "$IMPL_PLAN"
|
||||||
printf '{"FEATURE_SPEC":"%s","IMPL_PLAN":"%s","SPECS_DIR":"%s","BRANCH":"%s"}\n' \
|
echo "Copied plan template to $IMPL_PLAN"
|
||||||
"$FEATURE_SPEC" "$IMPL_PLAN" "$FEATURE_DIR" "$CURRENT_BRANCH"
|
|
||||||
else
|
else
|
||||||
echo "FEATURE_SPEC: $FEATURE_SPEC"; echo "IMPL_PLAN: $IMPL_PLAN"; echo "SPECS_DIR: $FEATURE_DIR"; echo "BRANCH: $CURRENT_BRANCH"
|
echo "Warning: Plan template not found at $TEMPLATE"
|
||||||
|
# Create a basic plan file if template doesn't exist
|
||||||
|
touch "$IMPL_PLAN"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Output results
|
||||||
|
if $JSON_MODE; then
|
||||||
|
printf '{"FEATURE_SPEC":"%s","IMPL_PLAN":"%s","SPECS_DIR":"%s","BRANCH":"%s","HAS_GIT":"%s"}\n' \
|
||||||
|
"$FEATURE_SPEC" "$IMPL_PLAN" "$FEATURE_DIR" "$CURRENT_BRANCH" "$HAS_GIT"
|
||||||
|
else
|
||||||
|
echo "FEATURE_SPEC: $FEATURE_SPEC"
|
||||||
|
echo "IMPL_PLAN: $IMPL_PLAN"
|
||||||
|
echo "SPECS_DIR: $FEATURE_DIR"
|
||||||
|
echo "BRANCH: $CURRENT_BRANCH"
|
||||||
|
echo "HAS_GIT: $HAS_GIT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -1,66 +1,807 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Update agent context files with information from plan.md
|
||||||
|
#
|
||||||
|
# This script maintains AI agent context files by parsing feature specifications
|
||||||
|
# and updating agent-specific configuration files with project information.
|
||||||
|
#
|
||||||
|
# MAIN FUNCTIONS:
|
||||||
|
# 1. Environment Validation
|
||||||
|
# - Verifies git repository structure and branch information
|
||||||
|
# - Checks for required plan.md files and templates
|
||||||
|
# - Validates file permissions and accessibility
|
||||||
|
#
|
||||||
|
# 2. Plan Data Extraction
|
||||||
|
# - Parses plan.md files to extract project metadata
|
||||||
|
# - Identifies language/version, frameworks, databases, and project types
|
||||||
|
# - Handles missing or incomplete specification data gracefully
|
||||||
|
#
|
||||||
|
# 3. Agent File Management
|
||||||
|
# - Creates new agent context files from templates when needed
|
||||||
|
# - Updates existing agent files with new project information
|
||||||
|
# - Preserves manual additions and custom configurations
|
||||||
|
# - Supports multiple AI agent formats and directory structures
|
||||||
|
#
|
||||||
|
# 4. Content Generation
|
||||||
|
# - Generates language-specific build/test commands
|
||||||
|
# - Creates appropriate project directory structures
|
||||||
|
# - Updates technology stacks and recent changes sections
|
||||||
|
# - Maintains consistent formatting and timestamps
|
||||||
|
#
|
||||||
|
# 5. Multi-Agent Support
|
||||||
|
# - Handles agent-specific file paths and naming conventions
|
||||||
|
# - Supports: Claude, Gemini, Copilot, Cursor, Qwen, opencode, Codex, Windsurf, Kilo Code, Auggie CLI, Roo Code, CodeBuddy CLI, Qoder CLI, Amp, SHAI, Amazon Q Developer CLI, or Antigravity
|
||||||
|
# - Can update single agents or all existing agent files
|
||||||
|
# - Creates default Claude file if no agent files exist
|
||||||
|
#
|
||||||
|
# Usage: ./update-agent-context.sh [agent_type]
|
||||||
|
# Agent types: claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|roo|codebuddy|amp|shai|q|agy|bob|qoder
|
||||||
|
# Leave empty to update all existing agent files
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
REPO_ROOT=$(git rev-parse --show-toplevel)
|
|
||||||
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
# Enable strict error handling
|
||||||
FEATURE_DIR="$REPO_ROOT/specs/$CURRENT_BRANCH"
|
set -u
|
||||||
NEW_PLAN="$FEATURE_DIR/plan.md"
|
set -o pipefail
|
||||||
CLAUDE_FILE="$REPO_ROOT/CLAUDE.md"; GEMINI_FILE="$REPO_ROOT/GEMINI.md"; COPILOT_FILE="$REPO_ROOT/.github/copilot-instructions.md"; CURSOR_FILE="$REPO_ROOT/.cursor/rules/specify-rules.mdc"; QWEN_FILE="$REPO_ROOT/QWEN.md"; AGENTS_FILE="$REPO_ROOT/AGENTS.md"
|
|
||||||
AGENT_TYPE="$1"
|
#==============================================================================
|
||||||
[ -f "$NEW_PLAN" ] || { echo "ERROR: No plan.md found at $NEW_PLAN"; exit 1; }
|
# Configuration and Global Variables
|
||||||
echo "=== Updating agent context files for feature $CURRENT_BRANCH ==="
|
#==============================================================================
|
||||||
NEW_LANG=$(grep "^**Language/Version**: " "$NEW_PLAN" 2>/dev/null | head -1 | sed 's/^**Language\/Version**: //' | grep -v "NEEDS CLARIFICATION" || echo "")
|
|
||||||
NEW_FRAMEWORK=$(grep "^**Primary Dependencies**: " "$NEW_PLAN" 2>/dev/null | head -1 | sed 's/^**Primary Dependencies**: //' | grep -v "NEEDS CLARIFICATION" || echo "")
|
# Get script directory and load common functions
|
||||||
NEW_DB=$(grep "^**Storage**: " "$NEW_PLAN" 2>/dev/null | head -1 | sed 's/^**Storage**: //' | grep -v "N/A" | grep -v "NEEDS CLARIFICATION" || echo "")
|
SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
NEW_PROJECT_TYPE=$(grep "^**Project Type**: " "$NEW_PLAN" 2>/dev/null | head -1 | sed 's/^**Project Type**: //' || echo "")
|
source "$SCRIPT_DIR/common.sh"
|
||||||
update_agent_file() { local target_file="$1" agent_name="$2"; echo "Updating $agent_name context file: $target_file"; local temp_file=$(mktemp); if [ ! -f "$target_file" ]; then
|
|
||||||
echo "Creating new $agent_name context file..."; if [ -f "$REPO_ROOT/.specify/templates/agent-file-template.md" ]; then cp "$REPO_ROOT/.specify/templates/agent-file-template.md" "$temp_file"; else echo "ERROR: Template not found"; return 1; fi;
|
# Get all paths and variables from common functions
|
||||||
sed -i.bak "s/\[PROJECT NAME\]/$(basename $REPO_ROOT)/" "$temp_file"; sed -i.bak "s/\[DATE\]/$(date +%Y-%m-%d)/" "$temp_file"; sed -i.bak "s/\[EXTRACTED FROM ALL PLAN.MD FILES\]/- $NEW_LANG + $NEW_FRAMEWORK ($CURRENT_BRANCH)/" "$temp_file";
|
eval $(get_feature_paths)
|
||||||
if [[ "$NEW_PROJECT_TYPE" == *"web"* ]]; then sed -i.bak "s|\[ACTUAL STRUCTURE FROM PLANS\]|backend/\nfrontend/\ntests/|" "$temp_file"; else sed -i.bak "s|\[ACTUAL STRUCTURE FROM PLANS\]|src/\ntests/|" "$temp_file"; fi;
|
|
||||||
if [[ "$NEW_LANG" == *"Python"* ]]; then COMMANDS="cd src && pytest && ruff check ."; elif [[ "$NEW_LANG" == *"Rust"* ]]; then COMMANDS="cargo test && cargo clippy"; elif [[ "$NEW_LANG" == *"JavaScript"* ]] || [[ "$NEW_LANG" == *"TypeScript"* ]]; then COMMANDS="npm test && npm run lint"; else COMMANDS="# Add commands for $NEW_LANG"; fi; sed -i.bak "s|\[ONLY COMMANDS FOR ACTIVE TECHNOLOGIES\]|$COMMANDS|" "$temp_file";
|
NEW_PLAN="$IMPL_PLAN" # Alias for compatibility with existing code
|
||||||
sed -i.bak "s|\[LANGUAGE-SPECIFIC, ONLY FOR LANGUAGES IN USE\]|$NEW_LANG: Follow standard conventions|" "$temp_file"; sed -i.bak "s|\[LAST 3 FEATURES AND WHAT THEY ADDED\]|- $CURRENT_BRANCH: Added $NEW_LANG + $NEW_FRAMEWORK|" "$temp_file"; rm "$temp_file.bak";
|
AGENT_TYPE="${1:-}"
|
||||||
else
|
|
||||||
echo "Updating existing $agent_name context file..."; manual_start=$(grep -n "<!-- MANUAL ADDITIONS START -->" "$target_file" | cut -d: -f1); manual_end=$(grep -n "<!-- MANUAL ADDITIONS END -->" "$target_file" | cut -d: -f1); if [ -n "$manual_start" ] && [ -n "$manual_end" ]; then sed -n "${manual_start},${manual_end}p" "$target_file" > /tmp/manual_additions.txt; fi;
|
# Agent-specific file paths
|
||||||
python3 - "$target_file" <<'EOF'
|
CLAUDE_FILE="$REPO_ROOT/CLAUDE.md"
|
||||||
import re,sys,datetime
|
GEMINI_FILE="$REPO_ROOT/GEMINI.md"
|
||||||
target=sys.argv[1]
|
COPILOT_FILE="$REPO_ROOT/.github/agents/copilot-instructions.md"
|
||||||
with open(target) as f: content=f.read()
|
CURSOR_FILE="$REPO_ROOT/.cursor/rules/specify-rules.mdc"
|
||||||
NEW_LANG="'$NEW_LANG'";NEW_FRAMEWORK="'$NEW_FRAMEWORK'";CURRENT_BRANCH="'$CURRENT_BRANCH'";NEW_DB="'$NEW_DB'";NEW_PROJECT_TYPE="'$NEW_PROJECT_TYPE'"
|
QWEN_FILE="$REPO_ROOT/QWEN.md"
|
||||||
# Tech section
|
AGENTS_FILE="$REPO_ROOT/AGENTS.md"
|
||||||
m=re.search(r'## Active Technologies\n(.*?)\n\n',content, re.DOTALL)
|
WINDSURF_FILE="$REPO_ROOT/.windsurf/rules/specify-rules.md"
|
||||||
if m:
|
KILOCODE_FILE="$REPO_ROOT/.kilocode/rules/specify-rules.md"
|
||||||
existing=m.group(1)
|
AUGGIE_FILE="$REPO_ROOT/.augment/rules/specify-rules.md"
|
||||||
additions=[]
|
ROO_FILE="$REPO_ROOT/.roo/rules/specify-rules.md"
|
||||||
if '$NEW_LANG' and '$NEW_LANG' not in existing: additions.append(f"- $NEW_LANG + $NEW_FRAMEWORK ($CURRENT_BRANCH)")
|
CODEBUDDY_FILE="$REPO_ROOT/CODEBUDDY.md"
|
||||||
if '$NEW_DB' and '$NEW_DB' not in existing and '$NEW_DB'!='N/A': additions.append(f"- $NEW_DB ($CURRENT_BRANCH)")
|
QODER_FILE="$REPO_ROOT/QODER.md"
|
||||||
if additions:
|
AMP_FILE="$REPO_ROOT/AGENTS.md"
|
||||||
new_block=existing+"\n"+"\n".join(additions)
|
SHAI_FILE="$REPO_ROOT/SHAI.md"
|
||||||
content=content.replace(m.group(0),f"## Active Technologies\n{new_block}\n\n")
|
Q_FILE="$REPO_ROOT/AGENTS.md"
|
||||||
# Recent changes
|
AGY_FILE="$REPO_ROOT/.agent/rules/specify-rules.md"
|
||||||
m2=re.search(r'## Recent Changes\n(.*?)(\n\n|$)',content, re.DOTALL)
|
BOB_FILE="$REPO_ROOT/AGENTS.md"
|
||||||
if m2:
|
|
||||||
lines=[l for l in m2.group(1).strip().split('\n') if l]
|
# Template file
|
||||||
lines.insert(0,f"- $CURRENT_BRANCH: Added $NEW_LANG + $NEW_FRAMEWORK")
|
TEMPLATE_FILE="$REPO_ROOT/.specify/templates/agent-file-template.md"
|
||||||
lines=lines[:3]
|
|
||||||
content=re.sub(r'## Recent Changes\n.*?(\n\n|$)', '## Recent Changes\n'+"\n".join(lines)+'\n\n', content, flags=re.DOTALL)
|
# Global variables for parsed plan data
|
||||||
content=re.sub(r'Last updated: \d{4}-\d{2}-\d{2}', 'Last updated: '+datetime.datetime.now().strftime('%Y-%m-%d'), content)
|
NEW_LANG=""
|
||||||
open(target+'.tmp','w').write(content)
|
NEW_FRAMEWORK=""
|
||||||
EOF
|
NEW_DB=""
|
||||||
mv "$target_file.tmp" "$target_file"; if [ -f /tmp/manual_additions.txt ]; then sed -i.bak '/<!-- MANUAL ADDITIONS START -->/,/<!-- MANUAL ADDITIONS END -->/d' "$target_file"; cat /tmp/manual_additions.txt >> "$target_file"; rm /tmp/manual_additions.txt "$target_file.bak"; fi;
|
NEW_PROJECT_TYPE=""
|
||||||
fi; mv "$temp_file" "$target_file" 2>/dev/null || true; echo "✅ $agent_name context file updated successfully"; }
|
|
||||||
case "$AGENT_TYPE" in
|
#==============================================================================
|
||||||
claude) update_agent_file "$CLAUDE_FILE" "Claude Code" ;;
|
# Utility Functions
|
||||||
gemini) update_agent_file "$GEMINI_FILE" "Gemini CLI" ;;
|
#==============================================================================
|
||||||
copilot) update_agent_file "$COPILOT_FILE" "GitHub Copilot" ;;
|
|
||||||
cursor) update_agent_file "$CURSOR_FILE" "Cursor IDE" ;;
|
log_info() {
|
||||||
qwen) update_agent_file "$QWEN_FILE" "Qwen Code" ;;
|
echo "INFO: $1"
|
||||||
opencode) update_agent_file "$AGENTS_FILE" "opencode" ;;
|
}
|
||||||
"") [ -f "$CLAUDE_FILE" ] && update_agent_file "$CLAUDE_FILE" "Claude Code"; \
|
|
||||||
[ -f "$GEMINI_FILE" ] && update_agent_file "$GEMINI_FILE" "Gemini CLI"; \
|
log_success() {
|
||||||
[ -f "$COPILOT_FILE" ] && update_agent_file "$COPILOT_FILE" "GitHub Copilot"; \
|
echo "✓ $1"
|
||||||
[ -f "$CURSOR_FILE" ] && update_agent_file "$CURSOR_FILE" "Cursor IDE"; \
|
}
|
||||||
[ -f "$QWEN_FILE" ] && update_agent_file "$QWEN_FILE" "Qwen Code"; \
|
|
||||||
[ -f "$AGENTS_FILE" ] && update_agent_file "$AGENTS_FILE" "opencode"; \
|
log_error() {
|
||||||
if [ ! -f "$CLAUDE_FILE" ] && [ ! -f "$GEMINI_FILE" ] && [ ! -f "$COPILOT_FILE" ] && [ ! -f "$CURSOR_FILE" ] && [ ! -f "$QWEN_FILE" ] && [ ! -f "$AGENTS_FILE" ]; then update_agent_file "$CLAUDE_FILE" "Claude Code"; fi ;;
|
echo "ERROR: $1" >&2
|
||||||
*) echo "ERROR: Unknown agent type '$AGENT_TYPE' (expected claude|gemini|copilot|cursor|qwen|opencode)"; exit 1 ;;
|
}
|
||||||
esac
|
|
||||||
echo; echo "Summary of changes:"; [ -n "$NEW_LANG" ] && echo "- Added language: $NEW_LANG"; [ -n "$NEW_FRAMEWORK" ] && echo "- Added framework: $NEW_FRAMEWORK"; [ -n "$NEW_DB" ] && [ "$NEW_DB" != "N/A" ] && echo "- Added database: $NEW_DB"; echo; echo "Usage: $0 [claude|gemini|copilot|cursor|qwen|opencode]"
|
log_warning() {
|
||||||
|
echo "WARNING: $1" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
# Cleanup function for temporary files
|
||||||
|
cleanup() {
|
||||||
|
local exit_code=$?
|
||||||
|
rm -f /tmp/agent_update_*_$$
|
||||||
|
rm -f /tmp/manual_additions_$$
|
||||||
|
exit $exit_code
|
||||||
|
}
|
||||||
|
|
||||||
|
# Set up cleanup trap
|
||||||
|
trap cleanup EXIT INT TERM
|
||||||
|
|
||||||
|
#==============================================================================
|
||||||
|
# Validation Functions
|
||||||
|
#==============================================================================
|
||||||
|
|
||||||
|
validate_environment() {
|
||||||
|
# Check if we have a current branch/feature (git or non-git)
|
||||||
|
if [[ -z "$CURRENT_BRANCH" ]]; then
|
||||||
|
log_error "Unable to determine current feature"
|
||||||
|
if [[ "$HAS_GIT" == "true" ]]; then
|
||||||
|
log_info "Make sure you're on a feature branch"
|
||||||
|
else
|
||||||
|
log_info "Set SPECIFY_FEATURE environment variable or create a feature first"
|
||||||
|
fi
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if plan.md exists
|
||||||
|
if [[ ! -f "$NEW_PLAN" ]]; then
|
||||||
|
log_error "No plan.md found at $NEW_PLAN"
|
||||||
|
log_info "Make sure you're working on a feature with a corresponding spec directory"
|
||||||
|
if [[ "$HAS_GIT" != "true" ]]; then
|
||||||
|
log_info "Use: export SPECIFY_FEATURE=your-feature-name or create a new feature first"
|
||||||
|
fi
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if template exists (needed for new files)
|
||||||
|
if [[ ! -f "$TEMPLATE_FILE" ]]; then
|
||||||
|
log_warning "Template file not found at $TEMPLATE_FILE"
|
||||||
|
log_warning "Creating new agent files will fail"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
#==============================================================================
|
||||||
|
# Plan Parsing Functions
|
||||||
|
#==============================================================================
|
||||||
|
|
||||||
|
extract_plan_field() {
|
||||||
|
local field_pattern="$1"
|
||||||
|
local plan_file="$2"
|
||||||
|
|
||||||
|
grep "^\*\*${field_pattern}\*\*: " "$plan_file" 2>/dev/null | \
|
||||||
|
head -1 | \
|
||||||
|
sed "s|^\*\*${field_pattern}\*\*: ||" | \
|
||||||
|
sed 's/^[ \t]*//;s/[ \t]*$//' | \
|
||||||
|
grep -v "NEEDS CLARIFICATION" | \
|
||||||
|
grep -v "^N/A$" || echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_plan_data() {
|
||||||
|
local plan_file="$1"
|
||||||
|
|
||||||
|
if [[ ! -f "$plan_file" ]]; then
|
||||||
|
log_error "Plan file not found: $plan_file"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -r "$plan_file" ]]; then
|
||||||
|
log_error "Plan file is not readable: $plan_file"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "Parsing plan data from $plan_file"
|
||||||
|
|
||||||
|
NEW_LANG=$(extract_plan_field "Language/Version" "$plan_file")
|
||||||
|
NEW_FRAMEWORK=$(extract_plan_field "Primary Dependencies" "$plan_file")
|
||||||
|
NEW_DB=$(extract_plan_field "Storage" "$plan_file")
|
||||||
|
NEW_PROJECT_TYPE=$(extract_plan_field "Project Type" "$plan_file")
|
||||||
|
|
||||||
|
# Log what we found
|
||||||
|
if [[ -n "$NEW_LANG" ]]; then
|
||||||
|
log_info "Found language: $NEW_LANG"
|
||||||
|
else
|
||||||
|
log_warning "No language information found in plan"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "$NEW_FRAMEWORK" ]]; then
|
||||||
|
log_info "Found framework: $NEW_FRAMEWORK"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "$NEW_DB" ]] && [[ "$NEW_DB" != "N/A" ]]; then
|
||||||
|
log_info "Found database: $NEW_DB"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "$NEW_PROJECT_TYPE" ]]; then
|
||||||
|
log_info "Found project type: $NEW_PROJECT_TYPE"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
format_technology_stack() {
|
||||||
|
local lang="$1"
|
||||||
|
local framework="$2"
|
||||||
|
local parts=()
|
||||||
|
|
||||||
|
# Add non-empty parts
|
||||||
|
[[ -n "$lang" && "$lang" != "NEEDS CLARIFICATION" ]] && parts+=("$lang")
|
||||||
|
[[ -n "$framework" && "$framework" != "NEEDS CLARIFICATION" && "$framework" != "N/A" ]] && parts+=("$framework")
|
||||||
|
|
||||||
|
# Join with proper formatting
|
||||||
|
if [[ ${#parts[@]} -eq 0 ]]; then
|
||||||
|
echo ""
|
||||||
|
elif [[ ${#parts[@]} -eq 1 ]]; then
|
||||||
|
echo "${parts[0]}"
|
||||||
|
else
|
||||||
|
# Join multiple parts with " + "
|
||||||
|
local result="${parts[0]}"
|
||||||
|
for ((i=1; i<${#parts[@]}; i++)); do
|
||||||
|
result="$result + ${parts[i]}"
|
||||||
|
done
|
||||||
|
echo "$result"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
#==============================================================================
|
||||||
|
# Template and Content Generation Functions
|
||||||
|
#==============================================================================
|
||||||
|
|
||||||
|
get_project_structure() {
|
||||||
|
local project_type="$1"
|
||||||
|
|
||||||
|
if [[ "$project_type" == *"web"* ]]; then
|
||||||
|
echo "backend/\\nfrontend/\\ntests/"
|
||||||
|
else
|
||||||
|
echo "src/\\ntests/"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
get_commands_for_language() {
|
||||||
|
local lang="$1"
|
||||||
|
|
||||||
|
case "$lang" in
|
||||||
|
*"Python"*)
|
||||||
|
echo "cd src && pytest && ruff check ."
|
||||||
|
;;
|
||||||
|
*"Rust"*)
|
||||||
|
echo "cargo test && cargo clippy"
|
||||||
|
;;
|
||||||
|
*"JavaScript"*|*"TypeScript"*)
|
||||||
|
echo "npm test \\&\\& npm run lint"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "# Add commands for $lang"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
get_language_conventions() {
|
||||||
|
local lang="$1"
|
||||||
|
echo "$lang: Follow standard conventions"
|
||||||
|
}
|
||||||
|
|
||||||
|
create_new_agent_file() {
|
||||||
|
local target_file="$1"
|
||||||
|
local temp_file="$2"
|
||||||
|
local project_name="$3"
|
||||||
|
local current_date="$4"
|
||||||
|
|
||||||
|
if [[ ! -f "$TEMPLATE_FILE" ]]; then
|
||||||
|
log_error "Template not found at $TEMPLATE_FILE"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -r "$TEMPLATE_FILE" ]]; then
|
||||||
|
log_error "Template file is not readable: $TEMPLATE_FILE"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "Creating new agent context file from template..."
|
||||||
|
|
||||||
|
if ! cp "$TEMPLATE_FILE" "$temp_file"; then
|
||||||
|
log_error "Failed to copy template file"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Replace template placeholders
|
||||||
|
local project_structure
|
||||||
|
project_structure=$(get_project_structure "$NEW_PROJECT_TYPE")
|
||||||
|
|
||||||
|
local commands
|
||||||
|
commands=$(get_commands_for_language "$NEW_LANG")
|
||||||
|
|
||||||
|
local language_conventions
|
||||||
|
language_conventions=$(get_language_conventions "$NEW_LANG")
|
||||||
|
|
||||||
|
# Perform substitutions with error checking using safer approach
|
||||||
|
# Escape special characters for sed by using a different delimiter or escaping
|
||||||
|
local escaped_lang=$(printf '%s\n' "$NEW_LANG" | sed 's/[\[\.*^$()+{}|]/\\&/g')
|
||||||
|
local escaped_framework=$(printf '%s\n' "$NEW_FRAMEWORK" | sed 's/[\[\.*^$()+{}|]/\\&/g')
|
||||||
|
local escaped_branch=$(printf '%s\n' "$CURRENT_BRANCH" | sed 's/[\[\.*^$()+{}|]/\\&/g')
|
||||||
|
|
||||||
|
# Build technology stack and recent change strings conditionally
|
||||||
|
local tech_stack
|
||||||
|
if [[ -n "$escaped_lang" && -n "$escaped_framework" ]]; then
|
||||||
|
tech_stack="- $escaped_lang + $escaped_framework ($escaped_branch)"
|
||||||
|
elif [[ -n "$escaped_lang" ]]; then
|
||||||
|
tech_stack="- $escaped_lang ($escaped_branch)"
|
||||||
|
elif [[ -n "$escaped_framework" ]]; then
|
||||||
|
tech_stack="- $escaped_framework ($escaped_branch)"
|
||||||
|
else
|
||||||
|
tech_stack="- ($escaped_branch)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
local recent_change
|
||||||
|
if [[ -n "$escaped_lang" && -n "$escaped_framework" ]]; then
|
||||||
|
recent_change="- $escaped_branch: Added $escaped_lang + $escaped_framework"
|
||||||
|
elif [[ -n "$escaped_lang" ]]; then
|
||||||
|
recent_change="- $escaped_branch: Added $escaped_lang"
|
||||||
|
elif [[ -n "$escaped_framework" ]]; then
|
||||||
|
recent_change="- $escaped_branch: Added $escaped_framework"
|
||||||
|
else
|
||||||
|
recent_change="- $escaped_branch: Added"
|
||||||
|
fi
|
||||||
|
|
||||||
|
local substitutions=(
|
||||||
|
"s|\[PROJECT NAME\]|$project_name|"
|
||||||
|
"s|\[DATE\]|$current_date|"
|
||||||
|
"s|\[EXTRACTED FROM ALL PLAN.MD FILES\]|$tech_stack|"
|
||||||
|
"s|\[ACTUAL STRUCTURE FROM PLANS\]|$project_structure|g"
|
||||||
|
"s|\[ONLY COMMANDS FOR ACTIVE TECHNOLOGIES\]|$commands|"
|
||||||
|
"s|\[LANGUAGE-SPECIFIC, ONLY FOR LANGUAGES IN USE\]|$language_conventions|"
|
||||||
|
"s|\[LAST 3 FEATURES AND WHAT THEY ADDED\]|$recent_change|"
|
||||||
|
)
|
||||||
|
|
||||||
|
for substitution in "${substitutions[@]}"; do
|
||||||
|
if ! sed -i.bak -e "$substitution" "$temp_file"; then
|
||||||
|
log_error "Failed to perform substitution: $substitution"
|
||||||
|
rm -f "$temp_file" "$temp_file.bak"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Convert \n sequences to actual newlines
|
||||||
|
newline=$(printf '\n')
|
||||||
|
sed -i.bak2 "s/\\\\n/${newline}/g" "$temp_file"
|
||||||
|
|
||||||
|
# Clean up backup files
|
||||||
|
rm -f "$temp_file.bak" "$temp_file.bak2"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
update_existing_agent_file() {
|
||||||
|
local target_file="$1"
|
||||||
|
local current_date="$2"
|
||||||
|
|
||||||
|
log_info "Updating existing agent context file..."
|
||||||
|
|
||||||
|
# Use a single temporary file for atomic update
|
||||||
|
local temp_file
|
||||||
|
temp_file=$(mktemp) || {
|
||||||
|
log_error "Failed to create temporary file"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Process the file in one pass
|
||||||
|
local tech_stack=$(format_technology_stack "$NEW_LANG" "$NEW_FRAMEWORK")
|
||||||
|
local new_tech_entries=()
|
||||||
|
local new_change_entry=""
|
||||||
|
|
||||||
|
# Prepare new technology entries
|
||||||
|
if [[ -n "$tech_stack" ]] && ! grep -q "$tech_stack" "$target_file"; then
|
||||||
|
new_tech_entries+=("- $tech_stack ($CURRENT_BRANCH)")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "$NEW_DB" ]] && [[ "$NEW_DB" != "N/A" ]] && [[ "$NEW_DB" != "NEEDS CLARIFICATION" ]] && ! grep -q "$NEW_DB" "$target_file"; then
|
||||||
|
new_tech_entries+=("- $NEW_DB ($CURRENT_BRANCH)")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Prepare new change entry
|
||||||
|
if [[ -n "$tech_stack" ]]; then
|
||||||
|
new_change_entry="- $CURRENT_BRANCH: Added $tech_stack"
|
||||||
|
elif [[ -n "$NEW_DB" ]] && [[ "$NEW_DB" != "N/A" ]] && [[ "$NEW_DB" != "NEEDS CLARIFICATION" ]]; then
|
||||||
|
new_change_entry="- $CURRENT_BRANCH: Added $NEW_DB"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if sections exist in the file
|
||||||
|
local has_active_technologies=0
|
||||||
|
local has_recent_changes=0
|
||||||
|
|
||||||
|
if grep -q "^## Active Technologies" "$target_file" 2>/dev/null; then
|
||||||
|
has_active_technologies=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if grep -q "^## Recent Changes" "$target_file" 2>/dev/null; then
|
||||||
|
has_recent_changes=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Process file line by line
|
||||||
|
local in_tech_section=false
|
||||||
|
local in_changes_section=false
|
||||||
|
local tech_entries_added=false
|
||||||
|
local changes_entries_added=false
|
||||||
|
local existing_changes_count=0
|
||||||
|
local file_ended=false
|
||||||
|
|
||||||
|
while IFS= read -r line || [[ -n "$line" ]]; do
|
||||||
|
# Handle Active Technologies section
|
||||||
|
if [[ "$line" == "## Active Technologies" ]]; then
|
||||||
|
echo "$line" >> "$temp_file"
|
||||||
|
in_tech_section=true
|
||||||
|
continue
|
||||||
|
elif [[ $in_tech_section == true ]] && [[ "$line" =~ ^##[[:space:]] ]]; then
|
||||||
|
# Add new tech entries before closing the section
|
||||||
|
if [[ $tech_entries_added == false ]] && [[ ${#new_tech_entries[@]} -gt 0 ]]; then
|
||||||
|
printf '%s\n' "${new_tech_entries[@]}" >> "$temp_file"
|
||||||
|
tech_entries_added=true
|
||||||
|
fi
|
||||||
|
echo "$line" >> "$temp_file"
|
||||||
|
in_tech_section=false
|
||||||
|
continue
|
||||||
|
elif [[ $in_tech_section == true ]] && [[ -z "$line" ]]; then
|
||||||
|
# Add new tech entries before empty line in tech section
|
||||||
|
if [[ $tech_entries_added == false ]] && [[ ${#new_tech_entries[@]} -gt 0 ]]; then
|
||||||
|
printf '%s\n' "${new_tech_entries[@]}" >> "$temp_file"
|
||||||
|
tech_entries_added=true
|
||||||
|
fi
|
||||||
|
echo "$line" >> "$temp_file"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Handle Recent Changes section
|
||||||
|
if [[ "$line" == "## Recent Changes" ]]; then
|
||||||
|
echo "$line" >> "$temp_file"
|
||||||
|
# Add new change entry right after the heading
|
||||||
|
if [[ -n "$new_change_entry" ]]; then
|
||||||
|
echo "$new_change_entry" >> "$temp_file"
|
||||||
|
fi
|
||||||
|
in_changes_section=true
|
||||||
|
changes_entries_added=true
|
||||||
|
continue
|
||||||
|
elif [[ $in_changes_section == true ]] && [[ "$line" =~ ^##[[:space:]] ]]; then
|
||||||
|
echo "$line" >> "$temp_file"
|
||||||
|
in_changes_section=false
|
||||||
|
continue
|
||||||
|
elif [[ $in_changes_section == true ]] && [[ "$line" == "- "* ]]; then
|
||||||
|
# Keep only first 2 existing changes
|
||||||
|
if [[ $existing_changes_count -lt 2 ]]; then
|
||||||
|
echo "$line" >> "$temp_file"
|
||||||
|
((existing_changes_count++))
|
||||||
|
fi
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Update timestamp
|
||||||
|
if [[ "$line" =~ \*\*Last\ updated\*\*:.*[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] ]]; then
|
||||||
|
echo "$line" | sed "s/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/$current_date/" >> "$temp_file"
|
||||||
|
else
|
||||||
|
echo "$line" >> "$temp_file"
|
||||||
|
fi
|
||||||
|
done < "$target_file"
|
||||||
|
|
||||||
|
# Post-loop check: if we're still in the Active Technologies section and haven't added new entries
|
||||||
|
if [[ $in_tech_section == true ]] && [[ $tech_entries_added == false ]] && [[ ${#new_tech_entries[@]} -gt 0 ]]; then
|
||||||
|
printf '%s\n' "${new_tech_entries[@]}" >> "$temp_file"
|
||||||
|
tech_entries_added=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If sections don't exist, add them at the end of the file
|
||||||
|
if [[ $has_active_technologies -eq 0 ]] && [[ ${#new_tech_entries[@]} -gt 0 ]]; then
|
||||||
|
echo "" >> "$temp_file"
|
||||||
|
echo "## Active Technologies" >> "$temp_file"
|
||||||
|
printf '%s\n' "${new_tech_entries[@]}" >> "$temp_file"
|
||||||
|
tech_entries_added=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $has_recent_changes -eq 0 ]] && [[ -n "$new_change_entry" ]]; then
|
||||||
|
echo "" >> "$temp_file"
|
||||||
|
echo "## Recent Changes" >> "$temp_file"
|
||||||
|
echo "$new_change_entry" >> "$temp_file"
|
||||||
|
changes_entries_added=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Move temp file to target atomically
|
||||||
|
if ! mv "$temp_file" "$target_file"; then
|
||||||
|
log_error "Failed to update target file"
|
||||||
|
rm -f "$temp_file"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
#==============================================================================
|
||||||
|
# Main Agent File Update Function
|
||||||
|
#==============================================================================
|
||||||
|
|
||||||
|
update_agent_file() {
|
||||||
|
local target_file="$1"
|
||||||
|
local agent_name="$2"
|
||||||
|
|
||||||
|
if [[ -z "$target_file" ]] || [[ -z "$agent_name" ]]; then
|
||||||
|
log_error "update_agent_file requires target_file and agent_name parameters"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "Updating $agent_name context file: $target_file"
|
||||||
|
|
||||||
|
local project_name
|
||||||
|
project_name=$(basename "$REPO_ROOT")
|
||||||
|
local current_date
|
||||||
|
current_date=$(date +%Y-%m-%d)
|
||||||
|
|
||||||
|
# Create directory if it doesn't exist
|
||||||
|
local target_dir
|
||||||
|
target_dir=$(dirname "$target_file")
|
||||||
|
if [[ ! -d "$target_dir" ]]; then
|
||||||
|
if ! mkdir -p "$target_dir"; then
|
||||||
|
log_error "Failed to create directory: $target_dir"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -f "$target_file" ]]; then
|
||||||
|
# Create new file from template
|
||||||
|
local temp_file
|
||||||
|
temp_file=$(mktemp) || {
|
||||||
|
log_error "Failed to create temporary file"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if create_new_agent_file "$target_file" "$temp_file" "$project_name" "$current_date"; then
|
||||||
|
if mv "$temp_file" "$target_file"; then
|
||||||
|
log_success "Created new $agent_name context file"
|
||||||
|
else
|
||||||
|
log_error "Failed to move temporary file to $target_file"
|
||||||
|
rm -f "$temp_file"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log_error "Failed to create new agent file"
|
||||||
|
rm -f "$temp_file"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Update existing file
|
||||||
|
if [[ ! -r "$target_file" ]]; then
|
||||||
|
log_error "Cannot read existing file: $target_file"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -w "$target_file" ]]; then
|
||||||
|
log_error "Cannot write to existing file: $target_file"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if update_existing_agent_file "$target_file" "$current_date"; then
|
||||||
|
log_success "Updated existing $agent_name context file"
|
||||||
|
else
|
||||||
|
log_error "Failed to update existing agent file"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
#==============================================================================
|
||||||
|
# Agent Selection and Processing
|
||||||
|
#==============================================================================
|
||||||
|
|
||||||
|
update_specific_agent() {
|
||||||
|
local agent_type="$1"
|
||||||
|
|
||||||
|
case "$agent_type" in
|
||||||
|
claude)
|
||||||
|
update_agent_file "$CLAUDE_FILE" "Claude Code"
|
||||||
|
;;
|
||||||
|
gemini)
|
||||||
|
update_agent_file "$GEMINI_FILE" "Gemini CLI"
|
||||||
|
;;
|
||||||
|
copilot)
|
||||||
|
update_agent_file "$COPILOT_FILE" "GitHub Copilot"
|
||||||
|
;;
|
||||||
|
cursor-agent)
|
||||||
|
update_agent_file "$CURSOR_FILE" "Cursor IDE"
|
||||||
|
;;
|
||||||
|
qwen)
|
||||||
|
update_agent_file "$QWEN_FILE" "Qwen Code"
|
||||||
|
;;
|
||||||
|
opencode)
|
||||||
|
update_agent_file "$AGENTS_FILE" "opencode"
|
||||||
|
;;
|
||||||
|
codex)
|
||||||
|
update_agent_file "$AGENTS_FILE" "Codex CLI"
|
||||||
|
;;
|
||||||
|
windsurf)
|
||||||
|
update_agent_file "$WINDSURF_FILE" "Windsurf"
|
||||||
|
;;
|
||||||
|
kilocode)
|
||||||
|
update_agent_file "$KILOCODE_FILE" "Kilo Code"
|
||||||
|
;;
|
||||||
|
auggie)
|
||||||
|
update_agent_file "$AUGGIE_FILE" "Auggie CLI"
|
||||||
|
;;
|
||||||
|
roo)
|
||||||
|
update_agent_file "$ROO_FILE" "Roo Code"
|
||||||
|
;;
|
||||||
|
codebuddy)
|
||||||
|
update_agent_file "$CODEBUDDY_FILE" "CodeBuddy CLI"
|
||||||
|
;;
|
||||||
|
qoder)
|
||||||
|
update_agent_file "$QODER_FILE" "Qoder CLI"
|
||||||
|
;;
|
||||||
|
amp)
|
||||||
|
update_agent_file "$AMP_FILE" "Amp"
|
||||||
|
;;
|
||||||
|
shai)
|
||||||
|
update_agent_file "$SHAI_FILE" "SHAI"
|
||||||
|
;;
|
||||||
|
q)
|
||||||
|
update_agent_file "$Q_FILE" "Amazon Q Developer CLI"
|
||||||
|
;;
|
||||||
|
agy)
|
||||||
|
update_agent_file "$AGY_FILE" "Antigravity"
|
||||||
|
;;
|
||||||
|
bob)
|
||||||
|
update_agent_file "$BOB_FILE" "IBM Bob"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
log_error "Unknown agent type '$agent_type'"
|
||||||
|
log_error "Expected: claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|roo|amp|shai|q|agy|bob|qoder"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
update_all_existing_agents() {
|
||||||
|
local found_agent=false
|
||||||
|
|
||||||
|
# Check each possible agent file and update if it exists
|
||||||
|
if [[ -f "$CLAUDE_FILE" ]]; then
|
||||||
|
update_agent_file "$CLAUDE_FILE" "Claude Code"
|
||||||
|
found_agent=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -f "$GEMINI_FILE" ]]; then
|
||||||
|
update_agent_file "$GEMINI_FILE" "Gemini CLI"
|
||||||
|
found_agent=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -f "$COPILOT_FILE" ]]; then
|
||||||
|
update_agent_file "$COPILOT_FILE" "GitHub Copilot"
|
||||||
|
found_agent=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -f "$CURSOR_FILE" ]]; then
|
||||||
|
update_agent_file "$CURSOR_FILE" "Cursor IDE"
|
||||||
|
found_agent=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -f "$QWEN_FILE" ]]; then
|
||||||
|
update_agent_file "$QWEN_FILE" "Qwen Code"
|
||||||
|
found_agent=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -f "$AGENTS_FILE" ]]; then
|
||||||
|
update_agent_file "$AGENTS_FILE" "Codex/opencode"
|
||||||
|
found_agent=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -f "$WINDSURF_FILE" ]]; then
|
||||||
|
update_agent_file "$WINDSURF_FILE" "Windsurf"
|
||||||
|
found_agent=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -f "$KILOCODE_FILE" ]]; then
|
||||||
|
update_agent_file "$KILOCODE_FILE" "Kilo Code"
|
||||||
|
found_agent=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -f "$AUGGIE_FILE" ]]; then
|
||||||
|
update_agent_file "$AUGGIE_FILE" "Auggie CLI"
|
||||||
|
found_agent=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -f "$ROO_FILE" ]]; then
|
||||||
|
update_agent_file "$ROO_FILE" "Roo Code"
|
||||||
|
found_agent=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -f "$CODEBUDDY_FILE" ]]; then
|
||||||
|
update_agent_file "$CODEBUDDY_FILE" "CodeBuddy CLI"
|
||||||
|
found_agent=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -f "$SHAI_FILE" ]]; then
|
||||||
|
update_agent_file "$SHAI_FILE" "SHAI"
|
||||||
|
found_agent=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -f "$QODER_FILE" ]]; then
|
||||||
|
update_agent_file "$QODER_FILE" "Qoder CLI"
|
||||||
|
found_agent=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -f "$Q_FILE" ]]; then
|
||||||
|
update_agent_file "$Q_FILE" "Amazon Q Developer CLI"
|
||||||
|
found_agent=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -f "$AGY_FILE" ]]; then
|
||||||
|
update_agent_file "$AGY_FILE" "Antigravity"
|
||||||
|
found_agent=true
|
||||||
|
fi
|
||||||
|
if [[ -f "$BOB_FILE" ]]; then
|
||||||
|
update_agent_file "$BOB_FILE" "IBM Bob"
|
||||||
|
found_agent=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If no agent files exist, create a default Claude file
|
||||||
|
if [[ "$found_agent" == false ]]; then
|
||||||
|
log_info "No existing agent files found, creating default Claude file..."
|
||||||
|
update_agent_file "$CLAUDE_FILE" "Claude Code"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
print_summary() {
|
||||||
|
echo
|
||||||
|
log_info "Summary of changes:"
|
||||||
|
|
||||||
|
if [[ -n "$NEW_LANG" ]]; then
|
||||||
|
echo " - Added language: $NEW_LANG"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "$NEW_FRAMEWORK" ]]; then
|
||||||
|
echo " - Added framework: $NEW_FRAMEWORK"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "$NEW_DB" ]] && [[ "$NEW_DB" != "N/A" ]]; then
|
||||||
|
echo " - Added database: $NEW_DB"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
|
||||||
|
log_info "Usage: $0 [claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|roo|codebuddy|amp|shai|q|agy|bob|qoder]"
|
||||||
|
}
|
||||||
|
|
||||||
|
#==============================================================================
|
||||||
|
# Main Execution
|
||||||
|
#==============================================================================
|
||||||
|
|
||||||
|
main() {
|
||||||
|
# Validate environment before proceeding
|
||||||
|
validate_environment
|
||||||
|
|
||||||
|
log_info "=== Updating agent context files for feature $CURRENT_BRANCH ==="
|
||||||
|
|
||||||
|
# Parse the plan file to extract project information
|
||||||
|
if ! parse_plan_data "$NEW_PLAN"; then
|
||||||
|
log_error "Failed to parse plan data"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Process based on agent type argument
|
||||||
|
local success=true
|
||||||
|
|
||||||
|
if [[ -z "$AGENT_TYPE" ]]; then
|
||||||
|
# No specific agent provided - update all existing agent files
|
||||||
|
log_info "No agent specified, updating all existing agent files..."
|
||||||
|
if ! update_all_existing_agents; then
|
||||||
|
success=false
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Specific agent provided - update only that agent
|
||||||
|
log_info "Updating specific agent: $AGENT_TYPE"
|
||||||
|
if ! update_specific_agent "$AGENT_TYPE"; then
|
||||||
|
success=false
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Print summary
|
||||||
|
print_summary
|
||||||
|
|
||||||
|
if [[ "$success" == true ]]; then
|
||||||
|
log_success "Agent context update completed successfully"
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
log_error "Agent context update completed with errors"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Execute main function if script is run directly
|
||||||
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||||
|
main "$@"
|
||||||
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
#!/usr/bin/env pwsh
|
|
||||||
[CmdletBinding()]
|
|
||||||
param([switch]$Json)
|
|
||||||
$ErrorActionPreference = 'Stop'
|
|
||||||
. "$PSScriptRoot/common.ps1"
|
|
||||||
|
|
||||||
$paths = Get-FeaturePathsEnv
|
|
||||||
if (-not (Test-FeatureBranch -Branch $paths.CURRENT_BRANCH)) { exit 1 }
|
|
||||||
|
|
||||||
if (-not (Test-Path $paths.FEATURE_DIR -PathType Container)) {
|
|
||||||
Write-Output "ERROR: Feature directory not found: $($paths.FEATURE_DIR)"
|
|
||||||
Write-Output "Run /specify first to create the feature structure."
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
if (-not (Test-Path $paths.IMPL_PLAN -PathType Leaf)) {
|
|
||||||
Write-Output "ERROR: plan.md not found in $($paths.FEATURE_DIR)"
|
|
||||||
Write-Output "Run /plan first to create the plan."
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
if (-not (Test-Path $paths.TASKS -PathType Leaf)) {
|
|
||||||
Write-Output "ERROR: tasks.md not found in $($paths.FEATURE_DIR)"
|
|
||||||
Write-Output "Run /tasks first to create the task list."
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($Json) {
|
|
||||||
$docs = @()
|
|
||||||
if (Test-Path $paths.RESEARCH) { $docs += 'research.md' }
|
|
||||||
if (Test-Path $paths.DATA_MODEL) { $docs += 'data-model.md' }
|
|
||||||
if ((Test-Path $paths.CONTRACTS_DIR) -and (Get-ChildItem -Path $paths.CONTRACTS_DIR -ErrorAction SilentlyContinue | Select-Object -First 1)) { $docs += 'contracts/' }
|
|
||||||
if (Test-Path $paths.QUICKSTART) { $docs += 'quickstart.md' }
|
|
||||||
if (Test-Path $paths.TASKS) { $docs += 'tasks.md' }
|
|
||||||
[PSCustomObject]@{ FEATURE_DIR=$paths.FEATURE_DIR; AVAILABLE_DOCS=$docs } | ConvertTo-Json -Compress
|
|
||||||
} else {
|
|
||||||
Write-Output "FEATURE_DIR:$($paths.FEATURE_DIR)"
|
|
||||||
Write-Output "AVAILABLE_DOCS:"
|
|
||||||
Test-FileExists -Path $paths.RESEARCH -Description 'research.md' | Out-Null
|
|
||||||
Test-FileExists -Path $paths.DATA_MODEL -Description 'data-model.md' | Out-Null
|
|
||||||
Test-DirHasFiles -Path $paths.CONTRACTS_DIR -Description 'contracts/' | Out-Null
|
|
||||||
Test-FileExists -Path $paths.QUICKSTART -Description 'quickstart.md' | Out-Null
|
|
||||||
Test-FileExists -Path $paths.TASKS -Description 'tasks.md' | Out-Null
|
|
||||||
}
|
|
||||||
148
scripts/powershell/check-prerequisites.ps1
Normal file
148
scripts/powershell/check-prerequisites.ps1
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
#!/usr/bin/env pwsh
|
||||||
|
|
||||||
|
# Consolidated prerequisite checking script (PowerShell)
|
||||||
|
#
|
||||||
|
# This script provides unified prerequisite checking for Spec-Driven Development workflow.
|
||||||
|
# It replaces the functionality previously spread across multiple scripts.
|
||||||
|
#
|
||||||
|
# Usage: ./check-prerequisites.ps1 [OPTIONS]
|
||||||
|
#
|
||||||
|
# OPTIONS:
|
||||||
|
# -Json Output in JSON format
|
||||||
|
# -RequireTasks Require tasks.md to exist (for implementation phase)
|
||||||
|
# -IncludeTasks Include tasks.md in AVAILABLE_DOCS list
|
||||||
|
# -PathsOnly Only output path variables (no validation)
|
||||||
|
# -Help, -h Show help message
|
||||||
|
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[switch]$Json,
|
||||||
|
[switch]$RequireTasks,
|
||||||
|
[switch]$IncludeTasks,
|
||||||
|
[switch]$PathsOnly,
|
||||||
|
[switch]$Help
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
|
# Show help if requested
|
||||||
|
if ($Help) {
|
||||||
|
Write-Output @"
|
||||||
|
Usage: check-prerequisites.ps1 [OPTIONS]
|
||||||
|
|
||||||
|
Consolidated prerequisite checking for Spec-Driven Development workflow.
|
||||||
|
|
||||||
|
OPTIONS:
|
||||||
|
-Json Output in JSON format
|
||||||
|
-RequireTasks Require tasks.md to exist (for implementation phase)
|
||||||
|
-IncludeTasks Include tasks.md in AVAILABLE_DOCS list
|
||||||
|
-PathsOnly Only output path variables (no prerequisite validation)
|
||||||
|
-Help, -h Show this help message
|
||||||
|
|
||||||
|
EXAMPLES:
|
||||||
|
# Check task prerequisites (plan.md required)
|
||||||
|
.\check-prerequisites.ps1 -Json
|
||||||
|
|
||||||
|
# Check implementation prerequisites (plan.md + tasks.md required)
|
||||||
|
.\check-prerequisites.ps1 -Json -RequireTasks -IncludeTasks
|
||||||
|
|
||||||
|
# Get feature paths only (no validation)
|
||||||
|
.\check-prerequisites.ps1 -PathsOnly
|
||||||
|
|
||||||
|
"@
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Source common functions
|
||||||
|
. "$PSScriptRoot/common.ps1"
|
||||||
|
|
||||||
|
# Get feature paths and validate branch
|
||||||
|
$paths = Get-FeaturePathsEnv
|
||||||
|
|
||||||
|
if (-not (Test-FeatureBranch -Branch $paths.CURRENT_BRANCH -HasGit:$paths.HAS_GIT)) {
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# If paths-only mode, output paths and exit (support combined -Json -PathsOnly)
|
||||||
|
if ($PathsOnly) {
|
||||||
|
if ($Json) {
|
||||||
|
[PSCustomObject]@{
|
||||||
|
REPO_ROOT = $paths.REPO_ROOT
|
||||||
|
BRANCH = $paths.CURRENT_BRANCH
|
||||||
|
FEATURE_DIR = $paths.FEATURE_DIR
|
||||||
|
FEATURE_SPEC = $paths.FEATURE_SPEC
|
||||||
|
IMPL_PLAN = $paths.IMPL_PLAN
|
||||||
|
TASKS = $paths.TASKS
|
||||||
|
} | ConvertTo-Json -Compress
|
||||||
|
} else {
|
||||||
|
Write-Output "REPO_ROOT: $($paths.REPO_ROOT)"
|
||||||
|
Write-Output "BRANCH: $($paths.CURRENT_BRANCH)"
|
||||||
|
Write-Output "FEATURE_DIR: $($paths.FEATURE_DIR)"
|
||||||
|
Write-Output "FEATURE_SPEC: $($paths.FEATURE_SPEC)"
|
||||||
|
Write-Output "IMPL_PLAN: $($paths.IMPL_PLAN)"
|
||||||
|
Write-Output "TASKS: $($paths.TASKS)"
|
||||||
|
}
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Validate required directories and files
|
||||||
|
if (-not (Test-Path $paths.FEATURE_DIR -PathType Container)) {
|
||||||
|
Write-Output "ERROR: Feature directory not found: $($paths.FEATURE_DIR)"
|
||||||
|
Write-Output "Run /speckit.specify first to create the feature structure."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not (Test-Path $paths.IMPL_PLAN -PathType Leaf)) {
|
||||||
|
Write-Output "ERROR: plan.md not found in $($paths.FEATURE_DIR)"
|
||||||
|
Write-Output "Run /speckit.plan first to create the implementation plan."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check for tasks.md if required
|
||||||
|
if ($RequireTasks -and -not (Test-Path $paths.TASKS -PathType Leaf)) {
|
||||||
|
Write-Output "ERROR: tasks.md not found in $($paths.FEATURE_DIR)"
|
||||||
|
Write-Output "Run /speckit.tasks first to create the task list."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Build list of available documents
|
||||||
|
$docs = @()
|
||||||
|
|
||||||
|
# Always check these optional docs
|
||||||
|
if (Test-Path $paths.RESEARCH) { $docs += 'research.md' }
|
||||||
|
if (Test-Path $paths.DATA_MODEL) { $docs += 'data-model.md' }
|
||||||
|
|
||||||
|
# Check contracts directory (only if it exists and has files)
|
||||||
|
if ((Test-Path $paths.CONTRACTS_DIR) -and (Get-ChildItem -Path $paths.CONTRACTS_DIR -ErrorAction SilentlyContinue | Select-Object -First 1)) {
|
||||||
|
$docs += 'contracts/'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Test-Path $paths.QUICKSTART) { $docs += 'quickstart.md' }
|
||||||
|
|
||||||
|
# Include tasks.md if requested and it exists
|
||||||
|
if ($IncludeTasks -and (Test-Path $paths.TASKS)) {
|
||||||
|
$docs += 'tasks.md'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Output results
|
||||||
|
if ($Json) {
|
||||||
|
# JSON output
|
||||||
|
[PSCustomObject]@{
|
||||||
|
FEATURE_DIR = $paths.FEATURE_DIR
|
||||||
|
AVAILABLE_DOCS = $docs
|
||||||
|
} | ConvertTo-Json -Compress
|
||||||
|
} else {
|
||||||
|
# Text output
|
||||||
|
Write-Output "FEATURE_DIR:$($paths.FEATURE_DIR)"
|
||||||
|
Write-Output "AVAILABLE_DOCS:"
|
||||||
|
|
||||||
|
# Show status of each potential document
|
||||||
|
Test-FileExists -Path $paths.RESEARCH -Description 'research.md' | Out-Null
|
||||||
|
Test-FileExists -Path $paths.DATA_MODEL -Description 'data-model.md' | Out-Null
|
||||||
|
Test-DirHasFiles -Path $paths.CONTRACTS_DIR -Description 'contracts/' | Out-Null
|
||||||
|
Test-FileExists -Path $paths.QUICKSTART -Description 'quickstart.md' | Out-Null
|
||||||
|
|
||||||
|
if ($IncludeTasks) {
|
||||||
|
Test-FileExists -Path $paths.TASKS -Description 'tasks.md' | Out-Null
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
#!/usr/bin/env pwsh
|
|
||||||
[CmdletBinding()]
|
|
||||||
param([switch]$Json)
|
|
||||||
$ErrorActionPreference = 'Stop'
|
|
||||||
. "$PSScriptRoot/common.ps1"
|
|
||||||
|
|
||||||
$paths = Get-FeaturePathsEnv
|
|
||||||
if (-not (Test-FeatureBranch -Branch $paths.CURRENT_BRANCH)) { exit 1 }
|
|
||||||
|
|
||||||
if (-not (Test-Path $paths.FEATURE_DIR -PathType Container)) {
|
|
||||||
Write-Output "ERROR: Feature directory not found: $($paths.FEATURE_DIR)"
|
|
||||||
Write-Output "Run /specify first to create the feature structure."
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
if (-not (Test-Path $paths.IMPL_PLAN -PathType Leaf)) {
|
|
||||||
Write-Output "ERROR: plan.md not found in $($paths.FEATURE_DIR)"
|
|
||||||
Write-Output "Run /plan first to create the plan."
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($Json) {
|
|
||||||
$docs = @()
|
|
||||||
if (Test-Path $paths.RESEARCH) { $docs += 'research.md' }
|
|
||||||
if (Test-Path $paths.DATA_MODEL) { $docs += 'data-model.md' }
|
|
||||||
if ((Test-Path $paths.CONTRACTS_DIR) -and (Get-ChildItem -Path $paths.CONTRACTS_DIR -ErrorAction SilentlyContinue | Select-Object -First 1)) { $docs += 'contracts/' }
|
|
||||||
if (Test-Path $paths.QUICKSTART) { $docs += 'quickstart.md' }
|
|
||||||
[PSCustomObject]@{ FEATURE_DIR=$paths.FEATURE_DIR; AVAILABLE_DOCS=$docs } | ConvertTo-Json -Compress
|
|
||||||
} else {
|
|
||||||
Write-Output "FEATURE_DIR:$($paths.FEATURE_DIR)"
|
|
||||||
Write-Output "AVAILABLE_DOCS:"
|
|
||||||
Test-FileExists -Path $paths.RESEARCH -Description 'research.md' | Out-Null
|
|
||||||
Test-FileExists -Path $paths.DATA_MODEL -Description 'data-model.md' | Out-Null
|
|
||||||
Test-DirHasFiles -Path $paths.CONTRACTS_DIR -Description 'contracts/' | Out-Null
|
|
||||||
Test-FileExists -Path $paths.QUICKSTART -Description 'quickstart.md' | Out-Null
|
|
||||||
}
|
|
||||||
@@ -1,16 +1,84 @@
|
|||||||
#!/usr/bin/env pwsh
|
#!/usr/bin/env pwsh
|
||||||
# Common PowerShell functions analogous to common.sh (moved to powershell/)
|
# Common PowerShell functions analogous to common.sh
|
||||||
|
|
||||||
function Get-RepoRoot {
|
function Get-RepoRoot {
|
||||||
git rev-parse --show-toplevel
|
try {
|
||||||
|
$result = git rev-parse --show-toplevel 2>$null
|
||||||
|
if ($LASTEXITCODE -eq 0) {
|
||||||
|
return $result
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
# Git command failed
|
||||||
|
}
|
||||||
|
|
||||||
|
# Fall back to script location for non-git repos
|
||||||
|
return (Resolve-Path (Join-Path $PSScriptRoot "../../..")).Path
|
||||||
}
|
}
|
||||||
|
|
||||||
function Get-CurrentBranch {
|
function Get-CurrentBranch {
|
||||||
git rev-parse --abbrev-ref HEAD
|
# First check if SPECIFY_FEATURE environment variable is set
|
||||||
|
if ($env:SPECIFY_FEATURE) {
|
||||||
|
return $env:SPECIFY_FEATURE
|
||||||
|
}
|
||||||
|
|
||||||
|
# Then check git if available
|
||||||
|
try {
|
||||||
|
$result = git rev-parse --abbrev-ref HEAD 2>$null
|
||||||
|
if ($LASTEXITCODE -eq 0) {
|
||||||
|
return $result
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
# Git command failed
|
||||||
|
}
|
||||||
|
|
||||||
|
# For non-git repos, try to find the latest feature directory
|
||||||
|
$repoRoot = Get-RepoRoot
|
||||||
|
$specsDir = Join-Path $repoRoot "specs"
|
||||||
|
|
||||||
|
if (Test-Path $specsDir) {
|
||||||
|
$latestFeature = ""
|
||||||
|
$highest = 0
|
||||||
|
|
||||||
|
Get-ChildItem -Path $specsDir -Directory | ForEach-Object {
|
||||||
|
if ($_.Name -match '^(\d{3})-') {
|
||||||
|
$num = [int]$matches[1]
|
||||||
|
if ($num -gt $highest) {
|
||||||
|
$highest = $num
|
||||||
|
$latestFeature = $_.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($latestFeature) {
|
||||||
|
return $latestFeature
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Final fallback
|
||||||
|
return "main"
|
||||||
|
}
|
||||||
|
|
||||||
|
function Test-HasGit {
|
||||||
|
try {
|
||||||
|
git rev-parse --show-toplevel 2>$null | Out-Null
|
||||||
|
return ($LASTEXITCODE -eq 0)
|
||||||
|
} catch {
|
||||||
|
return $false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function Test-FeatureBranch {
|
function Test-FeatureBranch {
|
||||||
param([string]$Branch)
|
param(
|
||||||
|
[string]$Branch,
|
||||||
|
[bool]$HasGit = $true
|
||||||
|
)
|
||||||
|
|
||||||
|
# For non-git repos, we can't enforce branch naming but still provide output
|
||||||
|
if (-not $HasGit) {
|
||||||
|
Write-Warning "[specify] Warning: Git repository not detected; skipped branch validation"
|
||||||
|
return $true
|
||||||
|
}
|
||||||
|
|
||||||
if ($Branch -notmatch '^[0-9]{3}-') {
|
if ($Branch -notmatch '^[0-9]{3}-') {
|
||||||
Write-Output "ERROR: Not on a feature branch. Current branch: $Branch"
|
Write-Output "ERROR: Not on a feature branch. Current branch: $Branch"
|
||||||
Write-Output "Feature branches should be named like: 001-feature-name"
|
Write-Output "Feature branches should be named like: 001-feature-name"
|
||||||
@@ -27,17 +95,20 @@ function Get-FeatureDir {
|
|||||||
function Get-FeaturePathsEnv {
|
function Get-FeaturePathsEnv {
|
||||||
$repoRoot = Get-RepoRoot
|
$repoRoot = Get-RepoRoot
|
||||||
$currentBranch = Get-CurrentBranch
|
$currentBranch = Get-CurrentBranch
|
||||||
|
$hasGit = Test-HasGit
|
||||||
$featureDir = Get-FeatureDir -RepoRoot $repoRoot -Branch $currentBranch
|
$featureDir = Get-FeatureDir -RepoRoot $repoRoot -Branch $currentBranch
|
||||||
|
|
||||||
[PSCustomObject]@{
|
[PSCustomObject]@{
|
||||||
REPO_ROOT = $repoRoot
|
REPO_ROOT = $repoRoot
|
||||||
CURRENT_BRANCH = $currentBranch
|
CURRENT_BRANCH = $currentBranch
|
||||||
FEATURE_DIR = $featureDir
|
HAS_GIT = $hasGit
|
||||||
FEATURE_SPEC = Join-Path $featureDir 'spec.md'
|
FEATURE_DIR = $featureDir
|
||||||
IMPL_PLAN = Join-Path $featureDir 'plan.md'
|
FEATURE_SPEC = Join-Path $featureDir 'spec.md'
|
||||||
TASKS = Join-Path $featureDir 'tasks.md'
|
IMPL_PLAN = Join-Path $featureDir 'plan.md'
|
||||||
RESEARCH = Join-Path $featureDir 'research.md'
|
TASKS = Join-Path $featureDir 'tasks.md'
|
||||||
DATA_MODEL = Join-Path $featureDir 'data-model.md'
|
RESEARCH = Join-Path $featureDir 'research.md'
|
||||||
QUICKSTART = Join-Path $featureDir 'quickstart.md'
|
DATA_MODEL = Join-Path $featureDir 'data-model.md'
|
||||||
|
QUICKSTART = Join-Path $featureDir 'quickstart.md'
|
||||||
CONTRACTS_DIR = Join-Path $featureDir 'contracts'
|
CONTRACTS_DIR = Join-Path $featureDir 'contracts'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -63,3 +134,4 @@ function Test-DirHasFiles {
|
|||||||
return $false
|
return $false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,52 +1,283 @@
|
|||||||
#!/usr/bin/env pwsh
|
#!/usr/bin/env pwsh
|
||||||
# Create a new feature (moved to powershell/)
|
# Create a new feature
|
||||||
[CmdletBinding()]
|
[CmdletBinding()]
|
||||||
param(
|
param(
|
||||||
[switch]$Json,
|
[switch]$Json,
|
||||||
|
[string]$ShortName,
|
||||||
|
[int]$Number = 0,
|
||||||
|
[switch]$Help,
|
||||||
[Parameter(ValueFromRemainingArguments = $true)]
|
[Parameter(ValueFromRemainingArguments = $true)]
|
||||||
[string[]]$FeatureDescription
|
[string[]]$FeatureDescription
|
||||||
)
|
)
|
||||||
$ErrorActionPreference = 'Stop'
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
if (-not $FeatureDescription -or $FeatureDescription.Count -eq 0) {
|
# Show help if requested
|
||||||
Write-Error "Usage: ./create-new-feature.ps1 [-Json] <feature description>"; exit 1
|
if ($Help) {
|
||||||
|
Write-Host "Usage: ./create-new-feature.ps1 [-Json] [-ShortName <name>] [-Number N] <feature description>"
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "Options:"
|
||||||
|
Write-Host " -Json Output in JSON format"
|
||||||
|
Write-Host " -ShortName <name> Provide a custom short name (2-4 words) for the branch"
|
||||||
|
Write-Host " -Number N Specify branch number manually (overrides auto-detection)"
|
||||||
|
Write-Host " -Help Show this help message"
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "Examples:"
|
||||||
|
Write-Host " ./create-new-feature.ps1 'Add user authentication system' -ShortName 'user-auth'"
|
||||||
|
Write-Host " ./create-new-feature.ps1 'Implement OAuth2 integration for API'"
|
||||||
|
exit 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Check if feature description provided
|
||||||
|
if (-not $FeatureDescription -or $FeatureDescription.Count -eq 0) {
|
||||||
|
Write-Error "Usage: ./create-new-feature.ps1 [-Json] [-ShortName <name>] <feature description>"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
$featureDesc = ($FeatureDescription -join ' ').Trim()
|
$featureDesc = ($FeatureDescription -join ' ').Trim()
|
||||||
|
|
||||||
$repoRoot = git rev-parse --show-toplevel
|
# Resolve repository root. Prefer git information when available, but fall back
|
||||||
|
# to searching for repository markers so the workflow still functions in repositories that
|
||||||
|
# were initialized with --no-git.
|
||||||
|
function Find-RepositoryRoot {
|
||||||
|
param(
|
||||||
|
[string]$StartDir,
|
||||||
|
[string[]]$Markers = @('.git', '.specify')
|
||||||
|
)
|
||||||
|
$current = Resolve-Path $StartDir
|
||||||
|
while ($true) {
|
||||||
|
foreach ($marker in $Markers) {
|
||||||
|
if (Test-Path (Join-Path $current $marker)) {
|
||||||
|
return $current
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$parent = Split-Path $current -Parent
|
||||||
|
if ($parent -eq $current) {
|
||||||
|
# Reached filesystem root without finding markers
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
$current = $parent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-HighestNumberFromSpecs {
|
||||||
|
param([string]$SpecsDir)
|
||||||
|
|
||||||
|
$highest = 0
|
||||||
|
if (Test-Path $SpecsDir) {
|
||||||
|
Get-ChildItem -Path $SpecsDir -Directory | ForEach-Object {
|
||||||
|
if ($_.Name -match '^(\d+)') {
|
||||||
|
$num = [int]$matches[1]
|
||||||
|
if ($num -gt $highest) { $highest = $num }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $highest
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-HighestNumberFromBranches {
|
||||||
|
param()
|
||||||
|
|
||||||
|
$highest = 0
|
||||||
|
try {
|
||||||
|
$branches = git branch -a 2>$null
|
||||||
|
if ($LASTEXITCODE -eq 0) {
|
||||||
|
foreach ($branch in $branches) {
|
||||||
|
# Clean branch name: remove leading markers and remote prefixes
|
||||||
|
$cleanBranch = $branch.Trim() -replace '^\*?\s+', '' -replace '^remotes/[^/]+/', ''
|
||||||
|
|
||||||
|
# Extract feature number if branch matches pattern ###-*
|
||||||
|
if ($cleanBranch -match '^(\d+)-') {
|
||||||
|
$num = [int]$matches[1]
|
||||||
|
if ($num -gt $highest) { $highest = $num }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
# If git command fails, return 0
|
||||||
|
Write-Verbose "Could not check Git branches: $_"
|
||||||
|
}
|
||||||
|
return $highest
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-NextBranchNumber {
|
||||||
|
param(
|
||||||
|
[string]$SpecsDir
|
||||||
|
)
|
||||||
|
|
||||||
|
# Fetch all remotes to get latest branch info (suppress errors if no remotes)
|
||||||
|
try {
|
||||||
|
git fetch --all --prune 2>$null | Out-Null
|
||||||
|
} catch {
|
||||||
|
# Ignore fetch errors
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get highest number from ALL branches (not just matching short name)
|
||||||
|
$highestBranch = Get-HighestNumberFromBranches
|
||||||
|
|
||||||
|
# Get highest number from ALL specs (not just matching short name)
|
||||||
|
$highestSpec = Get-HighestNumberFromSpecs -SpecsDir $SpecsDir
|
||||||
|
|
||||||
|
# Take the maximum of both
|
||||||
|
$maxNum = [Math]::Max($highestBranch, $highestSpec)
|
||||||
|
|
||||||
|
# Return next number
|
||||||
|
return $maxNum + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
function ConvertTo-CleanBranchName {
|
||||||
|
param([string]$Name)
|
||||||
|
|
||||||
|
return $Name.ToLower() -replace '[^a-z0-9]', '-' -replace '-{2,}', '-' -replace '^-', '' -replace '-$', ''
|
||||||
|
}
|
||||||
|
$fallbackRoot = (Find-RepositoryRoot -StartDir $PSScriptRoot)
|
||||||
|
if (-not $fallbackRoot) {
|
||||||
|
Write-Error "Error: Could not determine repository root. Please run this script from within the repository."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$repoRoot = git rev-parse --show-toplevel 2>$null
|
||||||
|
if ($LASTEXITCODE -eq 0) {
|
||||||
|
$hasGit = $true
|
||||||
|
} else {
|
||||||
|
throw "Git not available"
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
$repoRoot = $fallbackRoot
|
||||||
|
$hasGit = $false
|
||||||
|
}
|
||||||
|
|
||||||
|
Set-Location $repoRoot
|
||||||
|
|
||||||
$specsDir = Join-Path $repoRoot 'specs'
|
$specsDir = Join-Path $repoRoot 'specs'
|
||||||
New-Item -ItemType Directory -Path $specsDir -Force | Out-Null
|
New-Item -ItemType Directory -Path $specsDir -Force | Out-Null
|
||||||
|
|
||||||
$highest = 0
|
# Function to generate branch name with stop word filtering and length filtering
|
||||||
if (Test-Path $specsDir) {
|
function Get-BranchName {
|
||||||
Get-ChildItem -Path $specsDir -Directory | ForEach-Object {
|
param([string]$Description)
|
||||||
if ($_.Name -match '^(\d{3})') {
|
|
||||||
$num = [int]$matches[1]
|
# Common stop words to filter out
|
||||||
if ($num -gt $highest) { $highest = $num }
|
$stopWords = @(
|
||||||
|
'i', 'a', 'an', 'the', 'to', 'for', 'of', 'in', 'on', 'at', 'by', 'with', 'from',
|
||||||
|
'is', 'are', 'was', 'were', 'be', 'been', 'being', 'have', 'has', 'had',
|
||||||
|
'do', 'does', 'did', 'will', 'would', 'should', 'could', 'can', 'may', 'might', 'must', 'shall',
|
||||||
|
'this', 'that', 'these', 'those', 'my', 'your', 'our', 'their',
|
||||||
|
'want', 'need', 'add', 'get', 'set'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Convert to lowercase and extract words (alphanumeric only)
|
||||||
|
$cleanName = $Description.ToLower() -replace '[^a-z0-9\s]', ' '
|
||||||
|
$words = $cleanName -split '\s+' | Where-Object { $_ }
|
||||||
|
|
||||||
|
# Filter words: remove stop words and words shorter than 3 chars (unless they're uppercase acronyms in original)
|
||||||
|
$meaningfulWords = @()
|
||||||
|
foreach ($word in $words) {
|
||||||
|
# Skip stop words
|
||||||
|
if ($stopWords -contains $word) { continue }
|
||||||
|
|
||||||
|
# Keep words that are length >= 3 OR appear as uppercase in original (likely acronyms)
|
||||||
|
if ($word.Length -ge 3) {
|
||||||
|
$meaningfulWords += $word
|
||||||
|
} elseif ($Description -match "\b$($word.ToUpper())\b") {
|
||||||
|
# Keep short words if they appear as uppercase in original (likely acronyms)
|
||||||
|
$meaningfulWords += $word
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# If we have meaningful words, use first 3-4 of them
|
||||||
|
if ($meaningfulWords.Count -gt 0) {
|
||||||
|
$maxWords = if ($meaningfulWords.Count -eq 4) { 4 } else { 3 }
|
||||||
|
$result = ($meaningfulWords | Select-Object -First $maxWords) -join '-'
|
||||||
|
return $result
|
||||||
|
} else {
|
||||||
|
# Fallback to original logic if no meaningful words found
|
||||||
|
$result = ConvertTo-CleanBranchName -Name $Description
|
||||||
|
$fallbackWords = ($result -split '-') | Where-Object { $_ } | Select-Object -First 3
|
||||||
|
return [string]::Join('-', $fallbackWords)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
$next = $highest + 1
|
|
||||||
$featureNum = ('{0:000}' -f $next)
|
|
||||||
|
|
||||||
$branchName = $featureDesc.ToLower() -replace '[^a-z0-9]', '-' -replace '-{2,}', '-' -replace '^-', '' -replace '-$', ''
|
# Generate branch name
|
||||||
$words = ($branchName -split '-') | Where-Object { $_ } | Select-Object -First 3
|
if ($ShortName) {
|
||||||
$branchName = "$featureNum-$([string]::Join('-', $words))"
|
# Use provided short name, just clean it up
|
||||||
|
$branchSuffix = ConvertTo-CleanBranchName -Name $ShortName
|
||||||
|
} else {
|
||||||
|
# Generate from description with smart filtering
|
||||||
|
$branchSuffix = Get-BranchName -Description $featureDesc
|
||||||
|
}
|
||||||
|
|
||||||
git checkout -b $branchName | Out-Null
|
# Determine branch number
|
||||||
|
if ($Number -eq 0) {
|
||||||
|
if ($hasGit) {
|
||||||
|
# Check existing branches on remotes
|
||||||
|
$Number = Get-NextBranchNumber -SpecsDir $specsDir
|
||||||
|
} else {
|
||||||
|
# Fall back to local directory check
|
||||||
|
$Number = (Get-HighestNumberFromSpecs -SpecsDir $specsDir) + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$featureNum = ('{0:000}' -f $Number)
|
||||||
|
$branchName = "$featureNum-$branchSuffix"
|
||||||
|
|
||||||
|
# GitHub enforces a 244-byte limit on branch names
|
||||||
|
# Validate and truncate if necessary
|
||||||
|
$maxBranchLength = 244
|
||||||
|
if ($branchName.Length -gt $maxBranchLength) {
|
||||||
|
# Calculate how much we need to trim from suffix
|
||||||
|
# Account for: feature number (3) + hyphen (1) = 4 chars
|
||||||
|
$maxSuffixLength = $maxBranchLength - 4
|
||||||
|
|
||||||
|
# Truncate suffix
|
||||||
|
$truncatedSuffix = $branchSuffix.Substring(0, [Math]::Min($branchSuffix.Length, $maxSuffixLength))
|
||||||
|
# Remove trailing hyphen if truncation created one
|
||||||
|
$truncatedSuffix = $truncatedSuffix -replace '-$', ''
|
||||||
|
|
||||||
|
$originalBranchName = $branchName
|
||||||
|
$branchName = "$featureNum-$truncatedSuffix"
|
||||||
|
|
||||||
|
Write-Warning "[specify] Branch name exceeded GitHub's 244-byte limit"
|
||||||
|
Write-Warning "[specify] Original: $originalBranchName ($($originalBranchName.Length) bytes)"
|
||||||
|
Write-Warning "[specify] Truncated to: $branchName ($($branchName.Length) bytes)"
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($hasGit) {
|
||||||
|
try {
|
||||||
|
git checkout -b $branchName | Out-Null
|
||||||
|
} catch {
|
||||||
|
Write-Warning "Failed to create git branch: $branchName"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Write-Warning "[specify] Warning: Git repository not detected; skipped branch creation for $branchName"
|
||||||
|
}
|
||||||
|
|
||||||
$featureDir = Join-Path $specsDir $branchName
|
$featureDir = Join-Path $specsDir $branchName
|
||||||
New-Item -ItemType Directory -Path $featureDir -Force | Out-Null
|
New-Item -ItemType Directory -Path $featureDir -Force | Out-Null
|
||||||
|
|
||||||
$template = Join-Path $repoRoot 'templates/spec-template.md'
|
$template = Join-Path $repoRoot '.specify/templates/spec-template.md'
|
||||||
$specFile = Join-Path $featureDir 'spec.md'
|
$specFile = Join-Path $featureDir 'spec.md'
|
||||||
if (Test-Path $template) { Copy-Item $template $specFile -Force } else { New-Item -ItemType File -Path $specFile | Out-Null }
|
if (Test-Path $template) {
|
||||||
|
Copy-Item $template $specFile -Force
|
||||||
|
} else {
|
||||||
|
New-Item -ItemType File -Path $specFile | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
# Set the SPECIFY_FEATURE environment variable for the current session
|
||||||
|
$env:SPECIFY_FEATURE = $branchName
|
||||||
|
|
||||||
if ($Json) {
|
if ($Json) {
|
||||||
$obj = [PSCustomObject]@{ BRANCH_NAME = $branchName; SPEC_FILE = $specFile; FEATURE_NUM = $featureNum }
|
$obj = [PSCustomObject]@{
|
||||||
|
BRANCH_NAME = $branchName
|
||||||
|
SPEC_FILE = $specFile
|
||||||
|
FEATURE_NUM = $featureNum
|
||||||
|
HAS_GIT = $hasGit
|
||||||
|
}
|
||||||
$obj | ConvertTo-Json -Compress
|
$obj | ConvertTo-Json -Compress
|
||||||
} else {
|
} else {
|
||||||
Write-Output "BRANCH_NAME: $branchName"
|
Write-Output "BRANCH_NAME: $branchName"
|
||||||
Write-Output "SPEC_FILE: $specFile"
|
Write-Output "SPEC_FILE: $specFile"
|
||||||
Write-Output "FEATURE_NUM: $featureNum"
|
Write-Output "FEATURE_NUM: $featureNum"
|
||||||
|
Write-Output "HAS_GIT: $hasGit"
|
||||||
|
Write-Output "SPECIFY_FEATURE environment variable set to: $branchName"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
#!/usr/bin/env pwsh
|
|
||||||
param()
|
|
||||||
$ErrorActionPreference = 'Stop'
|
|
||||||
|
|
||||||
. "$PSScriptRoot/common.ps1"
|
|
||||||
|
|
||||||
$paths = Get-FeaturePathsEnv
|
|
||||||
if (-not (Test-FeatureBranch -Branch $paths.CURRENT_BRANCH)) { exit 1 }
|
|
||||||
|
|
||||||
Write-Output "REPO_ROOT: $($paths.REPO_ROOT)"
|
|
||||||
Write-Output "BRANCH: $($paths.CURRENT_BRANCH)"
|
|
||||||
Write-Output "FEATURE_DIR: $($paths.FEATURE_DIR)"
|
|
||||||
Write-Output "FEATURE_SPEC: $($paths.FEATURE_SPEC)"
|
|
||||||
Write-Output "IMPL_PLAN: $($paths.IMPL_PLAN)"
|
|
||||||
Write-Output "TASKS: $($paths.TASKS)"
|
|
||||||
@@ -1,21 +1,61 @@
|
|||||||
#!/usr/bin/env pwsh
|
#!/usr/bin/env pwsh
|
||||||
|
# Setup implementation plan for a feature
|
||||||
|
|
||||||
[CmdletBinding()]
|
[CmdletBinding()]
|
||||||
param([switch]$Json)
|
param(
|
||||||
|
[switch]$Json,
|
||||||
|
[switch]$Help
|
||||||
|
)
|
||||||
|
|
||||||
$ErrorActionPreference = 'Stop'
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
|
# Show help if requested
|
||||||
|
if ($Help) {
|
||||||
|
Write-Output "Usage: ./setup-plan.ps1 [-Json] [-Help]"
|
||||||
|
Write-Output " -Json Output results in JSON format"
|
||||||
|
Write-Output " -Help Show this help message"
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Load common functions
|
||||||
. "$PSScriptRoot/common.ps1"
|
. "$PSScriptRoot/common.ps1"
|
||||||
|
|
||||||
|
# Get all paths and variables from common functions
|
||||||
$paths = Get-FeaturePathsEnv
|
$paths = Get-FeaturePathsEnv
|
||||||
if (-not (Test-FeatureBranch -Branch $paths.CURRENT_BRANCH)) { exit 1 }
|
|
||||||
|
|
||||||
|
# Check if we're on a proper feature branch (only for git repos)
|
||||||
|
if (-not (Test-FeatureBranch -Branch $paths.CURRENT_BRANCH -HasGit $paths.HAS_GIT)) {
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Ensure the feature directory exists
|
||||||
New-Item -ItemType Directory -Path $paths.FEATURE_DIR -Force | Out-Null
|
New-Item -ItemType Directory -Path $paths.FEATURE_DIR -Force | Out-Null
|
||||||
$template = Join-Path $paths.REPO_ROOT 'templates/plan-template.md'
|
|
||||||
if (Test-Path $template) { Copy-Item $template $paths.IMPL_PLAN -Force }
|
|
||||||
|
|
||||||
|
# Copy plan template if it exists, otherwise note it or create empty file
|
||||||
|
$template = Join-Path $paths.REPO_ROOT '.specify/templates/plan-template.md'
|
||||||
|
if (Test-Path $template) {
|
||||||
|
Copy-Item $template $paths.IMPL_PLAN -Force
|
||||||
|
Write-Output "Copied plan template to $($paths.IMPL_PLAN)"
|
||||||
|
} else {
|
||||||
|
Write-Warning "Plan template not found at $template"
|
||||||
|
# Create a basic plan file if template doesn't exist
|
||||||
|
New-Item -ItemType File -Path $paths.IMPL_PLAN -Force | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
# Output results
|
||||||
if ($Json) {
|
if ($Json) {
|
||||||
[PSCustomObject]@{ FEATURE_SPEC=$paths.FEATURE_SPEC; IMPL_PLAN=$paths.IMPL_PLAN; SPECS_DIR=$paths.FEATURE_DIR; BRANCH=$paths.CURRENT_BRANCH } | ConvertTo-Json -Compress
|
$result = [PSCustomObject]@{
|
||||||
|
FEATURE_SPEC = $paths.FEATURE_SPEC
|
||||||
|
IMPL_PLAN = $paths.IMPL_PLAN
|
||||||
|
SPECS_DIR = $paths.FEATURE_DIR
|
||||||
|
BRANCH = $paths.CURRENT_BRANCH
|
||||||
|
HAS_GIT = $paths.HAS_GIT
|
||||||
|
}
|
||||||
|
$result | ConvertTo-Json -Compress
|
||||||
} else {
|
} else {
|
||||||
Write-Output "FEATURE_SPEC: $($paths.FEATURE_SPEC)"
|
Write-Output "FEATURE_SPEC: $($paths.FEATURE_SPEC)"
|
||||||
Write-Output "IMPL_PLAN: $($paths.IMPL_PLAN)"
|
Write-Output "IMPL_PLAN: $($paths.IMPL_PLAN)"
|
||||||
Write-Output "SPECS_DIR: $($paths.FEATURE_DIR)"
|
Write-Output "SPECS_DIR: $($paths.FEATURE_DIR)"
|
||||||
Write-Output "BRANCH: $($paths.CURRENT_BRANCH)"
|
Write-Output "BRANCH: $($paths.CURRENT_BRANCH)"
|
||||||
|
Write-Output "HAS_GIT: $($paths.HAS_GIT)"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,104 +1,451 @@
|
|||||||
#!/usr/bin/env pwsh
|
#!/usr/bin/env pwsh
|
||||||
[CmdletBinding()]
|
<#!
|
||||||
param([string]$AgentType)
|
.SYNOPSIS
|
||||||
|
Update agent context files with information from plan.md (PowerShell version)
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Mirrors the behavior of scripts/bash/update-agent-context.sh:
|
||||||
|
1. Environment Validation
|
||||||
|
2. Plan Data Extraction
|
||||||
|
3. Agent File Management (create from template or update existing)
|
||||||
|
4. Content Generation (technology stack, recent changes, timestamp)
|
||||||
|
5. Multi-Agent Support (claude, gemini, copilot, cursor-agent, qwen, opencode, codex, windsurf, kilocode, auggie, roo, codebuddy, amp, shai, q, agy, bob, qoder)
|
||||||
|
|
||||||
|
.PARAMETER AgentType
|
||||||
|
Optional agent key to update a single agent. If omitted, updates all existing agent files (creating a default Claude file if none exist).
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
./update-agent-context.ps1 -AgentType claude
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
./update-agent-context.ps1 # Updates all existing agent files
|
||||||
|
|
||||||
|
.NOTES
|
||||||
|
Relies on common helper functions in common.ps1
|
||||||
|
#>
|
||||||
|
param(
|
||||||
|
[Parameter(Position=0)]
|
||||||
|
[ValidateSet('claude','gemini','copilot','cursor-agent','qwen','opencode','codex','windsurf','kilocode','auggie','roo','codebuddy','amp','shai','q','agy','bob','qoder')]
|
||||||
|
[string]$AgentType
|
||||||
|
)
|
||||||
|
|
||||||
$ErrorActionPreference = 'Stop'
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
$repoRoot = git rev-parse --show-toplevel
|
# Import common helpers
|
||||||
$currentBranch = git rev-parse --abbrev-ref HEAD
|
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||||
$featureDir = Join-Path $repoRoot "specs/$currentBranch"
|
. (Join-Path $ScriptDir 'common.ps1')
|
||||||
$newPlan = Join-Path $featureDir 'plan.md'
|
|
||||||
if (-not (Test-Path $newPlan)) { Write-Error "ERROR: No plan.md found at $newPlan"; exit 1 }
|
|
||||||
|
|
||||||
$claudeFile = Join-Path $repoRoot 'CLAUDE.md'
|
# Acquire environment paths
|
||||||
$geminiFile = Join-Path $repoRoot 'GEMINI.md'
|
$envData = Get-FeaturePathsEnv
|
||||||
$copilotFile = Join-Path $repoRoot '.github/copilot-instructions.md'
|
$REPO_ROOT = $envData.REPO_ROOT
|
||||||
$cursorFile = Join-Path $repoRoot '.cursor/rules/specify-rules.mdc'
|
$CURRENT_BRANCH = $envData.CURRENT_BRANCH
|
||||||
$qwenFile = Join-Path $repoRoot 'QWEN.md'
|
$HAS_GIT = $envData.HAS_GIT
|
||||||
$agentsFile = Join-Path $repoRoot 'AGENTS.md'
|
$IMPL_PLAN = $envData.IMPL_PLAN
|
||||||
|
$NEW_PLAN = $IMPL_PLAN
|
||||||
|
|
||||||
Write-Output "=== Updating agent context files for feature $currentBranch ==="
|
# Agent file paths
|
||||||
|
$CLAUDE_FILE = Join-Path $REPO_ROOT 'CLAUDE.md'
|
||||||
|
$GEMINI_FILE = Join-Path $REPO_ROOT 'GEMINI.md'
|
||||||
|
$COPILOT_FILE = Join-Path $REPO_ROOT '.github/agents/copilot-instructions.md'
|
||||||
|
$CURSOR_FILE = Join-Path $REPO_ROOT '.cursor/rules/specify-rules.mdc'
|
||||||
|
$QWEN_FILE = Join-Path $REPO_ROOT 'QWEN.md'
|
||||||
|
$AGENTS_FILE = Join-Path $REPO_ROOT 'AGENTS.md'
|
||||||
|
$WINDSURF_FILE = Join-Path $REPO_ROOT '.windsurf/rules/specify-rules.md'
|
||||||
|
$KILOCODE_FILE = Join-Path $REPO_ROOT '.kilocode/rules/specify-rules.md'
|
||||||
|
$AUGGIE_FILE = Join-Path $REPO_ROOT '.augment/rules/specify-rules.md'
|
||||||
|
$ROO_FILE = Join-Path $REPO_ROOT '.roo/rules/specify-rules.md'
|
||||||
|
$CODEBUDDY_FILE = Join-Path $REPO_ROOT 'CODEBUDDY.md'
|
||||||
|
$QODER_FILE = Join-Path $REPO_ROOT 'QODER.md'
|
||||||
|
$AMP_FILE = Join-Path $REPO_ROOT 'AGENTS.md'
|
||||||
|
$SHAI_FILE = Join-Path $REPO_ROOT 'SHAI.md'
|
||||||
|
$Q_FILE = Join-Path $REPO_ROOT 'AGENTS.md'
|
||||||
|
$AGY_FILE = Join-Path $REPO_ROOT '.agent/rules/specify-rules.md'
|
||||||
|
$BOB_FILE = Join-Path $REPO_ROOT 'AGENTS.md'
|
||||||
|
|
||||||
function Get-PlanValue($pattern) {
|
$TEMPLATE_FILE = Join-Path $REPO_ROOT '.specify/templates/agent-file-template.md'
|
||||||
if (-not (Test-Path $newPlan)) { return '' }
|
|
||||||
$line = Select-String -Path $newPlan -Pattern $pattern | Select-Object -First 1
|
# Parsed plan data placeholders
|
||||||
if ($line) { return ($line.Line -replace "^\*\*$pattern\*\*: ", '') }
|
$script:NEW_LANG = ''
|
||||||
return ''
|
$script:NEW_FRAMEWORK = ''
|
||||||
|
$script:NEW_DB = ''
|
||||||
|
$script:NEW_PROJECT_TYPE = ''
|
||||||
|
|
||||||
|
function Write-Info {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory=$true)]
|
||||||
|
[string]$Message
|
||||||
|
)
|
||||||
|
Write-Host "INFO: $Message"
|
||||||
}
|
}
|
||||||
|
|
||||||
$newLang = Get-PlanValue 'Language/Version'
|
function Write-Success {
|
||||||
$newFramework = Get-PlanValue 'Primary Dependencies'
|
param(
|
||||||
$newTesting = Get-PlanValue 'Testing'
|
[Parameter(Mandatory=$true)]
|
||||||
$newDb = Get-PlanValue 'Storage'
|
[string]$Message
|
||||||
$newProjectType = Get-PlanValue 'Project Type'
|
)
|
||||||
|
Write-Host "$([char]0x2713) $Message"
|
||||||
function Initialize-AgentFile($targetFile, $agentName) {
|
|
||||||
if (Test-Path $targetFile) { return }
|
|
||||||
$template = Join-Path $repoRoot '.specify/templates/agent-file-template.md'
|
|
||||||
if (-not (Test-Path $template)) { Write-Error "Template not found: $template"; return }
|
|
||||||
$content = Get-Content $template -Raw
|
|
||||||
$content = $content.Replace('[PROJECT NAME]', (Split-Path $repoRoot -Leaf))
|
|
||||||
$content = $content.Replace('[DATE]', (Get-Date -Format 'yyyy-MM-dd'))
|
|
||||||
$content = $content.Replace('[EXTRACTED FROM ALL PLAN.MD FILES]', "- $newLang + $newFramework ($currentBranch)")
|
|
||||||
if ($newProjectType -match 'web') { $structure = "backend/`nfrontend/`ntests/" } else { $structure = "src/`ntests/" }
|
|
||||||
$content = $content.Replace('[ACTUAL STRUCTURE FROM PLANS]', $structure)
|
|
||||||
if ($newLang -match 'Python') { $commands = 'cd src && pytest && ruff check .' }
|
|
||||||
elseif ($newLang -match 'Rust') { $commands = 'cargo test && cargo clippy' }
|
|
||||||
elseif ($newLang -match 'JavaScript|TypeScript') { $commands = 'npm test && npm run lint' }
|
|
||||||
else { $commands = "# Add commands for $newLang" }
|
|
||||||
$content = $content.Replace('[ONLY COMMANDS FOR ACTIVE TECHNOLOGIES]', $commands)
|
|
||||||
$content = $content.Replace('[LANGUAGE-SPECIFIC, ONLY FOR LANGUAGES IN USE]', "${newLang}: Follow standard conventions")
|
|
||||||
$content = $content.Replace('[LAST 3 FEATURES AND WHAT THEY ADDED]', "- ${currentBranch}: Added ${newLang} + ${newFramework}")
|
|
||||||
$content | Set-Content $targetFile -Encoding UTF8
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function Update-AgentFile($targetFile, $agentName) {
|
function Write-WarningMsg {
|
||||||
if (-not (Test-Path $targetFile)) { Initialize-AgentFile $targetFile $agentName; return }
|
param(
|
||||||
$content = Get-Content $targetFile -Raw
|
[Parameter(Mandatory=$true)]
|
||||||
if ($newLang -and ($content -notmatch [regex]::Escape($newLang))) { $content = $content -replace '(## Active Technologies\n)', "`$1- $newLang + $newFramework ($currentBranch)`n" }
|
[string]$Message
|
||||||
if ($newDb -and $newDb -ne 'N/A' -and ($content -notmatch [regex]::Escape($newDb))) { $content = $content -replace '(## Active Technologies\n)', "`$1- $newDb ($currentBranch)`n" }
|
)
|
||||||
if ($content -match '## Recent Changes\n([\s\S]*?)(\n\n|$)') {
|
Write-Warning $Message
|
||||||
$changesBlock = $matches[1].Trim().Split("`n")
|
}
|
||||||
$changesBlock = ,"- ${currentBranch}: Added ${newLang} + ${newFramework}" + $changesBlock
|
|
||||||
$changesBlock = $changesBlock | Where-Object { $_ } | Select-Object -First 3
|
function Write-Err {
|
||||||
$joined = ($changesBlock -join "`n")
|
param(
|
||||||
$content = [regex]::Replace($content, '## Recent Changes\n([\s\S]*?)(\n\n|$)', "## Recent Changes`n$joined`n`n")
|
[Parameter(Mandatory=$true)]
|
||||||
|
[string]$Message
|
||||||
|
)
|
||||||
|
Write-Host "ERROR: $Message" -ForegroundColor Red
|
||||||
|
}
|
||||||
|
|
||||||
|
function Validate-Environment {
|
||||||
|
if (-not $CURRENT_BRANCH) {
|
||||||
|
Write-Err 'Unable to determine current feature'
|
||||||
|
if ($HAS_GIT) { Write-Info "Make sure you're on a feature branch" } else { Write-Info 'Set SPECIFY_FEATURE environment variable or create a feature first' }
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
if (-not (Test-Path $NEW_PLAN)) {
|
||||||
|
Write-Err "No plan.md found at $NEW_PLAN"
|
||||||
|
Write-Info 'Ensure you are working on a feature with a corresponding spec directory'
|
||||||
|
if (-not $HAS_GIT) { Write-Info 'Use: $env:SPECIFY_FEATURE=your-feature-name or create a new feature first' }
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
if (-not (Test-Path $TEMPLATE_FILE)) {
|
||||||
|
Write-Err "Template file not found at $TEMPLATE_FILE"
|
||||||
|
Write-Info 'Run specify init to scaffold .specify/templates, or add agent-file-template.md there.'
|
||||||
|
exit 1
|
||||||
}
|
}
|
||||||
$content = [regex]::Replace($content, 'Last updated: \d{4}-\d{2}-\d{2}', "Last updated: $(Get-Date -Format 'yyyy-MM-dd')")
|
|
||||||
$content | Set-Content $targetFile -Encoding UTF8
|
|
||||||
Write-Output "✅ $agentName context file updated successfully"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch ($AgentType) {
|
function Extract-PlanField {
|
||||||
'claude' { Update-AgentFile $claudeFile 'Claude Code' }
|
param(
|
||||||
'gemini' { Update-AgentFile $geminiFile 'Gemini CLI' }
|
[Parameter(Mandatory=$true)]
|
||||||
'copilot' { Update-AgentFile $copilotFile 'GitHub Copilot' }
|
[string]$FieldPattern,
|
||||||
'cursor' { Update-AgentFile $cursorFile 'Cursor IDE' }
|
[Parameter(Mandatory=$true)]
|
||||||
'qwen' { Update-AgentFile $qwenFile 'Qwen Code' }
|
[string]$PlanFile
|
||||||
'opencode' { Update-AgentFile $agentsFile 'opencode' }
|
)
|
||||||
'' {
|
if (-not (Test-Path $PlanFile)) { return '' }
|
||||||
foreach ($pair in @(
|
# Lines like **Language/Version**: Python 3.12
|
||||||
@{file=$claudeFile; name='Claude Code'},
|
$regex = "^\*\*$([Regex]::Escape($FieldPattern))\*\*: (.+)$"
|
||||||
@{file=$geminiFile; name='Gemini CLI'},
|
Get-Content -LiteralPath $PlanFile -Encoding utf8 | ForEach-Object {
|
||||||
@{file=$copilotFile; name='GitHub Copilot'},
|
if ($_ -match $regex) {
|
||||||
@{file=$cursorFile; name='Cursor IDE'},
|
$val = $Matches[1].Trim()
|
||||||
@{file=$qwenFile; name='Qwen Code'},
|
if ($val -notin @('NEEDS CLARIFICATION','N/A')) { return $val }
|
||||||
@{file=$agentsFile; name='opencode'}
|
|
||||||
)) {
|
|
||||||
if (Test-Path $pair.file) { Update-AgentFile $pair.file $pair.name }
|
|
||||||
}
|
}
|
||||||
if (-not (Test-Path $claudeFile) -and -not (Test-Path $geminiFile) -and -not (Test-Path $copilotFile) -and -not (Test-Path $cursorFile) -and -not (Test-Path $qwenFile) -and -not (Test-Path $agentsFile)) {
|
} | Select-Object -First 1
|
||||||
Write-Output 'No agent context files found. Creating Claude Code context file by default.'
|
}
|
||||||
Update-AgentFile $claudeFile 'Claude Code'
|
|
||||||
|
function Parse-PlanData {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory=$true)]
|
||||||
|
[string]$PlanFile
|
||||||
|
)
|
||||||
|
if (-not (Test-Path $PlanFile)) { Write-Err "Plan file not found: $PlanFile"; return $false }
|
||||||
|
Write-Info "Parsing plan data from $PlanFile"
|
||||||
|
$script:NEW_LANG = Extract-PlanField -FieldPattern 'Language/Version' -PlanFile $PlanFile
|
||||||
|
$script:NEW_FRAMEWORK = Extract-PlanField -FieldPattern 'Primary Dependencies' -PlanFile $PlanFile
|
||||||
|
$script:NEW_DB = Extract-PlanField -FieldPattern 'Storage' -PlanFile $PlanFile
|
||||||
|
$script:NEW_PROJECT_TYPE = Extract-PlanField -FieldPattern 'Project Type' -PlanFile $PlanFile
|
||||||
|
|
||||||
|
if ($NEW_LANG) { Write-Info "Found language: $NEW_LANG" } else { Write-WarningMsg 'No language information found in plan' }
|
||||||
|
if ($NEW_FRAMEWORK) { Write-Info "Found framework: $NEW_FRAMEWORK" }
|
||||||
|
if ($NEW_DB -and $NEW_DB -ne 'N/A') { Write-Info "Found database: $NEW_DB" }
|
||||||
|
if ($NEW_PROJECT_TYPE) { Write-Info "Found project type: $NEW_PROJECT_TYPE" }
|
||||||
|
return $true
|
||||||
|
}
|
||||||
|
|
||||||
|
function Format-TechnologyStack {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory=$false)]
|
||||||
|
[string]$Lang,
|
||||||
|
[Parameter(Mandatory=$false)]
|
||||||
|
[string]$Framework
|
||||||
|
)
|
||||||
|
$parts = @()
|
||||||
|
if ($Lang -and $Lang -ne 'NEEDS CLARIFICATION') { $parts += $Lang }
|
||||||
|
if ($Framework -and $Framework -notin @('NEEDS CLARIFICATION','N/A')) { $parts += $Framework }
|
||||||
|
if (-not $parts) { return '' }
|
||||||
|
return ($parts -join ' + ')
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-ProjectStructure {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory=$false)]
|
||||||
|
[string]$ProjectType
|
||||||
|
)
|
||||||
|
if ($ProjectType -match 'web') { return "backend/`nfrontend/`ntests/" } else { return "src/`ntests/" }
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-CommandsForLanguage {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory=$false)]
|
||||||
|
[string]$Lang
|
||||||
|
)
|
||||||
|
switch -Regex ($Lang) {
|
||||||
|
'Python' { return "cd src; pytest; ruff check ." }
|
||||||
|
'Rust' { return "cargo test; cargo clippy" }
|
||||||
|
'JavaScript|TypeScript' { return "npm test; npm run lint" }
|
||||||
|
default { return "# Add commands for $Lang" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-LanguageConventions {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory=$false)]
|
||||||
|
[string]$Lang
|
||||||
|
)
|
||||||
|
if ($Lang) { "${Lang}: Follow standard conventions" } else { 'General: Follow standard conventions' }
|
||||||
|
}
|
||||||
|
|
||||||
|
function New-AgentFile {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory=$true)]
|
||||||
|
[string]$TargetFile,
|
||||||
|
[Parameter(Mandatory=$true)]
|
||||||
|
[string]$ProjectName,
|
||||||
|
[Parameter(Mandatory=$true)]
|
||||||
|
[datetime]$Date
|
||||||
|
)
|
||||||
|
if (-not (Test-Path $TEMPLATE_FILE)) { Write-Err "Template not found at $TEMPLATE_FILE"; return $false }
|
||||||
|
$temp = New-TemporaryFile
|
||||||
|
Copy-Item -LiteralPath $TEMPLATE_FILE -Destination $temp -Force
|
||||||
|
|
||||||
|
$projectStructure = Get-ProjectStructure -ProjectType $NEW_PROJECT_TYPE
|
||||||
|
$commands = Get-CommandsForLanguage -Lang $NEW_LANG
|
||||||
|
$languageConventions = Get-LanguageConventions -Lang $NEW_LANG
|
||||||
|
|
||||||
|
$escaped_lang = $NEW_LANG
|
||||||
|
$escaped_framework = $NEW_FRAMEWORK
|
||||||
|
$escaped_branch = $CURRENT_BRANCH
|
||||||
|
|
||||||
|
$content = Get-Content -LiteralPath $temp -Raw -Encoding utf8
|
||||||
|
$content = $content -replace '\[PROJECT NAME\]',$ProjectName
|
||||||
|
$content = $content -replace '\[DATE\]',$Date.ToString('yyyy-MM-dd')
|
||||||
|
|
||||||
|
# Build the technology stack string safely
|
||||||
|
$techStackForTemplate = ""
|
||||||
|
if ($escaped_lang -and $escaped_framework) {
|
||||||
|
$techStackForTemplate = "- $escaped_lang + $escaped_framework ($escaped_branch)"
|
||||||
|
} elseif ($escaped_lang) {
|
||||||
|
$techStackForTemplate = "- $escaped_lang ($escaped_branch)"
|
||||||
|
} elseif ($escaped_framework) {
|
||||||
|
$techStackForTemplate = "- $escaped_framework ($escaped_branch)"
|
||||||
|
}
|
||||||
|
|
||||||
|
$content = $content -replace '\[EXTRACTED FROM ALL PLAN.MD FILES\]',$techStackForTemplate
|
||||||
|
# For project structure we manually embed (keep newlines)
|
||||||
|
$escapedStructure = [Regex]::Escape($projectStructure)
|
||||||
|
$content = $content -replace '\[ACTUAL STRUCTURE FROM PLANS\]',$escapedStructure
|
||||||
|
# Replace escaped newlines placeholder after all replacements
|
||||||
|
$content = $content -replace '\[ONLY COMMANDS FOR ACTIVE TECHNOLOGIES\]',$commands
|
||||||
|
$content = $content -replace '\[LANGUAGE-SPECIFIC, ONLY FOR LANGUAGES IN USE\]',$languageConventions
|
||||||
|
|
||||||
|
# Build the recent changes string safely
|
||||||
|
$recentChangesForTemplate = ""
|
||||||
|
if ($escaped_lang -and $escaped_framework) {
|
||||||
|
$recentChangesForTemplate = "- ${escaped_branch}: Added ${escaped_lang} + ${escaped_framework}"
|
||||||
|
} elseif ($escaped_lang) {
|
||||||
|
$recentChangesForTemplate = "- ${escaped_branch}: Added ${escaped_lang}"
|
||||||
|
} elseif ($escaped_framework) {
|
||||||
|
$recentChangesForTemplate = "- ${escaped_branch}: Added ${escaped_framework}"
|
||||||
|
}
|
||||||
|
|
||||||
|
$content = $content -replace '\[LAST 3 FEATURES AND WHAT THEY ADDED\]',$recentChangesForTemplate
|
||||||
|
# Convert literal \n sequences introduced by Escape to real newlines
|
||||||
|
$content = $content -replace '\\n',[Environment]::NewLine
|
||||||
|
|
||||||
|
$parent = Split-Path -Parent $TargetFile
|
||||||
|
if (-not (Test-Path $parent)) { New-Item -ItemType Directory -Path $parent | Out-Null }
|
||||||
|
Set-Content -LiteralPath $TargetFile -Value $content -NoNewline -Encoding utf8
|
||||||
|
Remove-Item $temp -Force
|
||||||
|
return $true
|
||||||
|
}
|
||||||
|
|
||||||
|
function Update-ExistingAgentFile {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory=$true)]
|
||||||
|
[string]$TargetFile,
|
||||||
|
[Parameter(Mandatory=$true)]
|
||||||
|
[datetime]$Date
|
||||||
|
)
|
||||||
|
if (-not (Test-Path $TargetFile)) { return (New-AgentFile -TargetFile $TargetFile -ProjectName (Split-Path $REPO_ROOT -Leaf) -Date $Date) }
|
||||||
|
|
||||||
|
$techStack = Format-TechnologyStack -Lang $NEW_LANG -Framework $NEW_FRAMEWORK
|
||||||
|
$newTechEntries = @()
|
||||||
|
if ($techStack) {
|
||||||
|
$escapedTechStack = [Regex]::Escape($techStack)
|
||||||
|
if (-not (Select-String -Pattern $escapedTechStack -Path $TargetFile -Quiet)) {
|
||||||
|
$newTechEntries += "- $techStack ($CURRENT_BRANCH)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Default { Write-Error "ERROR: Unknown agent type '$AgentType'. Use: claude, gemini, copilot, cursor, qwen, opencode or leave empty for all."; exit 1 }
|
if ($NEW_DB -and $NEW_DB -notin @('N/A','NEEDS CLARIFICATION')) {
|
||||||
|
$escapedDB = [Regex]::Escape($NEW_DB)
|
||||||
|
if (-not (Select-String -Pattern $escapedDB -Path $TargetFile -Quiet)) {
|
||||||
|
$newTechEntries += "- $NEW_DB ($CURRENT_BRANCH)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$newChangeEntry = ''
|
||||||
|
if ($techStack) { $newChangeEntry = "- ${CURRENT_BRANCH}: Added ${techStack}" }
|
||||||
|
elseif ($NEW_DB -and $NEW_DB -notin @('N/A','NEEDS CLARIFICATION')) { $newChangeEntry = "- ${CURRENT_BRANCH}: Added ${NEW_DB}" }
|
||||||
|
|
||||||
|
$lines = Get-Content -LiteralPath $TargetFile -Encoding utf8
|
||||||
|
$output = New-Object System.Collections.Generic.List[string]
|
||||||
|
$inTech = $false; $inChanges = $false; $techAdded = $false; $changeAdded = $false; $existingChanges = 0
|
||||||
|
|
||||||
|
for ($i=0; $i -lt $lines.Count; $i++) {
|
||||||
|
$line = $lines[$i]
|
||||||
|
if ($line -eq '## Active Technologies') {
|
||||||
|
$output.Add($line)
|
||||||
|
$inTech = $true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ($inTech -and $line -match '^##\s') {
|
||||||
|
if (-not $techAdded -and $newTechEntries.Count -gt 0) { $newTechEntries | ForEach-Object { $output.Add($_) }; $techAdded = $true }
|
||||||
|
$output.Add($line); $inTech = $false; continue
|
||||||
|
}
|
||||||
|
if ($inTech -and [string]::IsNullOrWhiteSpace($line)) {
|
||||||
|
if (-not $techAdded -and $newTechEntries.Count -gt 0) { $newTechEntries | ForEach-Object { $output.Add($_) }; $techAdded = $true }
|
||||||
|
$output.Add($line); continue
|
||||||
|
}
|
||||||
|
if ($line -eq '## Recent Changes') {
|
||||||
|
$output.Add($line)
|
||||||
|
if ($newChangeEntry) { $output.Add($newChangeEntry); $changeAdded = $true }
|
||||||
|
$inChanges = $true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ($inChanges -and $line -match '^##\s') { $output.Add($line); $inChanges = $false; continue }
|
||||||
|
if ($inChanges -and $line -match '^- ') {
|
||||||
|
if ($existingChanges -lt 2) { $output.Add($line); $existingChanges++ }
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ($line -match '\*\*Last updated\*\*: .*\d{4}-\d{2}-\d{2}') {
|
||||||
|
$output.Add(($line -replace '\d{4}-\d{2}-\d{2}',$Date.ToString('yyyy-MM-dd')))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
$output.Add($line)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Post-loop check: if we're still in the Active Technologies section and haven't added new entries
|
||||||
|
if ($inTech -and -not $techAdded -and $newTechEntries.Count -gt 0) {
|
||||||
|
$newTechEntries | ForEach-Object { $output.Add($_) }
|
||||||
|
}
|
||||||
|
|
||||||
|
Set-Content -LiteralPath $TargetFile -Value ($output -join [Environment]::NewLine) -Encoding utf8
|
||||||
|
return $true
|
||||||
}
|
}
|
||||||
|
|
||||||
Write-Output ''
|
function Update-AgentFile {
|
||||||
Write-Output 'Summary of changes:'
|
param(
|
||||||
if ($newLang) { Write-Output "- Added language: $newLang" }
|
[Parameter(Mandatory=$true)]
|
||||||
if ($newFramework) { Write-Output "- Added framework: $newFramework" }
|
[string]$TargetFile,
|
||||||
if ($newDb -and $newDb -ne 'N/A') { Write-Output "- Added database: $newDb" }
|
[Parameter(Mandatory=$true)]
|
||||||
|
[string]$AgentName
|
||||||
|
)
|
||||||
|
if (-not $TargetFile -or -not $AgentName) { Write-Err 'Update-AgentFile requires TargetFile and AgentName'; return $false }
|
||||||
|
Write-Info "Updating $AgentName context file: $TargetFile"
|
||||||
|
$projectName = Split-Path $REPO_ROOT -Leaf
|
||||||
|
$date = Get-Date
|
||||||
|
|
||||||
|
$dir = Split-Path -Parent $TargetFile
|
||||||
|
if (-not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir | Out-Null }
|
||||||
|
|
||||||
|
if (-not (Test-Path $TargetFile)) {
|
||||||
|
if (New-AgentFile -TargetFile $TargetFile -ProjectName $projectName -Date $date) { Write-Success "Created new $AgentName context file" } else { Write-Err 'Failed to create new agent file'; return $false }
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
if (Update-ExistingAgentFile -TargetFile $TargetFile -Date $date) { Write-Success "Updated existing $AgentName context file" } else { Write-Err 'Failed to update agent file'; return $false }
|
||||||
|
} catch {
|
||||||
|
Write-Err "Cannot access or update existing file: $TargetFile. $_"
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $true
|
||||||
|
}
|
||||||
|
|
||||||
|
function Update-SpecificAgent {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory=$true)]
|
||||||
|
[string]$Type
|
||||||
|
)
|
||||||
|
switch ($Type) {
|
||||||
|
'claude' { Update-AgentFile -TargetFile $CLAUDE_FILE -AgentName 'Claude Code' }
|
||||||
|
'gemini' { Update-AgentFile -TargetFile $GEMINI_FILE -AgentName 'Gemini CLI' }
|
||||||
|
'copilot' { Update-AgentFile -TargetFile $COPILOT_FILE -AgentName 'GitHub Copilot' }
|
||||||
|
'cursor-agent' { Update-AgentFile -TargetFile $CURSOR_FILE -AgentName 'Cursor IDE' }
|
||||||
|
'qwen' { Update-AgentFile -TargetFile $QWEN_FILE -AgentName 'Qwen Code' }
|
||||||
|
'opencode' { Update-AgentFile -TargetFile $AGENTS_FILE -AgentName 'opencode' }
|
||||||
|
'codex' { Update-AgentFile -TargetFile $AGENTS_FILE -AgentName 'Codex CLI' }
|
||||||
|
'windsurf' { Update-AgentFile -TargetFile $WINDSURF_FILE -AgentName 'Windsurf' }
|
||||||
|
'kilocode' { Update-AgentFile -TargetFile $KILOCODE_FILE -AgentName 'Kilo Code' }
|
||||||
|
'auggie' { Update-AgentFile -TargetFile $AUGGIE_FILE -AgentName 'Auggie CLI' }
|
||||||
|
'roo' { Update-AgentFile -TargetFile $ROO_FILE -AgentName 'Roo Code' }
|
||||||
|
'codebuddy' { Update-AgentFile -TargetFile $CODEBUDDY_FILE -AgentName 'CodeBuddy CLI' }
|
||||||
|
'qoder' { Update-AgentFile -TargetFile $QODER_FILE -AgentName 'Qoder CLI' }
|
||||||
|
'amp' { Update-AgentFile -TargetFile $AMP_FILE -AgentName 'Amp' }
|
||||||
|
'shai' { Update-AgentFile -TargetFile $SHAI_FILE -AgentName 'SHAI' }
|
||||||
|
'q' { Update-AgentFile -TargetFile $Q_FILE -AgentName 'Amazon Q Developer CLI' }
|
||||||
|
'agy' { Update-AgentFile -TargetFile $AGY_FILE -AgentName 'Antigravity' }
|
||||||
|
'bob' { Update-AgentFile -TargetFile $BOB_FILE -AgentName 'IBM Bob' }
|
||||||
|
default { Write-Err "Unknown agent type '$Type'"; Write-Err 'Expected: claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|roo|codebuddy|amp|shai|q|agy|bob|qoder'; return $false }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Update-AllExistingAgents {
|
||||||
|
$found = $false
|
||||||
|
$ok = $true
|
||||||
|
if (Test-Path $CLAUDE_FILE) { if (-not (Update-AgentFile -TargetFile $CLAUDE_FILE -AgentName 'Claude Code')) { $ok = $false }; $found = $true }
|
||||||
|
if (Test-Path $GEMINI_FILE) { if (-not (Update-AgentFile -TargetFile $GEMINI_FILE -AgentName 'Gemini CLI')) { $ok = $false }; $found = $true }
|
||||||
|
if (Test-Path $COPILOT_FILE) { if (-not (Update-AgentFile -TargetFile $COPILOT_FILE -AgentName 'GitHub Copilot')) { $ok = $false }; $found = $true }
|
||||||
|
if (Test-Path $CURSOR_FILE) { if (-not (Update-AgentFile -TargetFile $CURSOR_FILE -AgentName 'Cursor IDE')) { $ok = $false }; $found = $true }
|
||||||
|
if (Test-Path $QWEN_FILE) { if (-not (Update-AgentFile -TargetFile $QWEN_FILE -AgentName 'Qwen Code')) { $ok = $false }; $found = $true }
|
||||||
|
if (Test-Path $AGENTS_FILE) { if (-not (Update-AgentFile -TargetFile $AGENTS_FILE -AgentName 'Codex/opencode')) { $ok = $false }; $found = $true }
|
||||||
|
if (Test-Path $WINDSURF_FILE) { if (-not (Update-AgentFile -TargetFile $WINDSURF_FILE -AgentName 'Windsurf')) { $ok = $false }; $found = $true }
|
||||||
|
if (Test-Path $KILOCODE_FILE) { if (-not (Update-AgentFile -TargetFile $KILOCODE_FILE -AgentName 'Kilo Code')) { $ok = $false }; $found = $true }
|
||||||
|
if (Test-Path $AUGGIE_FILE) { if (-not (Update-AgentFile -TargetFile $AUGGIE_FILE -AgentName 'Auggie CLI')) { $ok = $false }; $found = $true }
|
||||||
|
if (Test-Path $ROO_FILE) { if (-not (Update-AgentFile -TargetFile $ROO_FILE -AgentName 'Roo Code')) { $ok = $false }; $found = $true }
|
||||||
|
if (Test-Path $CODEBUDDY_FILE) { if (-not (Update-AgentFile -TargetFile $CODEBUDDY_FILE -AgentName 'CodeBuddy CLI')) { $ok = $false }; $found = $true }
|
||||||
|
if (Test-Path $QODER_FILE) { if (-not (Update-AgentFile -TargetFile $QODER_FILE -AgentName 'Qoder CLI')) { $ok = $false }; $found = $true }
|
||||||
|
if (Test-Path $SHAI_FILE) { if (-not (Update-AgentFile -TargetFile $SHAI_FILE -AgentName 'SHAI')) { $ok = $false }; $found = $true }
|
||||||
|
if (Test-Path $Q_FILE) { if (-not (Update-AgentFile -TargetFile $Q_FILE -AgentName 'Amazon Q Developer CLI')) { $ok = $false }; $found = $true }
|
||||||
|
if (Test-Path $AGY_FILE) { if (-not (Update-AgentFile -TargetFile $AGY_FILE -AgentName 'Antigravity')) { $ok = $false }; $found = $true }
|
||||||
|
if (Test-Path $BOB_FILE) { if (-not (Update-AgentFile -TargetFile $BOB_FILE -AgentName 'IBM Bob')) { $ok = $false }; $found = $true }
|
||||||
|
if (-not $found) {
|
||||||
|
Write-Info 'No existing agent files found, creating default Claude file...'
|
||||||
|
if (-not (Update-AgentFile -TargetFile $CLAUDE_FILE -AgentName 'Claude Code')) { $ok = $false }
|
||||||
|
}
|
||||||
|
return $ok
|
||||||
|
}
|
||||||
|
|
||||||
|
function Print-Summary {
|
||||||
|
Write-Host ''
|
||||||
|
Write-Info 'Summary of changes:'
|
||||||
|
if ($NEW_LANG) { Write-Host " - Added language: $NEW_LANG" }
|
||||||
|
if ($NEW_FRAMEWORK) { Write-Host " - Added framework: $NEW_FRAMEWORK" }
|
||||||
|
if ($NEW_DB -and $NEW_DB -ne 'N/A') { Write-Host " - Added database: $NEW_DB" }
|
||||||
|
Write-Host ''
|
||||||
|
Write-Info 'Usage: ./update-agent-context.ps1 [-AgentType claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|roo|codebuddy|amp|shai|q|agy|bob|qoder]'
|
||||||
|
}
|
||||||
|
|
||||||
|
function Main {
|
||||||
|
Validate-Environment
|
||||||
|
Write-Info "=== Updating agent context files for feature $CURRENT_BRANCH ==="
|
||||||
|
if (-not (Parse-PlanData -PlanFile $NEW_PLAN)) { Write-Err 'Failed to parse plan data'; exit 1 }
|
||||||
|
$success = $true
|
||||||
|
if ($AgentType) {
|
||||||
|
Write-Info "Updating specific agent: $AgentType"
|
||||||
|
if (-not (Update-SpecificAgent -Type $AgentType)) { $success = $false }
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Info 'No agent specified, updating all existing agent files...'
|
||||||
|
if (-not (Update-AllExistingAgents)) { $success = $false }
|
||||||
|
}
|
||||||
|
Print-Summary
|
||||||
|
if ($success) { Write-Success 'Agent context update completed successfully'; exit 0 } else { Write-Err 'Agent context update completed with errors'; exit 1 }
|
||||||
|
}
|
||||||
|
|
||||||
|
Main
|
||||||
|
|
||||||
Write-Output ''
|
|
||||||
Write-Output 'Usage: ./update-agent-context.ps1 [claude|gemini|copilot|cursor|qwen|opencode]'
|
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ The key is treating specifications as the source of truth, with code as the gene
|
|||||||
|
|
||||||
The SDD methodology is significantly enhanced through three powerful commands that automate the specification → planning → tasking workflow:
|
The SDD methodology is significantly enhanced through three powerful commands that automate the specification → planning → tasking workflow:
|
||||||
|
|
||||||
### The `/specify` Command
|
### The `/speckit.specify` Command
|
||||||
|
|
||||||
This command transforms a simple feature description (the user-prompt) into a complete, structured specification with automatic repository management:
|
This command transforms a simple feature description (the user-prompt) into a complete, structured specification with automatic repository management:
|
||||||
|
|
||||||
@@ -83,7 +83,7 @@ This command transforms a simple feature description (the user-prompt) into a co
|
|||||||
3. **Template-Based Generation**: Copies and customizes the feature specification template with your requirements
|
3. **Template-Based Generation**: Copies and customizes the feature specification template with your requirements
|
||||||
4. **Directory Structure**: Creates the proper `specs/[branch-name]/` structure for all related documents
|
4. **Directory Structure**: Creates the proper `specs/[branch-name]/` structure for all related documents
|
||||||
|
|
||||||
### The `/plan` Command
|
### The `/speckit.plan` Command
|
||||||
|
|
||||||
Once a feature specification exists, this command creates a comprehensive implementation plan:
|
Once a feature specification exists, this command creates a comprehensive implementation plan:
|
||||||
|
|
||||||
@@ -93,7 +93,7 @@ Once a feature specification exists, this command creates a comprehensive implem
|
|||||||
4. **Detailed Documentation**: Generates supporting documents for data models, API contracts, and test scenarios
|
4. **Detailed Documentation**: Generates supporting documents for data models, API contracts, and test scenarios
|
||||||
5. **Quickstart Validation**: Produces a quickstart guide capturing key validation scenarios
|
5. **Quickstart Validation**: Produces a quickstart guide capturing key validation scenarios
|
||||||
|
|
||||||
### The `/tasks` Command
|
### The `/speckit.tasks` Command
|
||||||
|
|
||||||
After a plan is created, this command analyzes the plan and related design documents to generate an executable task list:
|
After a plan is created, this command analyzes the plan and related design documents to generate an executable task list:
|
||||||
|
|
||||||
@@ -121,7 +121,7 @@ Total: ~12 hours of documentation work
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Step 1: Create the feature specification (5 minutes)
|
# Step 1: Create the feature specification (5 minutes)
|
||||||
/specify Real-time chat system with message history and user presence
|
/speckit.specify Real-time chat system with message history and user presence
|
||||||
|
|
||||||
# This automatically:
|
# This automatically:
|
||||||
# - Creates branch "003-chat-system"
|
# - Creates branch "003-chat-system"
|
||||||
@@ -129,10 +129,10 @@ Total: ~12 hours of documentation work
|
|||||||
# - Populates it with structured requirements
|
# - Populates it with structured requirements
|
||||||
|
|
||||||
# Step 2: Generate implementation plan (5 minutes)
|
# Step 2: Generate implementation plan (5 minutes)
|
||||||
/plan WebSocket for real-time messaging, PostgreSQL for history, Redis for presence
|
/speckit.plan WebSocket for real-time messaging, PostgreSQL for history, Redis for presence
|
||||||
|
|
||||||
# Step 3: Generate executable tasks (5 minutes)
|
# Step 3: Generate executable tasks (5 minutes)
|
||||||
/tasks
|
/speckit.tasks
|
||||||
|
|
||||||
# This automatically creates:
|
# This automatically creates:
|
||||||
# - specs/003-chat-system/plan.md
|
# - specs/003-chat-system/plan.md
|
||||||
@@ -195,6 +195,7 @@ The templates include comprehensive checklists that act as "unit tests" for the
|
|||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
### Requirement Completeness
|
### Requirement Completeness
|
||||||
|
|
||||||
- [ ] No [NEEDS CLARIFICATION] markers remain
|
- [ ] No [NEEDS CLARIFICATION] markers remain
|
||||||
- [ ] Requirements are testable and unambiguous
|
- [ ] Requirements are testable and unambiguous
|
||||||
- [ ] Success criteria are measurable
|
- [ ] Success criteria are measurable
|
||||||
@@ -208,10 +209,14 @@ The implementation plan template enforces architectural principles through phase
|
|||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
### Phase -1: Pre-Implementation Gates
|
### Phase -1: Pre-Implementation Gates
|
||||||
|
|
||||||
#### Simplicity Gate (Article VII)
|
#### Simplicity Gate (Article VII)
|
||||||
|
|
||||||
- [ ] Using ≤3 projects?
|
- [ ] Using ≤3 projects?
|
||||||
- [ ] No future-proofing?
|
- [ ] No future-proofing?
|
||||||
|
|
||||||
#### Anti-Abstraction Gate (Article VIII)
|
#### Anti-Abstraction Gate (Article VIII)
|
||||||
|
|
||||||
- [ ] Using framework directly?
|
- [ ] Using framework directly?
|
||||||
- [ ] Single model representation?
|
- [ ] Single model representation?
|
||||||
```
|
```
|
||||||
@@ -347,15 +352,19 @@ The implementation plan template operationalizes these articles through concrete
|
|||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
### Phase -1: Pre-Implementation Gates
|
### Phase -1: Pre-Implementation Gates
|
||||||
|
|
||||||
#### Simplicity Gate (Article VII)
|
#### Simplicity Gate (Article VII)
|
||||||
|
|
||||||
- [ ] Using ≤3 projects?
|
- [ ] Using ≤3 projects?
|
||||||
- [ ] No future-proofing?
|
- [ ] No future-proofing?
|
||||||
|
|
||||||
#### Anti-Abstraction Gate (Article VIII)
|
#### Anti-Abstraction Gate (Article VIII)
|
||||||
|
|
||||||
- [ ] Using framework directly?
|
- [ ] Using framework directly?
|
||||||
- [ ] Single model representation?
|
- [ ] Single model representation?
|
||||||
|
|
||||||
#### Integration-First Gate (Article IX)
|
#### Integration-First Gate (Article IX)
|
||||||
|
|
||||||
- [ ] Contracts defined?
|
- [ ] Contracts defined?
|
||||||
- [ ] Contract tests written?
|
- [ ] Contract tests written?
|
||||||
```
|
```
|
||||||
|
|||||||
8
spec-kit.code-workspace
Normal file
8
spec-kit.code-workspace
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"folders": [
|
||||||
|
{
|
||||||
|
"path": "."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
1785
src/specify_cli/extensions.py
Normal file
1785
src/specify_cli/extensions.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -3,20 +3,25 @@
|
|||||||
Auto-generated from all feature plans. Last updated: [DATE]
|
Auto-generated from all feature plans. Last updated: [DATE]
|
||||||
|
|
||||||
## Active Technologies
|
## Active Technologies
|
||||||
|
|
||||||
[EXTRACTED FROM ALL PLAN.MD FILES]
|
[EXTRACTED FROM ALL PLAN.MD FILES]
|
||||||
|
|
||||||
## Project Structure
|
## Project Structure
|
||||||
```
|
|
||||||
|
```text
|
||||||
[ACTUAL STRUCTURE FROM PLANS]
|
[ACTUAL STRUCTURE FROM PLANS]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
[ONLY COMMANDS FOR ACTIVE TECHNOLOGIES]
|
[ONLY COMMANDS FOR ACTIVE TECHNOLOGIES]
|
||||||
|
|
||||||
## Code Style
|
## Code Style
|
||||||
|
|
||||||
[LANGUAGE-SPECIFIC, ONLY FOR LANGUAGES IN USE]
|
[LANGUAGE-SPECIFIC, ONLY FOR LANGUAGES IN USE]
|
||||||
|
|
||||||
## Recent Changes
|
## Recent Changes
|
||||||
|
|
||||||
[LAST 3 FEATURES AND WHAT THEY ADDED]
|
[LAST 3 FEATURES AND WHAT THEY ADDED]
|
||||||
|
|
||||||
<!-- MANUAL ADDITIONS START -->
|
<!-- MANUAL ADDITIONS START -->
|
||||||
|
|||||||
40
templates/checklist-template.md
Normal file
40
templates/checklist-template.md
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# [CHECKLIST TYPE] Checklist: [FEATURE NAME]
|
||||||
|
|
||||||
|
**Purpose**: [Brief description of what this checklist covers]
|
||||||
|
**Created**: [DATE]
|
||||||
|
**Feature**: [Link to spec.md or relevant documentation]
|
||||||
|
|
||||||
|
**Note**: This checklist is generated by the `/speckit.checklist` command based on feature context and requirements.
|
||||||
|
|
||||||
|
<!--
|
||||||
|
============================================================================
|
||||||
|
IMPORTANT: The checklist items below are SAMPLE ITEMS for illustration only.
|
||||||
|
|
||||||
|
The /speckit.checklist command MUST replace these with actual items based on:
|
||||||
|
- User's specific checklist request
|
||||||
|
- Feature requirements from spec.md
|
||||||
|
- Technical context from plan.md
|
||||||
|
- Implementation details from tasks.md
|
||||||
|
|
||||||
|
DO NOT keep these sample items in the generated checklist file.
|
||||||
|
============================================================================
|
||||||
|
-->
|
||||||
|
|
||||||
|
## [Category 1]
|
||||||
|
|
||||||
|
- [ ] CHK001 First checklist item with clear action
|
||||||
|
- [ ] CHK002 Second checklist item
|
||||||
|
- [ ] CHK003 Third checklist item
|
||||||
|
|
||||||
|
## [Category 2]
|
||||||
|
|
||||||
|
- [ ] CHK004 Another category item
|
||||||
|
- [ ] CHK005 Item with specific criteria
|
||||||
|
- [ ] CHK006 Final item in this category
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Check items off as completed: `[x]`
|
||||||
|
- Add comments or findings inline
|
||||||
|
- Link to relevant resources or documentation
|
||||||
|
- Items are numbered sequentially for easy reference
|
||||||
187
templates/commands/analyze.md
Normal file
187
templates/commands/analyze.md
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
---
|
||||||
|
description: Perform a non-destructive cross-artifact consistency and quality analysis across spec.md, plan.md, and tasks.md after task generation.
|
||||||
|
scripts:
|
||||||
|
sh: scripts/bash/check-prerequisites.sh --json --require-tasks --include-tasks
|
||||||
|
ps: scripts/powershell/check-prerequisites.ps1 -Json -RequireTasks -IncludeTasks
|
||||||
|
---
|
||||||
|
|
||||||
|
## User Input
|
||||||
|
|
||||||
|
```text
|
||||||
|
$ARGUMENTS
|
||||||
|
```
|
||||||
|
|
||||||
|
You **MUST** consider the user input before proceeding (if not empty).
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Identify inconsistencies, duplications, ambiguities, and underspecified items across the three core artifacts (`spec.md`, `plan.md`, `tasks.md`) before implementation. This command MUST run only after `/speckit.tasks` has successfully produced a complete `tasks.md`.
|
||||||
|
|
||||||
|
## Operating Constraints
|
||||||
|
|
||||||
|
**STRICTLY READ-ONLY**: Do **not** modify any files. Output a structured analysis report. Offer an optional remediation plan (user must explicitly approve before any follow-up editing commands would be invoked manually).
|
||||||
|
|
||||||
|
**Constitution Authority**: The project constitution (`/memory/constitution.md`) is **non-negotiable** within this analysis scope. Constitution conflicts are automatically CRITICAL and require adjustment of the spec, plan, or tasks—not dilution, reinterpretation, or silent ignoring of the principle. If a principle itself needs to change, that must occur in a separate, explicit constitution update outside `/speckit.analyze`.
|
||||||
|
|
||||||
|
## Execution Steps
|
||||||
|
|
||||||
|
### 1. Initialize Analysis Context
|
||||||
|
|
||||||
|
Run `{SCRIPT}` once from repo root and parse JSON for FEATURE_DIR and AVAILABLE_DOCS. Derive absolute paths:
|
||||||
|
|
||||||
|
- SPEC = FEATURE_DIR/spec.md
|
||||||
|
- PLAN = FEATURE_DIR/plan.md
|
||||||
|
- TASKS = FEATURE_DIR/tasks.md
|
||||||
|
|
||||||
|
Abort with an error message if any required file is missing (instruct the user to run missing prerequisite command).
|
||||||
|
For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot").
|
||||||
|
|
||||||
|
### 2. Load Artifacts (Progressive Disclosure)
|
||||||
|
|
||||||
|
Load only the minimal necessary context from each artifact:
|
||||||
|
|
||||||
|
**From spec.md:**
|
||||||
|
|
||||||
|
- Overview/Context
|
||||||
|
- Functional Requirements
|
||||||
|
- Non-Functional Requirements
|
||||||
|
- User Stories
|
||||||
|
- Edge Cases (if present)
|
||||||
|
|
||||||
|
**From plan.md:**
|
||||||
|
|
||||||
|
- Architecture/stack choices
|
||||||
|
- Data Model references
|
||||||
|
- Phases
|
||||||
|
- Technical constraints
|
||||||
|
|
||||||
|
**From tasks.md:**
|
||||||
|
|
||||||
|
- Task IDs
|
||||||
|
- Descriptions
|
||||||
|
- Phase grouping
|
||||||
|
- Parallel markers [P]
|
||||||
|
- Referenced file paths
|
||||||
|
|
||||||
|
**From constitution:**
|
||||||
|
|
||||||
|
- Load `/memory/constitution.md` for principle validation
|
||||||
|
|
||||||
|
### 3. Build Semantic Models
|
||||||
|
|
||||||
|
Create internal representations (do not include raw artifacts in output):
|
||||||
|
|
||||||
|
- **Requirements inventory**: Each functional + non-functional requirement with a stable key (derive slug based on imperative phrase; e.g., "User can upload file" → `user-can-upload-file`)
|
||||||
|
- **User story/action inventory**: Discrete user actions with acceptance criteria
|
||||||
|
- **Task coverage mapping**: Map each task to one or more requirements or stories (inference by keyword / explicit reference patterns like IDs or key phrases)
|
||||||
|
- **Constitution rule set**: Extract principle names and MUST/SHOULD normative statements
|
||||||
|
|
||||||
|
### 4. Detection Passes (Token-Efficient Analysis)
|
||||||
|
|
||||||
|
Focus on high-signal findings. Limit to 50 findings total; aggregate remainder in overflow summary.
|
||||||
|
|
||||||
|
#### A. Duplication Detection
|
||||||
|
|
||||||
|
- Identify near-duplicate requirements
|
||||||
|
- Mark lower-quality phrasing for consolidation
|
||||||
|
|
||||||
|
#### B. Ambiguity Detection
|
||||||
|
|
||||||
|
- Flag vague adjectives (fast, scalable, secure, intuitive, robust) lacking measurable criteria
|
||||||
|
- Flag unresolved placeholders (TODO, TKTK, ???, `<placeholder>`, etc.)
|
||||||
|
|
||||||
|
#### C. Underspecification
|
||||||
|
|
||||||
|
- Requirements with verbs but missing object or measurable outcome
|
||||||
|
- User stories missing acceptance criteria alignment
|
||||||
|
- Tasks referencing files or components not defined in spec/plan
|
||||||
|
|
||||||
|
#### D. Constitution Alignment
|
||||||
|
|
||||||
|
- Any requirement or plan element conflicting with a MUST principle
|
||||||
|
- Missing mandated sections or quality gates from constitution
|
||||||
|
|
||||||
|
#### E. Coverage Gaps
|
||||||
|
|
||||||
|
- Requirements with zero associated tasks
|
||||||
|
- Tasks with no mapped requirement/story
|
||||||
|
- Non-functional requirements not reflected in tasks (e.g., performance, security)
|
||||||
|
|
||||||
|
#### F. Inconsistency
|
||||||
|
|
||||||
|
- Terminology drift (same concept named differently across files)
|
||||||
|
- Data entities referenced in plan but absent in spec (or vice versa)
|
||||||
|
- Task ordering contradictions (e.g., integration tasks before foundational setup tasks without dependency note)
|
||||||
|
- Conflicting requirements (e.g., one requires Next.js while other specifies Vue)
|
||||||
|
|
||||||
|
### 5. Severity Assignment
|
||||||
|
|
||||||
|
Use this heuristic to prioritize findings:
|
||||||
|
|
||||||
|
- **CRITICAL**: Violates constitution MUST, missing core spec artifact, or requirement with zero coverage that blocks baseline functionality
|
||||||
|
- **HIGH**: Duplicate or conflicting requirement, ambiguous security/performance attribute, untestable acceptance criterion
|
||||||
|
- **MEDIUM**: Terminology drift, missing non-functional task coverage, underspecified edge case
|
||||||
|
- **LOW**: Style/wording improvements, minor redundancy not affecting execution order
|
||||||
|
|
||||||
|
### 6. Produce Compact Analysis Report
|
||||||
|
|
||||||
|
Output a Markdown report (no file writes) with the following structure:
|
||||||
|
|
||||||
|
## Specification Analysis Report
|
||||||
|
|
||||||
|
| ID | Category | Severity | Location(s) | Summary | Recommendation |
|
||||||
|
|----|----------|----------|-------------|---------|----------------|
|
||||||
|
| A1 | Duplication | HIGH | spec.md:L120-134 | Two similar requirements ... | Merge phrasing; keep clearer version |
|
||||||
|
|
||||||
|
(Add one row per finding; generate stable IDs prefixed by category initial.)
|
||||||
|
|
||||||
|
**Coverage Summary Table:**
|
||||||
|
|
||||||
|
| Requirement Key | Has Task? | Task IDs | Notes |
|
||||||
|
|-----------------|-----------|----------|-------|
|
||||||
|
|
||||||
|
**Constitution Alignment Issues:** (if any)
|
||||||
|
|
||||||
|
**Unmapped Tasks:** (if any)
|
||||||
|
|
||||||
|
|||||||