specweave 0.30.14 → 0.30.17

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 (100) hide show
  1. package/CLAUDE.md +43 -0
  2. package/README.md +32 -0
  3. package/bin/specweave.js +28 -0
  4. package/dist/src/cli/commands/commits.d.ts +7 -0
  5. package/dist/src/cli/commands/commits.d.ts.map +1 -0
  6. package/dist/src/cli/commands/commits.js +42 -0
  7. package/dist/src/cli/commands/commits.js.map +1 -0
  8. package/dist/src/cli/commands/living-docs.d.ts +29 -0
  9. package/dist/src/cli/commands/living-docs.d.ts.map +1 -0
  10. package/dist/src/cli/commands/living-docs.js +350 -0
  11. package/dist/src/cli/commands/living-docs.js.map +1 -0
  12. package/dist/src/cli/helpers/ado-area-selector.js +1 -1
  13. package/dist/src/cli/helpers/ado-area-selector.js.map +1 -1
  14. package/dist/src/cli/workers/living-docs-worker.js +80 -44
  15. package/dist/src/cli/workers/living-docs-worker.js.map +1 -1
  16. package/dist/src/core/background/index.d.ts +2 -2
  17. package/dist/src/core/background/index.d.ts.map +1 -1
  18. package/dist/src/core/background/index.js +1 -1
  19. package/dist/src/core/background/index.js.map +1 -1
  20. package/dist/src/core/living-docs/living-docs-sync.d.ts +60 -24
  21. package/dist/src/core/living-docs/living-docs-sync.d.ts.map +1 -1
  22. package/dist/src/core/living-docs/living-docs-sync.js +360 -103
  23. package/dist/src/core/living-docs/living-docs-sync.js.map +1 -1
  24. package/dist/src/core/llm/index.d.ts +1 -0
  25. package/dist/src/core/llm/index.d.ts.map +1 -1
  26. package/dist/src/core/llm/index.js +2 -0
  27. package/dist/src/core/llm/index.js.map +1 -1
  28. package/dist/src/core/llm/providers/anthropic-provider.d.ts.map +1 -1
  29. package/dist/src/core/llm/providers/anthropic-provider.js +15 -26
  30. package/dist/src/core/llm/providers/anthropic-provider.js.map +1 -1
  31. package/dist/src/core/llm/providers/azure-openai-provider.d.ts.map +1 -1
  32. package/dist/src/core/llm/providers/azure-openai-provider.js +13 -5
  33. package/dist/src/core/llm/providers/azure-openai-provider.js.map +1 -1
  34. package/dist/src/core/llm/providers/bedrock-provider.d.ts.map +1 -1
  35. package/dist/src/core/llm/providers/bedrock-provider.js +12 -8
  36. package/dist/src/core/llm/providers/bedrock-provider.js.map +1 -1
  37. package/dist/src/core/llm/providers/claude-code-provider.d.ts.map +1 -1
  38. package/dist/src/core/llm/providers/claude-code-provider.js +15 -25
  39. package/dist/src/core/llm/providers/claude-code-provider.js.map +1 -1
  40. package/dist/src/core/llm/providers/ollama-provider.d.ts.map +1 -1
  41. package/dist/src/core/llm/providers/ollama-provider.js +12 -9
  42. package/dist/src/core/llm/providers/ollama-provider.js.map +1 -1
  43. package/dist/src/core/llm/providers/openai-provider.d.ts.map +1 -1
  44. package/dist/src/core/llm/providers/openai-provider.js +13 -6
  45. package/dist/src/core/llm/providers/openai-provider.js.map +1 -1
  46. package/dist/src/core/llm/providers/vertex-ai-provider.d.ts.map +1 -1
  47. package/dist/src/core/llm/providers/vertex-ai-provider.js +12 -8
  48. package/dist/src/core/llm/providers/vertex-ai-provider.js.map +1 -1
  49. package/dist/src/importers/ado-importer.js +2 -2
  50. package/dist/src/importers/ado-importer.js.map +1 -1
  51. package/dist/src/importers/item-converter.d.ts +6 -1
  52. package/dist/src/importers/item-converter.d.ts.map +1 -1
  53. package/dist/src/importers/item-converter.js +15 -2
  54. package/dist/src/importers/item-converter.js.map +1 -1
  55. package/dist/src/integrations/ado/ado-pat-provider.d.ts +3 -3
  56. package/dist/src/integrations/ado/ado-pat-provider.js +3 -3
  57. package/dist/src/living-docs/epic-id-allocator.d.ts +1 -1
  58. package/dist/src/living-docs/epic-id-allocator.js +1 -1
  59. package/dist/src/living-docs/fs-id-allocator.d.ts +1 -1
  60. package/dist/src/living-docs/fs-id-allocator.js +1 -1
  61. package/dist/src/utils/auth-helpers.d.ts +23 -0
  62. package/dist/src/utils/auth-helpers.d.ts.map +1 -1
  63. package/dist/src/utils/auth-helpers.js +51 -0
  64. package/dist/src/utils/auth-helpers.js.map +1 -1
  65. package/dist/src/utils/feature-id-collision.d.ts +48 -5
  66. package/dist/src/utils/feature-id-collision.d.ts.map +1 -1
  67. package/dist/src/utils/feature-id-collision.js +251 -19
  68. package/dist/src/utils/feature-id-collision.js.map +1 -1
  69. package/dist/src/utils/llm-json-extractor.d.ts +105 -0
  70. package/dist/src/utils/llm-json-extractor.d.ts.map +1 -0
  71. package/dist/src/utils/llm-json-extractor.js +336 -0
  72. package/dist/src/utils/llm-json-extractor.js.map +1 -0
  73. package/dist/src/utils/structure-level-detector.d.ts +105 -0
  74. package/dist/src/utils/structure-level-detector.d.ts.map +1 -0
  75. package/dist/src/utils/structure-level-detector.js +388 -0
  76. package/dist/src/utils/structure-level-detector.js.map +1 -0
  77. package/dist/src/utils/validators/ado-validator.js +2 -2
  78. package/dist/src/utils/validators/ado-validator.js.map +1 -1
  79. package/package.json +1 -2
  80. package/plugins/specweave/commands/specweave-increment.md +57 -9
  81. package/plugins/specweave/commands/specweave-living-docs.md +321 -0
  82. package/plugins/specweave/commands/specweave-sync-specs.md +37 -6
  83. package/plugins/specweave/hooks/hooks.json +10 -0
  84. package/plugins/specweave/hooks/spec-project-validator.sh +111 -0
  85. package/plugins/specweave/hooks/v2/handlers/github-sync-handler.sh +10 -1
  86. package/plugins/specweave/hooks/v2/handlers/living-docs-handler.sh +10 -1
  87. package/plugins/specweave/skills/increment-planner/SKILL.md +109 -10
  88. package/plugins/specweave/skills/increment-planner/templates/spec-multi-project.md +2 -0
  89. package/plugins/specweave/skills/increment-planner/templates/spec-single-project.md +1 -0
  90. package/plugins/specweave/skills/multi-project-spec-mapper/SKILL.md +24 -1
  91. package/plugins/specweave/skills/spec-generator/SKILL.md +18 -0
  92. package/plugins/specweave/skills/specweave-framework/SKILL.md +25 -0
  93. package/plugins/specweave-ado/agents/ado-manager/AGENT.md +58 -0
  94. package/plugins/specweave-ado/commands/pull.md +30 -0
  95. package/plugins/specweave-ado/commands/push.md +30 -0
  96. package/plugins/specweave-ado/commands/sync.md +31 -0
  97. package/plugins/specweave-github/agents/github-manager/AGENT.md +22 -0
  98. package/plugins/specweave-github/hooks/.specweave/logs/hooks-debug.log +14 -0
  99. package/plugins/specweave-jira/agents/jira-manager/AGENT.md +30 -0
  100. package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +21 -0
