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.
Files changed (47) hide show
  1. package/CLAUDE.md +11 -6
  2. package/bin/specweave.js +9 -0
  3. package/dist/cli/commands/revert-wip-limit.js +60 -0
  4. package/dist/plugins/specweave/lib/hooks/sync-living-docs.d.ts.map +1 -1
  5. package/dist/plugins/specweave/lib/hooks/sync-living-docs.js +38 -2
  6. package/dist/plugins/specweave/lib/hooks/sync-living-docs.js.map +1 -1
  7. package/dist/src/cli/commands/migrate-to-profiles.d.ts +32 -0
  8. package/dist/src/cli/commands/migrate-to-profiles.d.ts.map +1 -1
  9. package/dist/src/cli/commands/migrate-to-profiles.js +8 -6
  10. package/dist/src/cli/commands/migrate-to-profiles.js.map +1 -1
  11. package/dist/src/cli/commands/revert-wip-limit.d.ts +8 -0
  12. package/dist/src/cli/commands/revert-wip-limit.d.ts.map +1 -0
  13. package/dist/src/cli/commands/revert-wip-limit.js +61 -0
  14. package/dist/src/cli/commands/revert-wip-limit.js.map +1 -0
  15. package/dist/src/cli/helpers/issue-tracker/ado.d.ts.map +1 -1
  16. package/dist/src/cli/helpers/issue-tracker/ado.js +5 -0
  17. package/dist/src/cli/helpers/issue-tracker/ado.js.map +1 -1
  18. package/dist/src/cli/helpers/issue-tracker/jira.d.ts.map +1 -1
  19. package/dist/src/cli/helpers/issue-tracker/jira.js +5 -0
  20. package/dist/src/cli/helpers/issue-tracker/jira.js.map +1 -1
  21. package/dist/src/core/increment/metadata-manager.d.ts.map +1 -1
  22. package/dist/src/core/increment/metadata-manager.js +6 -0
  23. package/dist/src/core/increment/metadata-manager.js.map +1 -1
  24. package/dist/src/core/repo-structure/setup-state-manager.d.ts +7 -0
  25. package/dist/src/core/repo-structure/setup-state-manager.d.ts.map +1 -1
  26. package/dist/src/core/repo-structure/setup-state-manager.js +28 -2
  27. package/dist/src/core/repo-structure/setup-state-manager.js.map +1 -1
  28. package/dist/src/utils/external-resource-validator.d.ts +6 -0
  29. package/dist/src/utils/external-resource-validator.d.ts.map +1 -1
  30. package/dist/src/utils/external-resource-validator.js +298 -57
  31. package/dist/src/utils/external-resource-validator.js.map +1 -1
  32. package/package.json +1 -1
  33. package/plugins/specweave/.claude-plugin/plugin.json +2 -1
  34. package/plugins/specweave/commands/revert-wip-limit.md +82 -0
  35. package/plugins/specweave/hooks/hooks.json +10 -0
  36. package/plugins/specweave/hooks/lib/migrate-increment-work.sh +245 -0
  37. package/plugins/specweave/hooks/post-increment-planning.sh +26 -40
  38. package/plugins/specweave/hooks/user-prompt-submit.sh +119 -12
  39. package/plugins/specweave/lib/hooks/sync-living-docs.js +113 -129
  40. package/plugins/specweave/lib/hooks/sync-living-docs.ts +46 -2
  41. package/plugins/specweave-ado/.claude-plugin/plugin.json +2 -1
  42. package/plugins/specweave-ado/skills/ado-resource-validator/SKILL.md +27 -1
  43. package/plugins/specweave-github/.claude-plugin/plugin.json +2 -1
  44. package/plugins/specweave-jira/.claude-plugin/plugin.json +2 -1
  45. package/plugins/specweave-jira/skills/jira-resource-validator/SKILL.md +31 -1
  46. package/plugins/specweave-release/.claude-plugin/plugin.json +2 -1
  47. 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$(echo "$INCOMPLETE_INCREMENTS" | sed 's/^/ - /')\n\nšŸ’” Complete or close them first:\n - /specweave:done <id> # Mark as complete\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"
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
- # Get task completion percentage
146
- TASKS_FILE=".specweave/increments/$ACTIVE_INCREMENT/tasks.md"
147
- if [[ -f "$TASKS_FILE" ]]; then
148
- TASK_STATS=$(grep -E "^\s*-\s*\[[ x]\]" "$TASKS_FILE" 2>/dev/null | wc -l | xargs || echo "0")
149
- COMPLETED_TASKS=$(grep -E "^\s*-\s*\[x\]" "$TASKS_FILE" 2>/dev/null | wc -l | xargs || echo "0")
150
-
151
- if [[ "$TASK_STATS" -gt 0 ]]; then
152
- PERCENTAGE=$(( COMPLETED_TASKS * 100 / TASK_STATS ))
153
- CONTEXT="šŸ“ Active Increment: $ACTIVE_INCREMENT ($PERCENTAGE% complete, $COMPLETED_TASKS/$TASK_STATS tasks)"
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
- CONTEXT="šŸ“ Active Increment: $ACTIVE_INCREMENT"
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
- CONTEXT="šŸ“ Active Increment: $ACTIVE_INCREMENT"
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
- * SpecWeave Living Docs Auto-Sync
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
- try {
30
- console.log(`\nšŸ“š Checking living docs sync for increment: ${incrementId}`);
31
- // 1. Load config
32
- const configPath = path.join(process.cwd(), '.specweave', 'config.json');
33
- let config = {};
34
- if (fs.existsSync(configPath)) {
35
- config = JSON.parse(await fs.readFile(configPath, 'utf-8'));
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
- catch (error) {
58
- console.error('āŒ Error syncing living docs:', error);
59
- // Don't exit with error - this is best-effort
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
- * Detect changed documentation files via git diff
64
- */
65
- function detectChangedDocs() {
66
- try {
67
- // Get changed files in .specweave/docs/
68
- const output = execSync('git diff --name-only .specweave/docs/ 2>/dev/null || true', {
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
- catch (error) {
83
- console.warn('āš ļø Could not detect git changes:', error);
84
- return [];
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
- try {
92
- console.log('\nšŸ”„ Syncing to GitHub...');
93
- // Dynamic import to avoid circular dependencies
94
- const { loadIncrementMetadata, detectRepo, collectLivingDocs, updateIssueLivingDocs, postArchitectureComment } = await import('../../../specweave-github/lib/github-issue-updater.js');
95
- // 1. Load metadata
96
- const metadata = await loadIncrementMetadata(incrementId);
97
- if (!metadata?.github?.issue) {
98
- console.log('ā„¹ļø No GitHub issue linked, skipping GitHub sync');
99
- return;
100
- }
101
- // 2. Detect repository
102
- const repoInfo = await detectRepo();
103
- if (!repoInfo) {
104
- console.log('āš ļø Could not detect GitHub repository, skipping GitHub sync');
105
- return;
106
- }
107
- const { owner, repo } = repoInfo;
108
- const issueNumber = metadata.github.issue;
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
- catch (error) {
125
- console.error('āŒ Error syncing to GitHub:', error);
126
- console.error(' (Non-blocking - continuing...)');
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
- const incrementId = process.argv[2];
133
- if (!incrementId) {
134
- console.error('āŒ Usage: sync-living-docs <incrementId>');
135
- console.error(' Example: sync-living-docs 0006-llm-native-i18n');
136
- process.exit(1);
137
- }
138
- syncLivingDocs(incrementId).catch((error) => {
139
- console.error('āŒ Fatal error:', error);
140
- // Don't exit with error code - best-effort sync
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 { syncLivingDocs };
144
- //# sourceMappingURL=sync-living-docs.js.map
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. Detect changed docs via git diff
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
  */
@@ -16,5 +16,6 @@
16
16
  "sync",
17
17
  "integration"
18
18
  ],
19
- "license": "MIT"
19
+ "license": "MIT",
20
+ "hooks": "hooks/hooks.json"
20
21
  }
@@ -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
@@ -15,5 +15,6 @@
15
15
  "integration",
16
16
  "sync",
17
17
  "specweave"
18
- ]
18
+ ],
19
+ "hooks": "hooks/hooks.json"
19
20
  }
@@ -16,5 +16,6 @@
16
16
  "sync",
17
17
  "specweave",
18
18
  "project-management"
19
- ]
19
+ ],
20
+ "hooks": "hooks/hooks.json"
20
21
  }
@@ -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
@@ -37,5 +37,6 @@
37
37
  },
38
38
  "skills": "skills",
39
39
  "agents": "agents",
40
- "commands": "commands"
40
+ "commands": "commands",
41
+ "hooks": "hooks/hooks.json"
41
42
  }