specweave 0.16.11 ā 0.16.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +11 -6
- package/bin/specweave.js +9 -0
- package/dist/cli/commands/revert-wip-limit.js +60 -0
- package/dist/plugins/specweave/lib/hooks/sync-living-docs.d.ts.map +1 -1
- package/dist/plugins/specweave/lib/hooks/sync-living-docs.js +38 -2
- package/dist/plugins/specweave/lib/hooks/sync-living-docs.js.map +1 -1
- package/dist/src/cli/commands/migrate-to-profiles.d.ts +32 -0
- package/dist/src/cli/commands/migrate-to-profiles.d.ts.map +1 -1
- package/dist/src/cli/commands/migrate-to-profiles.js +8 -6
- package/dist/src/cli/commands/migrate-to-profiles.js.map +1 -1
- package/dist/src/cli/commands/revert-wip-limit.d.ts +8 -0
- package/dist/src/cli/commands/revert-wip-limit.d.ts.map +1 -0
- package/dist/src/cli/commands/revert-wip-limit.js +61 -0
- package/dist/src/cli/commands/revert-wip-limit.js.map +1 -0
- package/dist/src/cli/helpers/issue-tracker/ado.d.ts.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/ado.js +5 -0
- package/dist/src/cli/helpers/issue-tracker/ado.js.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/jira.d.ts.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/jira.js +5 -0
- package/dist/src/cli/helpers/issue-tracker/jira.js.map +1 -1
- package/dist/src/core/increment/metadata-manager.d.ts.map +1 -1
- package/dist/src/core/increment/metadata-manager.js +6 -0
- package/dist/src/core/increment/metadata-manager.js.map +1 -1
- package/dist/src/core/repo-structure/setup-state-manager.d.ts +7 -0
- package/dist/src/core/repo-structure/setup-state-manager.d.ts.map +1 -1
- package/dist/src/core/repo-structure/setup-state-manager.js +28 -2
- package/dist/src/core/repo-structure/setup-state-manager.js.map +1 -1
- package/dist/src/utils/external-resource-validator.d.ts +6 -0
- package/dist/src/utils/external-resource-validator.d.ts.map +1 -1
- package/dist/src/utils/external-resource-validator.js +298 -57
- package/dist/src/utils/external-resource-validator.js.map +1 -1
- package/package.json +1 -1
- package/plugins/specweave/.claude-plugin/plugin.json +2 -1
- package/plugins/specweave/commands/revert-wip-limit.md +82 -0
- package/plugins/specweave/hooks/hooks.json +10 -0
- package/plugins/specweave/hooks/lib/migrate-increment-work.sh +245 -0
- package/plugins/specweave/hooks/post-increment-planning.sh +26 -40
- package/plugins/specweave/hooks/user-prompt-submit.sh +119 -12
- package/plugins/specweave/lib/hooks/sync-living-docs.js +113 -129
- package/plugins/specweave/lib/hooks/sync-living-docs.ts +46 -2
- package/plugins/specweave-ado/.claude-plugin/plugin.json +2 -1
- package/plugins/specweave-ado/skills/ado-resource-validator/SKILL.md +27 -1
- package/plugins/specweave-github/.claude-plugin/plugin.json +2 -1
- package/plugins/specweave-jira/.claude-plugin/plugin.json +2 -1
- package/plugins/specweave-jira/skills/jira-resource-validator/SKILL.md +31 -1
- package/plugins/specweave-release/.claude-plugin/plugin.json +2 -1
- package/dist/core/increment/metadata-manager.js +0 -335
|
@@ -102,10 +102,23 @@ EOF
|
|
|
102
102
|
if [[ -n "$INCOMPLETE_INCREMENTS" ]]; then
|
|
103
103
|
COUNT=$(echo "$INCOMPLETE_INCREMENTS" | wc -l | xargs)
|
|
104
104
|
|
|
105
|
+
# Get incomplete task count for migration guidance
|
|
106
|
+
MIGRATION_SCRIPT="$(dirname "${BASH_SOURCE[0]}")/lib/migrate-increment-work.sh"
|
|
107
|
+
INCOMPLETE_TASKS=""
|
|
108
|
+
|
|
109
|
+
for increment in $INCOMPLETE_INCREMENTS; do
|
|
110
|
+
if [[ -x "$MIGRATION_SCRIPT" ]]; then
|
|
111
|
+
TASK_COUNT=$("$MIGRATION_SCRIPT" count-incomplete "$increment" 2>/dev/null || echo "?")
|
|
112
|
+
INCOMPLETE_TASKS="${INCOMPLETE_TASKS}\n - $increment ($TASK_COUNT incomplete tasks)"
|
|
113
|
+
else
|
|
114
|
+
INCOMPLETE_TASKS="${INCOMPLETE_TASKS}\n - $increment"
|
|
115
|
+
fi
|
|
116
|
+
done
|
|
117
|
+
|
|
105
118
|
cat <<EOF
|
|
106
119
|
{
|
|
107
120
|
"decision": "block",
|
|
108
|
-
"reason": "ā Cannot create new increment! You have $COUNT incomplete increment(s):\n\n
|
|
121
|
+
"reason": "ā Cannot create new increment! You have $COUNT incomplete increment(s):$INCOMPLETE_TASKS\n\nš” **SMART MIGRATION OPTIONS:**\n\n1ļøā£ **Transfer Work** (Recommended)\n Move incomplete tasks to new increment:\n \`\`\`bash\n # After creating new increment, run:\n bash plugins/specweave/hooks/lib/migrate-increment-work.sh transfer <old-id> <new-id>\n \`\`\`\n ā
Clean closure + work continues\n\n2ļøā£ **Adjust WIP Limit** (Emergency Only)\n Temporarily allow 3 active increments:\n \`\`\`bash\n bash plugins/specweave/hooks/lib/migrate-increment-work.sh adjust-wip 3\n \`\`\`\n ā ļø 20% productivity cost, revert ASAP\n\n3ļøā£ **Force-Close** (Quick Fix)\n Mark increment as complete (work lost):\n \`\`\`bash\n bash plugins/specweave/hooks/lib/migrate-increment-work.sh force-close <increment-id>\n \`\`\`\n ā ļø Incomplete work NOT transferred!\n\nš **Traditional Options:**\n - /specweave:done <id> # Complete properly\n - /specweave:pause <id> # Pause for later\n - /specweave:abandon <id> # Abandon if obsolete\n\nā¹ļø The discipline exists for a reason:\n ā Prevents scope creep\n ā Ensures completions are tracked\n ā Maintains living docs accuracy\n ā Keeps work focused"
|
|
109
122
|
}
|
|
110
123
|
EOF
|
|
111
124
|
exit 0
|
|
@@ -114,6 +127,76 @@ EOF
|
|
|
114
127
|
fi
|
|
115
128
|
fi
|
|
116
129
|
|
|
130
|
+
# ==============================================================================
|
|
131
|
+
# PRE-FLIGHT SYNC CHECK: Ensure living docs are fresh before operations
|
|
132
|
+
# ==============================================================================
|
|
133
|
+
|
|
134
|
+
# Detect increment operations that need fresh data
|
|
135
|
+
if echo "$PROMPT" | grep -qE "/(specweave:)?(done|validate|progress|do)"; then
|
|
136
|
+
# Extract increment ID from prompt (if provided)
|
|
137
|
+
INCREMENT_ID=$(echo "$PROMPT" | grep -oE "[0-9]{4}[a-z0-9-]*" | head -1)
|
|
138
|
+
|
|
139
|
+
# If no ID in prompt, try to find active increment
|
|
140
|
+
if [[ -z "$INCREMENT_ID" ]] && [[ -d ".specweave/increments" ]]; then
|
|
141
|
+
INCREMENT_ID=$(find .specweave/increments -mindepth 1 -maxdepth 1 -type d | while read increment_dir; do
|
|
142
|
+
metadata="$increment_dir/metadata.json"
|
|
143
|
+
if [[ -f "$metadata" ]]; then
|
|
144
|
+
status=$(node -e "
|
|
145
|
+
try {
|
|
146
|
+
const data = JSON.parse(require('fs').readFileSync('$metadata', 'utf-8'));
|
|
147
|
+
if (data.status === 'active') {
|
|
148
|
+
console.log('$(basename "$increment_dir")');
|
|
149
|
+
}
|
|
150
|
+
} catch (e) {}
|
|
151
|
+
" 2>/dev/null)
|
|
152
|
+
|
|
153
|
+
if [[ -n "$status" ]]; then
|
|
154
|
+
echo "$status"
|
|
155
|
+
break
|
|
156
|
+
fi
|
|
157
|
+
fi
|
|
158
|
+
done)
|
|
159
|
+
fi
|
|
160
|
+
|
|
161
|
+
# If we have an increment ID, check freshness
|
|
162
|
+
if [[ -n "$INCREMENT_ID" ]]; then
|
|
163
|
+
INCREMENT_SPEC=".specweave/increments/$INCREMENT_ID/spec.md"
|
|
164
|
+
LIVING_DOCS_SPEC=".specweave/docs/internal/specs/spec-$INCREMENT_ID.md"
|
|
165
|
+
|
|
166
|
+
# Check if increment spec exists
|
|
167
|
+
if [[ -f "$INCREMENT_SPEC" ]]; then
|
|
168
|
+
# Get modification times
|
|
169
|
+
if [[ "$(uname)" == "Darwin" ]]; then
|
|
170
|
+
# macOS
|
|
171
|
+
INCREMENT_MTIME=$(stat -f %m "$INCREMENT_SPEC" 2>/dev/null || echo 0)
|
|
172
|
+
LIVING_DOCS_MTIME=$(stat -f %m "$LIVING_DOCS_SPEC" 2>/dev/null || echo 0)
|
|
173
|
+
else
|
|
174
|
+
# Linux
|
|
175
|
+
INCREMENT_MTIME=$(stat -c %Y "$INCREMENT_SPEC" 2>/dev/null || echo 0)
|
|
176
|
+
LIVING_DOCS_MTIME=$(stat -c %Y "$LIVING_DOCS_SPEC" 2>/dev/null || echo 0)
|
|
177
|
+
fi
|
|
178
|
+
|
|
179
|
+
# Check if increment is newer than living docs (or living docs doesn't exist)
|
|
180
|
+
if [[ "$INCREMENT_MTIME" -gt "$LIVING_DOCS_MTIME" ]]; then
|
|
181
|
+
# Sync needed - run sync-living-docs
|
|
182
|
+
PLUGIN_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
183
|
+
SYNC_SCRIPT="$PLUGIN_ROOT/lib/hooks/sync-living-docs.js"
|
|
184
|
+
|
|
185
|
+
if [[ -f "$SYNC_SCRIPT" ]]; then
|
|
186
|
+
# Run sync (capture output but don't block on errors)
|
|
187
|
+
if node "$SYNC_SCRIPT" "$INCREMENT_ID" >/dev/null 2>&1; then
|
|
188
|
+
# Success - sync completed
|
|
189
|
+
:
|
|
190
|
+
else
|
|
191
|
+
# Sync failed - log but continue
|
|
192
|
+
echo "[WARNING] Pre-flight sync failed for $INCREMENT_ID" >&2
|
|
193
|
+
fi
|
|
194
|
+
fi
|
|
195
|
+
fi
|
|
196
|
+
fi
|
|
197
|
+
fi
|
|
198
|
+
fi
|
|
199
|
+
|
|
117
200
|
# ==============================================================================
|
|
118
201
|
# CONTEXT INJECTION: Add current increment status
|
|
119
202
|
# ==============================================================================
|
|
@@ -142,20 +225,44 @@ if [[ -d ".specweave/increments" ]]; then
|
|
|
142
225
|
done)
|
|
143
226
|
|
|
144
227
|
if [[ -n "$ACTIVE_INCREMENT" ]]; then
|
|
145
|
-
#
|
|
146
|
-
|
|
147
|
-
if [[ -f "$
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
228
|
+
# Use status line cache for consistent display
|
|
229
|
+
STATUS_LINE_CACHE=".specweave/state/status-line.json"
|
|
230
|
+
if [[ -f "$STATUS_LINE_CACHE" ]]; then
|
|
231
|
+
# Get status line from cache (ultra-fast)
|
|
232
|
+
STATUS_LINE=$(node -e "
|
|
233
|
+
try {
|
|
234
|
+
const { StatusLineManager } = require('./dist/core/status-line/status-line-manager.js');
|
|
235
|
+
const manager = new StatusLineManager(process.cwd());
|
|
236
|
+
const result = manager.render();
|
|
237
|
+
console.log(result || '');
|
|
238
|
+
} catch (e) {
|
|
239
|
+
// Fallback: show increment name only
|
|
240
|
+
console.log('');
|
|
241
|
+
}
|
|
242
|
+
" 2>/dev/null || echo "")
|
|
243
|
+
|
|
244
|
+
if [[ -n "$STATUS_LINE" ]]; then
|
|
245
|
+
CONTEXT="ā $STATUS_LINE"
|
|
154
246
|
else
|
|
155
|
-
|
|
247
|
+
# Fallback: parse tasks.md manually
|
|
248
|
+
TASKS_FILE=".specweave/increments/$ACTIVE_INCREMENT/tasks.md"
|
|
249
|
+
if [[ -f "$TASKS_FILE" ]]; then
|
|
250
|
+
TASK_STATS=$(grep -E "^\s*-\s*\[[ x]\]" "$TASKS_FILE" 2>/dev/null | wc -l | xargs || echo "0")
|
|
251
|
+
COMPLETED_TASKS=$(grep -E "^\s*-\s*\[x\]" "$TASKS_FILE" 2>/dev/null | wc -l | xargs || echo "0")
|
|
252
|
+
|
|
253
|
+
if [[ "$TASK_STATS" -gt 0 ]]; then
|
|
254
|
+
PERCENTAGE=$(( COMPLETED_TASKS * 100 / TASK_STATS ))
|
|
255
|
+
CONTEXT="ā Active Increment: $ACTIVE_INCREMENT ($PERCENTAGE% complete, $COMPLETED_TASKS/$TASK_STATS tasks)"
|
|
256
|
+
else
|
|
257
|
+
CONTEXT="ā Active Increment: $ACTIVE_INCREMENT"
|
|
258
|
+
fi
|
|
259
|
+
else
|
|
260
|
+
CONTEXT="ā Active Increment: $ACTIVE_INCREMENT"
|
|
261
|
+
fi
|
|
156
262
|
fi
|
|
157
263
|
else
|
|
158
|
-
|
|
264
|
+
# No cache, fall back to increment name only
|
|
265
|
+
CONTEXT="ā Active Increment: $ACTIVE_INCREMENT"
|
|
159
266
|
fi
|
|
160
267
|
fi
|
|
161
268
|
fi
|
|
@@ -1,144 +1,128 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
* Automatically syncs living documentation after task completion.
|
|
6
|
-
*
|
|
7
|
-
* Usage:
|
|
8
|
-
* node dist/hooks/lib/sync-living-docs.js <incrementId>
|
|
9
|
-
*
|
|
10
|
-
* Example:
|
|
11
|
-
* node dist/hooks/lib/sync-living-docs.js 0006-llm-native-i18n
|
|
12
|
-
*
|
|
13
|
-
* What it does:
|
|
14
|
-
* 1. Checks if sync_living_docs enabled in config
|
|
15
|
-
* 2. Detects changed docs via git diff
|
|
16
|
-
* 3. Invokes /sync-docs update command (future implementation)
|
|
17
|
-
* 4. Logs sync actions
|
|
18
|
-
*
|
|
19
|
-
* @author SpecWeave Team
|
|
20
|
-
* @version 1.0.0
|
|
21
|
-
*/
|
|
22
|
-
import fs from 'fs-extra';
|
|
23
|
-
import path from 'path';
|
|
24
|
-
import { execSync } from 'child_process';
|
|
25
|
-
/**
|
|
26
|
-
* Main function - sync living docs for given increment
|
|
27
|
-
*/
|
|
2
|
+
import fs from "fs-extra";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { execSync } from "child_process";
|
|
28
5
|
async function syncLivingDocs(incrementId) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
// 2. Check if sync enabled
|
|
38
|
-
const syncEnabled = config.hooks?.post_task_completion?.sync_living_docs ?? false;
|
|
39
|
-
if (!syncEnabled) {
|
|
40
|
-
console.log('ā¹ļø Living docs sync disabled in config');
|
|
41
|
-
console.log(' To enable: Set hooks.post_task_completion.sync_living_docs = true');
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
console.log('ā
Living docs sync enabled');
|
|
45
|
-
// 3. Detect changed docs via git diff
|
|
46
|
-
const changedDocs = detectChangedDocs();
|
|
47
|
-
if (changedDocs.length === 0) {
|
|
48
|
-
console.log('ā¹ļø No living docs changed (no git diff in .specweave/docs/)');
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
console.log(`š Detected ${changedDocs.length} changed doc(s):`);
|
|
52
|
-
changedDocs.forEach((doc) => console.log(` - ${doc}`));
|
|
53
|
-
// 4. Sync to GitHub if configured
|
|
54
|
-
await syncToGitHub(incrementId, changedDocs);
|
|
55
|
-
console.log('ā
Living docs sync complete\n');
|
|
6
|
+
try {
|
|
7
|
+
console.log(`
|
|
8
|
+
\u{1F4DA} Checking living docs sync for increment: ${incrementId}`);
|
|
9
|
+
const configPath = path.join(process.cwd(), ".specweave", "config.json");
|
|
10
|
+
let config = {};
|
|
11
|
+
if (fs.existsSync(configPath)) {
|
|
12
|
+
config = JSON.parse(await fs.readFile(configPath, "utf-8"));
|
|
56
13
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
14
|
+
const syncEnabled = config.hooks?.post_task_completion?.sync_living_docs ?? false;
|
|
15
|
+
if (!syncEnabled) {
|
|
16
|
+
console.log("\u2139\uFE0F Living docs sync disabled in config");
|
|
17
|
+
console.log(" To enable: Set hooks.post_task_completion.sync_living_docs = true");
|
|
18
|
+
return;
|
|
60
19
|
}
|
|
20
|
+
console.log("\u2705 Living docs sync enabled");
|
|
21
|
+
const specCopied = await copyIncrementSpecToLivingDocs(incrementId);
|
|
22
|
+
const changedDocs = detectChangedDocs();
|
|
23
|
+
if (changedDocs.length === 0 && !specCopied) {
|
|
24
|
+
console.log("\u2139\uFE0F No living docs changed (no git diff in .specweave/docs/)");
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
console.log(`\u{1F4C4} Detected ${changedDocs.length} changed doc(s):`);
|
|
28
|
+
changedDocs.forEach((doc) => console.log(` - ${doc}`));
|
|
29
|
+
await syncToGitHub(incrementId, changedDocs);
|
|
30
|
+
console.log("\u2705 Living docs sync complete\n");
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.error("\u274C Error syncing living docs:", error);
|
|
33
|
+
}
|
|
61
34
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
encoding: 'utf-8',
|
|
70
|
-
cwd: process.cwd(),
|
|
71
|
-
}).trim();
|
|
72
|
-
if (!output) {
|
|
73
|
-
return [];
|
|
74
|
-
}
|
|
75
|
-
// Filter to only .md files
|
|
76
|
-
const files = output
|
|
77
|
-
.split('\n')
|
|
78
|
-
.filter((f) => f.endsWith('.md'))
|
|
79
|
-
.filter((f) => f.length > 0);
|
|
80
|
-
return files;
|
|
35
|
+
async function copyIncrementSpecToLivingDocs(incrementId) {
|
|
36
|
+
try {
|
|
37
|
+
const incrementSpecPath = path.join(process.cwd(), ".specweave", "increments", incrementId, "spec.md");
|
|
38
|
+
const livingDocsPath = path.join(process.cwd(), ".specweave", "docs", "internal", "specs", `spec-${incrementId}.md`);
|
|
39
|
+
if (!fs.existsSync(incrementSpecPath)) {
|
|
40
|
+
console.log(`\u26A0\uFE0F Increment spec not found: ${incrementSpecPath}`);
|
|
41
|
+
return false;
|
|
81
42
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
43
|
+
if (fs.existsSync(livingDocsPath)) {
|
|
44
|
+
const incrementContent = await fs.readFile(incrementSpecPath, "utf-8");
|
|
45
|
+
const livingDocsContent = await fs.readFile(livingDocsPath, "utf-8");
|
|
46
|
+
if (incrementContent === livingDocsContent) {
|
|
47
|
+
console.log(`\u2139\uFE0F Living docs spec already up-to-date: spec-${incrementId}.md`);
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
await fs.ensureDir(path.dirname(livingDocsPath));
|
|
52
|
+
await fs.copy(incrementSpecPath, livingDocsPath);
|
|
53
|
+
console.log(`\u2705 Copied increment spec to living docs: spec-${incrementId}.md`);
|
|
54
|
+
return true;
|
|
55
|
+
} catch (error) {
|
|
56
|
+
console.error(`\u274C Error copying increment spec: ${error}`);
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function detectChangedDocs() {
|
|
61
|
+
try {
|
|
62
|
+
const output = execSync("git diff --name-only .specweave/docs/ 2>/dev/null || true", {
|
|
63
|
+
encoding: "utf-8",
|
|
64
|
+
cwd: process.cwd()
|
|
65
|
+
}).trim();
|
|
66
|
+
if (!output) {
|
|
67
|
+
return [];
|
|
85
68
|
}
|
|
69
|
+
const files = output.split("\n").filter((f) => f.endsWith(".md")).filter((f) => f.length > 0);
|
|
70
|
+
return files;
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.warn("\u26A0\uFE0F Could not detect git changes:", error);
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
86
75
|
}
|
|
87
|
-
/**
|
|
88
|
-
* Sync changed docs to GitHub
|
|
89
|
-
*/
|
|
90
76
|
async function syncToGitHub(incrementId, changedDocs) {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
console.log(` Syncing to ${owner}/${repo}#${issueNumber}`);
|
|
110
|
-
// 3. Collect all living docs
|
|
111
|
-
const livingDocs = await collectLivingDocs(incrementId);
|
|
112
|
-
// 4. Update issue with living docs section
|
|
113
|
-
if (livingDocs.specs.length > 0 || livingDocs.architecture.length > 0 || livingDocs.diagrams.length > 0) {
|
|
114
|
-
await updateIssueLivingDocs(issueNumber, livingDocs, owner, repo);
|
|
115
|
-
}
|
|
116
|
-
// 5. Post comments for newly created architecture docs
|
|
117
|
-
for (const docPath of changedDocs) {
|
|
118
|
-
if (docPath.includes('/architecture/')) {
|
|
119
|
-
await postArchitectureComment(issueNumber, docPath, owner, repo);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
console.log('ā
GitHub sync complete');
|
|
77
|
+
try {
|
|
78
|
+
console.log("\n\u{1F504} Syncing to GitHub...");
|
|
79
|
+
const {
|
|
80
|
+
loadIncrementMetadata,
|
|
81
|
+
detectRepo,
|
|
82
|
+
collectLivingDocs,
|
|
83
|
+
updateIssueLivingDocs,
|
|
84
|
+
postArchitectureComment
|
|
85
|
+
} = await import("../../../specweave-github/lib/github-issue-updater.js");
|
|
86
|
+
const metadata = await loadIncrementMetadata(incrementId);
|
|
87
|
+
if (!metadata?.github?.issue) {
|
|
88
|
+
console.log("\u2139\uFE0F No GitHub issue linked, skipping GitHub sync");
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const repoInfo = await detectRepo();
|
|
92
|
+
if (!repoInfo) {
|
|
93
|
+
console.log("\u26A0\uFE0F Could not detect GitHub repository, skipping GitHub sync");
|
|
94
|
+
return;
|
|
123
95
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
96
|
+
const { owner, repo } = repoInfo;
|
|
97
|
+
const issueNumber = metadata.github.issue;
|
|
98
|
+
console.log(` Syncing to ${owner}/${repo}#${issueNumber}`);
|
|
99
|
+
const livingDocs = await collectLivingDocs(incrementId);
|
|
100
|
+
if (livingDocs.specs.length > 0 || livingDocs.architecture.length > 0 || livingDocs.diagrams.length > 0) {
|
|
101
|
+
await updateIssueLivingDocs(issueNumber, livingDocs, owner, repo);
|
|
127
102
|
}
|
|
103
|
+
for (const docPath of changedDocs) {
|
|
104
|
+
if (docPath.includes("/architecture/")) {
|
|
105
|
+
await postArchitectureComment(issueNumber, docPath, owner, repo);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
console.log("\u2705 GitHub sync complete");
|
|
109
|
+
} catch (error) {
|
|
110
|
+
console.error("\u274C Error syncing to GitHub:", error);
|
|
111
|
+
console.error(" (Non-blocking - continuing...)");
|
|
112
|
+
}
|
|
128
113
|
}
|
|
129
|
-
// CLI entry point
|
|
130
114
|
const isMainModule = import.meta.url === `file://${process.argv[1]}`;
|
|
131
115
|
if (isMainModule) {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
});
|
|
116
|
+
const incrementId = process.argv[2];
|
|
117
|
+
if (!incrementId) {
|
|
118
|
+
console.error("\u274C Usage: sync-living-docs <incrementId>");
|
|
119
|
+
console.error(" Example: sync-living-docs 0006-llm-native-i18n");
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
syncLivingDocs(incrementId).catch((error) => {
|
|
123
|
+
console.error("\u274C Fatal error:", error);
|
|
124
|
+
});
|
|
142
125
|
}
|
|
143
|
-
export {
|
|
144
|
-
|
|
126
|
+
export {
|
|
127
|
+
syncLivingDocs
|
|
128
|
+
};
|
|
@@ -59,10 +59,13 @@ async function syncLivingDocs(incrementId: string): Promise<void> {
|
|
|
59
59
|
|
|
60
60
|
console.log('ā
Living docs sync enabled');
|
|
61
61
|
|
|
62
|
-
// 3.
|
|
62
|
+
// 3. Copy increment spec to living docs
|
|
63
|
+
const specCopied = await copyIncrementSpecToLivingDocs(incrementId);
|
|
64
|
+
|
|
65
|
+
// 4. Detect changed docs via git diff
|
|
63
66
|
const changedDocs = detectChangedDocs();
|
|
64
67
|
|
|
65
|
-
if (changedDocs.length === 0) {
|
|
68
|
+
if (changedDocs.length === 0 && !specCopied) {
|
|
66
69
|
console.log('ā¹ļø No living docs changed (no git diff in .specweave/docs/)');
|
|
67
70
|
return;
|
|
68
71
|
}
|
|
@@ -81,6 +84,47 @@ async function syncLivingDocs(incrementId: string): Promise<void> {
|
|
|
81
84
|
}
|
|
82
85
|
}
|
|
83
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Copy increment spec to living docs
|
|
89
|
+
* Returns true if spec was copied, false if skipped
|
|
90
|
+
*/
|
|
91
|
+
async function copyIncrementSpecToLivingDocs(incrementId: string): Promise<boolean> {
|
|
92
|
+
try {
|
|
93
|
+
const incrementSpecPath = path.join(process.cwd(), '.specweave', 'increments', incrementId, 'spec.md');
|
|
94
|
+
const livingDocsPath = path.join(process.cwd(), '.specweave', 'docs', 'internal', 'specs', `spec-${incrementId}.md`);
|
|
95
|
+
|
|
96
|
+
// Check if increment spec exists
|
|
97
|
+
if (!fs.existsSync(incrementSpecPath)) {
|
|
98
|
+
console.log(`ā ļø Increment spec not found: ${incrementSpecPath}`);
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Check if living docs spec already exists and is identical
|
|
103
|
+
if (fs.existsSync(livingDocsPath)) {
|
|
104
|
+
const incrementContent = await fs.readFile(incrementSpecPath, 'utf-8');
|
|
105
|
+
const livingDocsContent = await fs.readFile(livingDocsPath, 'utf-8');
|
|
106
|
+
|
|
107
|
+
if (incrementContent === livingDocsContent) {
|
|
108
|
+
console.log(`ā¹ļø Living docs spec already up-to-date: spec-${incrementId}.md`);
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Ensure target directory exists
|
|
114
|
+
await fs.ensureDir(path.dirname(livingDocsPath));
|
|
115
|
+
|
|
116
|
+
// Copy spec to living docs
|
|
117
|
+
await fs.copy(incrementSpecPath, livingDocsPath);
|
|
118
|
+
|
|
119
|
+
console.log(`ā
Copied increment spec to living docs: spec-${incrementId}.md`);
|
|
120
|
+
return true;
|
|
121
|
+
|
|
122
|
+
} catch (error) {
|
|
123
|
+
console.error(`ā Error copying increment spec: ${error}`);
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
84
128
|
/**
|
|
85
129
|
* Detect changed documentation files via git diff
|
|
86
130
|
*/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: ado-resource-validator
|
|
3
|
-
description: Validates Azure DevOps projects and resources exist, creates missing resources automatically. Smart enough to prompt user to select existing or create new projects. Supports multiple projects for project-per-team strategy, area paths for area-path-based strategy, and teams for team-based strategy. Activates for ado setup, ado validation, ado configuration, missing ado project, azure devops .env setup.
|
|
3
|
+
description: Validates Azure DevOps projects and resources exist, creates missing resources automatically. Smart enough to prompt user to select existing or create new projects. Supports multiple projects for project-per-team strategy, area paths for area-path-based strategy, and teams for team-based strategy. NEW - Per-project configuration support - AZURE_DEVOPS_AREA_PATHS_{ProjectName} and AZURE_DEVOPS_TEAMS_{ProjectName} for hierarchical organization. Activates for ado setup, ado validation, ado configuration, missing ado project, azure devops .env setup, per-project area paths, per-project teams.
|
|
4
4
|
allowed-tools: Read, Bash, Write, Edit
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -66,6 +66,32 @@ AZURE_DEVOPS_TEAMS=Alpha Team,Beta Team,Gamma Team
|
|
|
66
66
|
ā Validates MainProduct project exists
|
|
67
67
|
ā Creates teams if missing: Alpha Team, Beta Team, Gamma Team
|
|
68
68
|
|
|
69
|
+
**NEW: Per-Project Configuration** (Advanced - Multiple Projects Ć Resources)
|
|
70
|
+
```bash
|
|
71
|
+
# Multiple projects with their own area paths and teams
|
|
72
|
+
AZURE_DEVOPS_STRATEGY=project-per-team
|
|
73
|
+
AZURE_DEVOPS_PROJECTS=Backend,Frontend,Mobile
|
|
74
|
+
|
|
75
|
+
# Per-project area paths (hierarchical naming)
|
|
76
|
+
AZURE_DEVOPS_AREA_PATHS_Backend=API,Database,Cache
|
|
77
|
+
AZURE_DEVOPS_AREA_PATHS_Frontend=Web,Admin,Public
|
|
78
|
+
AZURE_DEVOPS_AREA_PATHS_Mobile=iOS,Android,Shared
|
|
79
|
+
|
|
80
|
+
# Per-project teams (optional)
|
|
81
|
+
AZURE_DEVOPS_TEAMS_Backend=Alpha,Beta
|
|
82
|
+
AZURE_DEVOPS_TEAMS_Frontend=Gamma
|
|
83
|
+
```
|
|
84
|
+
ā Validates 3 projects exist: Backend, Frontend, Mobile
|
|
85
|
+
ā Creates area paths per project:
|
|
86
|
+
- Backend\API, Backend\Database, Backend\Cache
|
|
87
|
+
- Frontend\Web, Frontend\Admin, Frontend\Public
|
|
88
|
+
- Mobile\iOS, Mobile\Android, Mobile\Shared
|
|
89
|
+
ā Creates teams per project:
|
|
90
|
+
- Backend: Alpha, Beta
|
|
91
|
+
- Frontend: Gamma
|
|
92
|
+
|
|
93
|
+
**Naming Convention**: `{PROVIDER}_{RESOURCE_TYPE}_{PROJECT_NAME}`
|
|
94
|
+
|
|
69
95
|
## Validation Flow
|
|
70
96
|
|
|
71
97
|
### Step 1: Strategy Detection
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: jira-resource-validator
|
|
3
|
-
description: Validates Jira projects and boards exist, creates missing resources automatically. Smart enough to prompt user to select existing or create new projects. For boards, accepts either IDs (validates existence) or names (creates boards and updates .env with IDs). Activates for jira setup, jira validation, jira configuration, missing jira project, missing jira boards, jira .env setup.
|
|
3
|
+
description: Validates Jira projects and boards exist, creates missing resources automatically. Smart enough to prompt user to select existing or create new projects. For boards, accepts either IDs (validates existence) or names (creates boards and updates .env with IDs). NEW - Per-project configuration support - JIRA_BOARDS_{ProjectKey} for hierarchical board organization across multiple projects. Activates for jira setup, jira validation, jira configuration, missing jira project, missing jira boards, jira .env setup, per-project boards.
|
|
4
4
|
allowed-tools: Read, Bash, Write, Edit
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -70,6 +70,36 @@ JIRA_BOARDS=101,102,QA,Dashboard
|
|
|
70
70
|
- Non-numeric (e.g., "QA") ā Create board with that name
|
|
71
71
|
- After creation, .env is updated with ALL board IDs
|
|
72
72
|
|
|
73
|
+
### NEW: Per-Project Configuration (Advanced - Multiple Projects Ć Boards)
|
|
74
|
+
|
|
75
|
+
**Multiple JIRA projects with their own boards:**
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# Multiple projects with their own boards
|
|
79
|
+
JIRA_STRATEGY=project-per-team
|
|
80
|
+
JIRA_PROJECTS=BACKEND,FRONTEND,MOBILE
|
|
81
|
+
|
|
82
|
+
# Per-project boards (hierarchical naming)
|
|
83
|
+
JIRA_BOARDS_BACKEND=123,456 # Sprint + Kanban (IDs)
|
|
84
|
+
JIRA_BOARDS_FRONTEND=Sprint,Bug # Create these boards
|
|
85
|
+
JIRA_BOARDS_MOBILE=789,012,345 # iOS + Android + Release (IDs)
|
|
86
|
+
```
|
|
87
|
+
ā Validates 3 projects exist: BACKEND, FRONTEND, MOBILE
|
|
88
|
+
ā Validates/creates boards per project:
|
|
89
|
+
- BACKEND: Validates boards 123, 456 exist
|
|
90
|
+
- FRONTEND: Creates "Sprint" and "Bug" boards, updates .env with IDs
|
|
91
|
+
- MOBILE: Validates boards 789, 012, 345 exist
|
|
92
|
+
|
|
93
|
+
**Naming Convention**: `{PROVIDER}_{RESOURCE_TYPE}_{PROJECT_KEY}`
|
|
94
|
+
|
|
95
|
+
**Mixed IDs and Names Per Project**:
|
|
96
|
+
```bash
|
|
97
|
+
JIRA_BOARDS_BACKEND=123,NewBoard,456
|
|
98
|
+
```
|
|
99
|
+
ā Validates 123, 456 exist
|
|
100
|
+
ā Creates "NewBoard"
|
|
101
|
+
ā Updates .env: `JIRA_BOARDS_BACKEND=123,789,456` (all IDs!)
|
|
102
|
+
|
|
73
103
|
## Validation Flow
|
|
74
104
|
|
|
75
105
|
### Step 1: Project Validation
|