@@ -0,0 +1,321 @@
1
+ ---
2
+ name: specweave:living-docs
3
+ description: Launch or resume Living Docs Builder independently. Generates documentation from codebase analysis with AI-powered insights.
4
+ usage: /specweave:living-docs [--resume <jobId>] [--depth <level>] [--priority <modules>] [--sources <folders>] [--depends-on <jobIds>] [--foreground]
5
+ ---
6
+
7
+ # Living Docs Builder (Standalone)
8
+
9
+ **Usage**: `/specweave:living-docs [options]`
10
+
11
+ ---
12
+
13
+ ## Purpose
14
+
15
+ Launch the Living Docs Builder independently of `specweave init`. This is essential for:
16
+ - **Resuming after crash** - Claude Code crashed after init, need to restart living docs
17
+ - **On-demand analysis** - Re-analyze codebase after major changes
18
+ - **Large brownfield projects** - Run targeted analysis on specific modules
19
+ - **CI/CD integration** - Automate documentation generation
20
+
21
+ ---
22
+
23
+ ## Command Options
24
+
25
+ | Option | Description |
26
+ |--------|-------------|
27
+ | (none) | Interactive mode - prompts for configuration |
28
+ | `--resume <jobId>` | Resume orphaned/paused living-docs job |
29
+ | `--depth <level>` | Analysis depth: `quick`, `standard`, `deep-native`, `deep-api` |
30
+ | `--priority <modules>` | Priority modules (comma-separated): `auth,payments,api` |
31
+ | `--sources <folders>` | Additional doc folders (comma-separated): `docs/,wiki/` |
32
+ | `--depends-on <jobIds>` | Wait for jobs before starting (comma-separated) |
33
+ | `--foreground` | Run in current session instead of background |
34
+
35
+ ---
36
+
37
+ ## Quick Start
38
+
39
+ ### Launch New Analysis (Interactive)
40
+
41
+ ```bash
42
+ /specweave:living-docs
43
+
44
+ # Prompts for:
45
+ # 1. Analysis depth (quick/standard/deep-native/deep-api)
46
+ # 2. Priority modules to focus on
47
+ # 3. Additional documentation sources
48
+ # 4. Confirmation to launch
49
+ ```
50
+
51
+ ### Resume After Crash
52
+
53
+ ```bash
54
+ # Check for orphaned jobs first
55
+ /specweave:jobs
56
+
57
+ # If you see an orphaned living-docs-builder job:
58
+ /specweave:living-docs --resume abc12345
59
+
60
+ # Or let it auto-detect:
61
+ /specweave:living-docs
62
+ # → "Found orphaned job abc12345. Resume? [Y/n]"
63
+ ```
64
+
65
+ ### Quick Analysis (Non-Interactive)
66
+
67
+ ```bash
68
+ # Quick scan - 5-10 minutes
69
+ /specweave:living-docs --depth quick
70
+
71
+ # Standard analysis - 15-30 minutes
72
+ /specweave:living-docs --depth standard --priority auth,payments
73
+
74
+ # AI-powered deep analysis (FREE with MAX subscription)
75
+ /specweave:living-docs --depth deep-native --priority core,api
76
+ ```
77
+
78
+ ---
79
+
80
+ ## Analysis Depths
81
+
82
+ | Depth | Duration | What It Does | Cost |
83
+ |-------|----------|--------------|------|
84
+ | `quick` | ~5-10 min | Structure scan, tech detection, imports map | Free |
85
+ | `standard` | ~15-30 min | Module analysis, exports, dependencies | Free |
86
+ | `deep-native` | Progress-based | AI analysis via Claude Code CLI | FREE (MAX) |
87
+ | `deep-api` | Progress-based | AI analysis via API key | API costs |
88
+
89
+ ### Deep-Native (Recommended for MAX Users)
90
+
91
+ Uses your Claude MAX subscription via `claude --print`:
92
+ - **No extra cost** - included in MAX
93
+ - Runs in **background** - survives terminal close
94
+ - **Checkpoint/resume** - can resume from any phase
95
+ - Uses **Opus 4.5** for best quality
96
+
97
+ ```bash
98
+ /specweave:living-docs --depth deep-native
99
+
100
+ # Monitor progress:
101
+ /specweave:jobs --follow <jobId>
102
+ ```
103
+
104
+ ---
105
+
106
+ ## Implementation Steps
107
+
108
+ When this command is invoked:
109
+
110
+ ### Step 1: Check for Orphaned Jobs
111
+
112
+ ```typescript
113
+ import { getOrphanedJobs, getJobManager } from '../../../src/core/background/job-launcher.js';
114
+
115
+ const orphaned = getOrphanedJobs(projectPath).filter(j => j.type === 'living-docs-builder');
116
+ if (orphaned.length > 0) {
117
+ // Prompt: "Found orphaned job {id}. Resume? [Y/n]"
118
+ // If yes: resume job
119
+ // If no: ask if they want to start fresh
120
+ }
121
+ ```
122
+
123
+ ### Step 2: Collect Configuration (if not --resume)
124
+
125
+ If no `--resume` flag and no auto-resume:
126
+
127
+ ```typescript
128
+ import { collectLivingDocsInputs } from '../../../src/cli/helpers/init/living-docs-preflight.js';
129
+
130
+ const result = await collectLivingDocsInputs({
131
+ projectPath,
132
+ language: 'en',
133
+ isCi: hasFlags, // Skip prompts if flags provided
134
+ });
135
+ ```
136
+
137
+ Override with flags:
138
+ - `--depth` → `result.userInputs.analysisDepth`
139
+ - `--priority` → `result.userInputs.priorityAreas`
140
+ - `--sources` → `result.userInputs.additionalSources`
141
+
142
+ ### Step 3: Launch Job
143
+
144
+ ```typescript
145
+ import { launchLivingDocsJob } from '../../../src/core/background/job-launcher.js';
146
+
147
+ const { job, pid, isBackground } = await launchLivingDocsJob({
148
+ projectPath,
149
+ userInputs: result.userInputs,
150
+ dependsOn: dependsOnJobIds,
151
+ foreground: hasForegroundFlag,
152
+ });
153
+ ```
154
+
155
+ ### Step 4: Display Status
156
+
157
+ ```
158
+ ✅ Living Docs Builder launched!
159
+
160
+ Job ID: ldb-abc12345
161
+ Depth: deep-native (Claude Code Opus 4.5)
162
+ Priority: auth, payments, api
163
+ PID: 45678
164
+
165
+ Monitor: /specweave:jobs --follow ldb-abc12345
166
+ Logs: /specweave:jobs --logs ldb-abc12345
167
+
168
+ 💡 This job runs in background and survives terminal close.
169
+ Output will be saved to:
170
+ - .specweave/docs/SUGGESTIONS.md
171
+ - .specweave/docs/ENTERPRISE-HEALTH.md
172
+ ```
173
+
174
+ ---
175
+
176
+ ## Resume Behavior
177
+
178
+ When resuming a job:
179
+
180
+ 1. **Load checkpoint** from `.specweave/state/jobs/<jobId>/checkpoints/`
181
+ 2. **Skip completed phases**:
182
+ - `waiting` → dependency waiting
183
+ - `discovery` → codebase scanning
184
+ - `foundation` → high-level docs
185
+ - `integration` → work item matching
186
+ - `deep-dive` → module analysis (per-module checkpoints)
187
+ - `suggestions` → recommendations
188
+ - `enterprise` → health report
189
+ 3. **Continue from resume point**
190
+
191
+ ```bash
192
+ # Example: Job crashed during deep-dive phase
193
+ /specweave:living-docs --resume abc12345
194
+
195
+ # Output:
196
+ # Resuming from checkpoint: phase=deep-dive, module=auth (5/18)
197
+ # ✓ Skipping completed phases: waiting, discovery, foundation, integration
198
+ # → Continuing deep-dive from module: payments
199
+ ```
200
+
201
+ ---
202
+
203
+ ## Waiting for Dependencies
204
+
205
+ For umbrella projects with clone/import jobs:
206
+
207
+ ```bash
208
+ # Launch after clone completes
209
+ /specweave:living-docs --depends-on clone-xyz123 --depth standard
210
+
211
+ # Launch after both clone and import complete
212
+ /specweave:living-docs --depends-on clone-xyz123,import-abc456
213
+ ```
214
+
215
+ The job will:
216
+ 1. Enter `waiting` phase
217
+ 2. Poll dependency status every 30 seconds
218
+ 3. Start analysis once all dependencies complete
219
+ 4. Warn if any dependency failed (proceeds with available data)
220
+
221
+ ---
222
+
223
+ ## Output Files
224
+
225
+ After completion:
226
+
227
+ | File | Description |
228
+ |------|-------------|
229
+ | `.specweave/docs/SUGGESTIONS.md` | Documentation recommendations by priority |
230
+ | `.specweave/docs/ENTERPRISE-HEALTH.md` | Health score, coverage, accuracy metrics |
231
+ | `.specweave/docs/overview/PROJECT-OVERVIEW.md` | Auto-generated project overview |
232
+ | `.specweave/docs/overview/TECH-STACK.md` | Detected technologies and frameworks |
233
+ | `.specweave/docs/modules/*.md` | Per-module documentation |
234
+
235
+ ---
236
+
237
+ ## Examples
238
+
239
+ ### Example 1: Post-Crash Resume
240
+
241
+ ```bash
242
+ # Claude crashed after init, living docs job orphaned
243
+
244
+ # Step 1: Check what's there
245
+ /specweave:jobs
246
+ # Shows: [ldb-abc123] living-docs-builder - ORPHANED (worker died)
247
+
248
+ # Step 2: Resume
249
+ /specweave:living-docs --resume ldb-abc123
250
+
251
+ # Output:
252
+ # ✅ Resuming Living Docs Builder (ldb-abc123)
253
+ # Last checkpoint: deep-dive phase, module 12/45
254
+ # Continuing from: payments-service
255
+ ```
256
+
257
+ ### Example 2: Large Brownfield (247 repos)
258
+
259
+ ```bash
260
+ # Focus on critical modules first
261
+ /specweave:living-docs --depth deep-native \
262
+ --priority auth,payments,billing,core \
263
+ --depends-on clone-main123
264
+
265
+ # Monitor in another terminal
266
+ /specweave:jobs --follow ldb-xyz789
267
+ ```
268
+
269
+ ### Example 3: CI/CD Integration
270
+
271
+ ```bash
272
+ # In CI pipeline (non-interactive)
273
+ specweave living-docs --depth quick --foreground
274
+
275
+ # Or background with polling
276
+ specweave living-docs --depth standard
277
+ specweave jobs --wait ldb-latest # Wait for completion
278
+ ```
279
+
280
+ ---
281
+
282
+ ## Error Handling
283
+
284
+ ### Worker Crashed
285
+ ```
286
+ /specweave:jobs
287
+ # Shows: ORPHANED status
288
+
289
+ /specweave:living-docs --resume <jobId>
290
+ # Resumes from last checkpoint
291
+ ```
292
+
293
+ ### Dependency Failed
294
+ ```
295
+ ⚠️ Dependency clone-xyz123 failed
296
+ Reason: Network timeout
297
+
298
+ Proceeding with available data...
299
+ Some repositories may be missing from analysis.
300
+ ```
301
+
302
+ ### No Brownfield Detected
303
+ ```
304
+ ℹ️ No existing code detected (greenfield project)
305
+ Living docs will sync automatically as you create increments.
306
+
307
+ To force analysis anyway: /specweave:living-docs --force
308
+ ```
309
+
310
+ ---
311
+
312
+ ## See Also
313
+
314
+ - `/specweave:jobs` - Monitor all background jobs
315
+ - `/specweave:import-docs` - Import existing documentation
316
+ - `specweave:brownfield-analyzer` skill - Analyze doc gaps
317
+ - `specweave:brownfield-onboarder` skill - Merge existing docs
318
+
319
+ ---
320
+
321
+ **Implementation**: `src/cli/commands/living-docs.ts`
@@ -103,22 +103,53 @@ await syncSpecs(args);
103
103
 
104
104
  **This will**:
105
105
  1. **Derive feature ID from increment number** (e.g., 0040 → FS-040, 0002 → FS-002)
106
- 2. **Smart project matching** from (priority order):
107
- - `**Project**:` field in spec.md (explicit)
106
+ 2. **Read project/board from spec.md YAML frontmatter** (v0.31.0+ REQUIRED):
107
+ - For 1-level: `project:` field REQUIRED
108
+ - For 2-level: `project:` AND `board:` fields REQUIRED
109
+ - See [ADR-0190](/internal/architecture/adr/0190-spec-project-board-requirement.md)
110
+ 3. **Smart project matching fallback** (only if YAML fields missing - deprecated):
111
+ - `**Project**:` field in spec.md body (legacy)
108
112
  - `multiProject.activeProject` in config.json
109
113
  - ADO area path / JIRA board mapping
110
114
  - Git remote (repo name)
111
115
  - **ASK USER if unsure** (multi-project mode)
112
- 3. Parse spec.md for user stories and acceptance criteria
113
- 4. Create living docs structure:
114
- - `.specweave/docs/internal/specs/{project}/FS-XXX/FEATURE.md`
115
- - `.specweave/docs/internal/specs/{project}/FS-XXX/us-*.md`
116
+ 4. Parse spec.md for user stories and acceptance criteria
117
+ 5. Create living docs structure:
118
+ - 1-level: `.specweave/docs/internal/specs/{project}/FS-XXX/`
119
+ - 2-level: `.specweave/docs/internal/specs/{project}/{board}/FS-XXX/`
116
120
 
117
121
  **CRITICAL**: Feature ID is DERIVED from increment number (ADR-0187)
118
122
  - Increment 0002-user-authentication → FS-002
119
123
  - Increment 0040-some-feature → FS-040
120
124
  - NO date-based patterns like FS-YY-MM-DD-name
121
125
 
126
+ ### 3.2 Project/Board Validation (v0.31.0+)
127
+
128
+ **Structure Level Detection** (from `src/utils/structure-level-detector.ts`):
129
+ - Detects 1-level vs 2-level from config (ADO, JIRA, umbrella, multiProject, folders)
130
+ - Validates spec.md has required fields based on structure level
131
+ - For 2-level: **ERROR if project OR board missing** (cannot sync)
132
+ - For 1-level: **WARNING if project missing** (uses fallback, deprecated)
133
+
134
+ **Expected spec.md frontmatter**:
135
+
136
+ ```yaml
137
+ # 1-level structure
138
+ ---
139
+ increment: 0001-feature-name
140
+ project: my-project # REQUIRED for 1-level
141
+ ---
142
+
143
+ # 2-level structure
144
+ ---
145
+ increment: 0001-feature-name
146
+ project: acme-corp # REQUIRED for 2-level
147
+ board: clinical-insights # REQUIRED for 2-level
148
+ ---
149
+ ```
150
+
151
+ **Migration**: See [Migration Guide](/public/guides/migration-v031-project-fields.md)
152
+
122
153
  ---
123
154
 
124
155
  ## STEP 4: Report Distribution Results
@@ -20,6 +20,16 @@
20
20
  "command": "bash -c 'F=\"${CLAUDE_PLUGIN_ROOT}/hooks/universal/dispatcher.mjs\"; [ -f \"$F\" ] && node \"$F\" completion-guard || printf \"{\\\"continue\\\":true}\"' 2>/dev/null || printf '{\"continue\":true}'"
21
21
  }
22
22
  ]
23
+ },
24
+ {
25
+ "matcher": "Write",
26
+ "matcher_content": "\\.specweave/increments/\\d{4}-[^/]+/spec\\.md",
27
+ "hooks": [
28
+ {
29
+ "type": "command",
30
+ "command": "bash -c 'F=\"${CLAUDE_PLUGIN_ROOT}/hooks/spec-project-validator.sh\"; [ -f \"$F\" ] && \"$F\" || printf \"{\\\"decision\\\":\\\"allow\\\"}\"' 2>/dev/null || printf '{\"decision\":\"allow\"}'"
31
+ }
32
+ ]
23
33
  }
24
34
  ],
25
35
  "PostToolUse": [
@@ -0,0 +1,111 @@
1
+ #!/bin/bash
2
+ #
3
+ # spec-project-validator.sh
4
+ #
5
+ # Pre-tool-use hook that validates spec.md has required project/board fields
6
+ # before allowing Write tool to create/update spec.md files.
7
+ #
8
+ # Activation:
9
+ # - tool_name: Write
10
+ # - file_path matches: .specweave/increments/*/spec.md
11
+ #
12
+ # Rules:
13
+ # - 1-level structure: spec.md MUST have `project:` in YAML frontmatter
14
+ # - 2-level structure: spec.md MUST have BOTH `project:` AND `board:` in frontmatter
15
+ #
16
+ # Returns exit code 1 (block) if validation fails, 0 (allow) otherwise.
17
+
18
+ set -e
19
+
20
+ # Read tool input from stdin
21
+ INPUT=$(cat)
22
+
23
+ # Extract tool name
24
+ TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // ""')
25
+
26
+ # Only validate Write tool calls
27
+ if [ "$TOOL_NAME" != "Write" ]; then
28
+ echo '{"decision": "allow"}'
29
+ exit 0
30
+ fi
31
+
32
+ # Extract file path
33
+ FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""')
34
+
35
+ # Only validate spec.md files in increments folder
36
+ if [[ ! "$FILE_PATH" =~ \.specweave/increments/[0-9]{4}-[^/]+/spec\.md$ ]]; then
37
+ echo '{"decision": "allow"}'
38
+ exit 0
39
+ fi
40
+
41
+ # Extract file content
42
+ CONTENT=$(echo "$INPUT" | jq -r '.tool_input.content // ""')
43
+
44
+ # Check if content has YAML frontmatter
45
+ if [[ ! "$CONTENT" =~ ^---$'\n' ]]; then
46
+ echo '{"decision": "block", "reason": "spec.md must have YAML frontmatter (starting with ---)"}'
47
+ exit 0
48
+ fi
49
+
50
+ # Extract YAML frontmatter
51
+ FRONTMATTER=$(echo "$CONTENT" | sed -n '/^---$/,/^---$/p' | tail -n +2 | head -n -1)
52
+
53
+ # Check for unresolved project placeholder
54
+ if echo "$FRONTMATTER" | grep -q 'project:\s*{{PROJECT_ID}}'; then
55
+ echo '{"decision": "block", "reason": "spec.md has unresolved placeholder {{PROJECT_ID}}. Run STEP 0B to select project before creating spec.md."}'
56
+ exit 0
57
+ fi
58
+
59
+ # Check for unresolved board placeholder
60
+ if echo "$FRONTMATTER" | grep -q 'board:\s*{{BOARD_ID}}'; then
61
+ echo '{"decision": "block", "reason": "spec.md has unresolved placeholder {{BOARD_ID}}. Run STEP 0B to select board before creating spec.md."}'
62
+ exit 0
63
+ fi
64
+
65
+ # Check for project field
66
+ PROJECT=$(echo "$FRONTMATTER" | grep -E '^project:\s*' | sed 's/^project:\s*//' | tr -d '"'"'" | tr -d '[:space:]')
67
+
68
+ # Detect structure level by checking config
69
+ PROJECT_ROOT="${FILE_PATH%%/.specweave/*}"
70
+ CONFIG_PATH="$PROJECT_ROOT/.specweave/config.json"
71
+
72
+ IS_2_LEVEL=false
73
+
74
+ if [ -f "$CONFIG_PATH" ]; then
75
+ # Check for ADO area path mapping (indicates 2-level)
76
+ if jq -e '.sync.profiles | to_entries[] | select(.value.provider == "ado") | .value.config.areaPathMapping.mappings | length > 0' "$CONFIG_PATH" >/dev/null 2>&1; then
77
+ IS_2_LEVEL=true
78
+ fi
79
+
80
+ # Check for ADO areaPaths (indicates 2-level)
81
+ if jq -e '.sync.profiles | to_entries[] | select(.value.provider == "ado") | .value.config.areaPaths | length > 0' "$CONFIG_PATH" >/dev/null 2>&1; then
82
+ IS_2_LEVEL=true
83
+ fi
84
+ fi
85
+
86
+ # Validation based on structure level
87
+ if [ "$IS_2_LEVEL" = true ]; then
88
+ # 2-level: BOTH project AND board required
89
+ if [ -z "$PROJECT" ] || [ "$PROJECT" = "null" ]; then
90
+ echo '{"decision": "block", "reason": "spec.md missing required '\''project:'\'' field in YAML frontmatter. This is a 2-level structure - add '\''project: <project_name>'\'' to frontmatter."}'
91
+ exit 0
92
+ fi
93
+
94
+ BOARD=$(echo "$FRONTMATTER" | grep -E '^board:\s*' | sed 's/^board:\s*//' | tr -d '"'"'" | tr -d '[:space:]')
95
+
96
+ if [ -z "$BOARD" ] || [ "$BOARD" = "null" ]; then
97
+ echo '{"decision": "block", "reason": "spec.md missing required '\''board:'\'' field in YAML frontmatter. This is a 2-level structure - add '\''board: <board_name>'\'' to frontmatter."}'
98
+ exit 0
99
+ fi
100
+ else
101
+ # 1-level: project is strongly recommended (warning, not block)
102
+ if [ -z "$PROJECT" ] || [ "$PROJECT" = "null" ]; then
103
+ # For 1-level, we warn but don't block (backward compatibility)
104
+ echo '{"decision": "allow", "message": "⚠️ spec.md should have '\''project:'\'' field in YAML frontmatter for explicit sync target."}'
105
+ exit 0
106
+ fi
107
+ fi
108
+
109
+ # All validations passed
110
+ echo '{"decision": "allow"}'
111
+ exit 0
@@ -26,15 +26,24 @@ GITHUB_ENABLED=$(grep -o '"enabled"[[:space:]]*:[[:space:]]*true' "$CONFIG_FILE"
26
26
 
27
27
  # Throttle: max once per 5 minutes per increment
28
28
  THROTTLE_FILE="$PROJECT_ROOT/.specweave/state/.github-sync-$INC_ID"
29
+ THROTTLE_LOG="$PROJECT_ROOT/.specweave/logs/throttle.log"
30
+ THROTTLE_WINDOW=300 # 5 minutes
31
+ mkdir -p "$(dirname "$THROTTLE_LOG")" 2>/dev/null
32
+
29
33
  if [[ -f "$THROTTLE_FILE" ]]; then
30
34
  if [[ "$(uname)" == "Darwin" ]]; then
31
35
  AGE=$(($(date +%s) - $(stat -f %m "$THROTTLE_FILE" 2>/dev/null || echo 0)))
32
36
  else
33
37
  AGE=$(($(date +%s) - $(stat -c %Y "$THROTTLE_FILE" 2>/dev/null || echo 0)))
34
38
  fi
35
- [[ $AGE -lt 300 ]] && exit 0
39
+ if [[ $AGE -lt $THROTTLE_WINDOW ]]; then
40
+ REMAINING=$((THROTTLE_WINDOW - AGE))
41
+ echo "[$(date '+%Y-%m-%d %H:%M:%S')] [github-sync] THROTTLED $INC_ID (wait ${REMAINING}s, use /specweave:sync-progress to bypass)" >> "$THROTTLE_LOG" 2>/dev/null
42
+ exit 0
43
+ fi
36
44
  fi
37
45
  touch "$THROTTLE_FILE"
46
+ echo "[$(date '+%Y-%m-%d %H:%M:%S')] [github-sync] EXECUTING $INC_ID" >> "$THROTTLE_LOG" 2>/dev/null
38
47
 
39
48
  # Cross-platform timeout wrapper
40
49
  run_with_timeout() {
@@ -19,15 +19,24 @@ done
19
19
 
20
20
  # Throttle: max once per minute per increment
21
21
  THROTTLE_FILE="$PROJECT_ROOT/.specweave/state/.living-docs-$INC_ID"
22
+ THROTTLE_LOG="$PROJECT_ROOT/.specweave/logs/throttle.log"
23
+ THROTTLE_WINDOW=60 # 1 minute
24
+ mkdir -p "$(dirname "$THROTTLE_LOG")" 2>/dev/null
25
+
22
26
  if [[ -f "$THROTTLE_FILE" ]]; then
23
27
  if [[ "$(uname)" == "Darwin" ]]; then
24
28
  AGE=$(($(date +%s) - $(stat -f %m "$THROTTLE_FILE" 2>/dev/null || echo 0)))
25
29
  else
26
30
  AGE=$(($(date +%s) - $(stat -c %Y "$THROTTLE_FILE" 2>/dev/null || echo 0)))
27
31
  fi
28
- [[ $AGE -lt 60 ]] && exit 0
32
+ if [[ $AGE -lt $THROTTLE_WINDOW ]]; then
33
+ REMAINING=$((THROTTLE_WINDOW - AGE))
34
+ echo "[$(date '+%Y-%m-%d %H:%M:%S')] [living-docs] THROTTLED $INC_ID (wait ${REMAINING}s)" >> "$THROTTLE_LOG" 2>/dev/null
35
+ exit 0
36
+ fi
29
37
  fi
30
38
  touch "$THROTTLE_FILE"
39
+ echo "[$(date '+%Y-%m-%d %H:%M:%S')] [living-docs] EXECUTING $INC_ID" >> "$THROTTLE_LOG" 2>/dev/null
31
40
 
32
41
  # Cross-platform timeout wrapper
33
42
  run_with_timeout() {