@paths.design/caws-cli 9.3.2 → 10.1.0

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 (286) hide show
  1. package/README.md +71 -32
  2. package/dist/budget-derivation.js +221 -74
  3. package/dist/commands/archive.js +67 -28
  4. package/dist/commands/burnup.js +20 -11
  5. package/dist/commands/diagnose.js +34 -22
  6. package/dist/commands/evaluate.js +41 -15
  7. package/dist/commands/gates.js +149 -0
  8. package/dist/commands/init.js +150 -19
  9. package/dist/commands/iterate.js +81 -4
  10. package/dist/commands/parallel.js +4 -0
  11. package/dist/commands/plan.js +9 -19
  12. package/dist/commands/provenance.js +53 -17
  13. package/dist/commands/quality-monitor.js +64 -45
  14. package/dist/commands/scope.js +264 -0
  15. package/dist/commands/sidecar.js +74 -0
  16. package/dist/commands/specs.js +381 -45
  17. package/dist/commands/status.js +117 -9
  18. package/dist/commands/templates.js +0 -8
  19. package/dist/commands/tutorial.js +10 -9
  20. package/dist/commands/validate.js +70 -6
  21. package/dist/commands/verify-acs.js +48 -76
  22. package/dist/commands/waivers.js +212 -13
  23. package/dist/commands/worktree.js +131 -26
  24. package/dist/error-handler.js +2 -13
  25. package/dist/gates/budget-limit.js +121 -0
  26. package/dist/gates/feedback.js +260 -0
  27. package/dist/gates/format.js +179 -0
  28. package/dist/gates/god-object.js +117 -0
  29. package/dist/gates/pipeline.js +167 -0
  30. package/dist/gates/scope-boundary.js +93 -0
  31. package/dist/gates/spec-completeness.js +109 -0
  32. package/dist/gates/todo-detection.js +205 -0
  33. package/dist/index.js +157 -151
  34. package/dist/parallel/parallel-manager.js +3 -3
  35. package/dist/policy/PolicyManager.js +51 -17
  36. package/dist/scaffold/claude-hooks.js +24 -1
  37. package/dist/scaffold/git-hooks.js +45 -102
  38. package/dist/scaffold/index.js +4 -3
  39. package/dist/session/session-manager.js +105 -14
  40. package/dist/sidecars/index.js +33 -0
  41. package/dist/sidecars/listeners.js +40 -0
  42. package/dist/sidecars/provenance-summary.js +238 -0
  43. package/dist/sidecars/quality-gaps.js +258 -0
  44. package/dist/sidecars/schema.js +149 -0
  45. package/dist/sidecars/spec-drift.js +151 -0
  46. package/dist/sidecars/waiver-draft.js +176 -0
  47. package/dist/templates/.caws/schemas/policy.schema.json +112 -0
  48. package/dist/templates/.caws/schemas/scope.schema.json +3 -3
  49. package/dist/templates/.caws/schemas/waivers.schema.json +96 -20
  50. package/dist/templates/.caws/schemas/working-spec.schema.json +264 -57
  51. package/dist/templates/.caws/schemas/worktrees.schema.json +3 -1
  52. package/dist/templates/.caws/templates/working-spec.template.yml +10 -4
  53. package/dist/templates/.caws/tools/scope-guard.js +66 -15
  54. package/dist/templates/.claude/README.md +1 -1
  55. package/dist/templates/.claude/hooks/audit.sh +0 -0
  56. package/dist/templates/.claude/hooks/block-dangerous.sh +52 -11
  57. package/dist/templates/.claude/hooks/classify_command.py +592 -0
  58. package/dist/templates/.claude/hooks/doc-frontmatter-check.sh +173 -0
  59. package/dist/templates/.claude/hooks/protected-paths.sh +39 -0
  60. package/dist/templates/.claude/hooks/quality-check.sh +23 -10
  61. package/dist/templates/.claude/hooks/scope-guard.sh +136 -55
  62. package/dist/templates/.claude/hooks/session-caws-status.sh +2 -2
  63. package/dist/templates/.claude/hooks/session-log.sh +76 -3
  64. package/dist/templates/.claude/hooks/stop-worktree-check.sh +1 -1
  65. package/dist/templates/.claude/hooks/test_classify_command.py +370 -0
  66. package/dist/templates/.claude/hooks/test_wrapper_smoke.sh +96 -0
  67. package/dist/templates/.claude/hooks/worktree-guard.sh +2 -2
  68. package/dist/templates/.claude/hooks/worktree-write-guard.sh +97 -4
  69. package/dist/templates/.claude/settings.json +31 -0
  70. package/dist/templates/.cursor/hooks/caws-quality-check.sh +4 -4
  71. package/dist/templates/.cursor/hooks/caws-scope-guard.sh +1 -1
  72. package/dist/templates/.cursor/hooks/session-log.sh +924 -0
  73. package/dist/templates/.cursor/hooks.json +25 -0
  74. package/dist/templates/.cursor/rules/02-quality-gates.mdc +3 -5
  75. package/dist/templates/.cursor/rules/10-documentation-quality-standards.mdc +6 -11
  76. package/dist/templates/.cursor/rules/11-scope-management-waivers.mdc +14 -18
  77. package/dist/templates/.cursor/rules/12-implementation-completeness.mdc +4 -4
  78. package/dist/templates/.cursor/rules/13-language-agnostic-standards.mdc +3 -13
  79. package/dist/templates/.github/copilot-instructions.md +5 -5
  80. package/dist/templates/.idea/runConfigurations/CAWS_Evaluate.xml +1 -1
  81. package/dist/templates/.junie/guidelines.md +2 -2
  82. package/dist/templates/.vscode/settings.json +3 -1
  83. package/dist/templates/.windsurf/rules/caws-quality-standards.md +2 -2
  84. package/dist/templates/.windsurf/workflows/caws-guided-development.md +3 -3
  85. package/dist/templates/CLAUDE.md +77 -8
  86. package/dist/templates/agents.md +50 -9
  87. package/dist/templates/docs/README.md +8 -7
  88. package/dist/templates/scripts/new_feature.sh +80 -0
  89. package/dist/test-analysis.js +43 -30
  90. package/dist/tool-loader.js +1 -1
  91. package/dist/utils/agent-session.js +202 -0
  92. package/dist/utils/detection.js +8 -2
  93. package/dist/utils/event-log.js +584 -0
  94. package/dist/utils/event-renderer.js +521 -0
  95. package/dist/utils/finalization.js +7 -6
  96. package/dist/utils/gitignore-updater.js +3 -0
  97. package/dist/utils/lifecycle-events.js +94 -0
  98. package/dist/utils/quality-gates-utils.js +29 -44
  99. package/dist/utils/schema-validator.js +50 -0
  100. package/dist/utils/spec-resolver.js +93 -21
  101. package/dist/utils/working-state.js +530 -0
  102. package/dist/validation/spec-validation.js +191 -31
  103. package/dist/waivers-manager.js +144 -6
  104. package/dist/worktree/worktree-manager.js +598 -95
  105. package/package.json +9 -8
  106. package/templates/.caws/schemas/policy.schema.json +112 -0
  107. package/templates/.caws/schemas/scope.schema.json +3 -3
  108. package/templates/.caws/schemas/waivers.schema.json +96 -20
  109. package/templates/.caws/schemas/working-spec.schema.json +264 -57
  110. package/templates/.caws/schemas/worktrees.schema.json +3 -1
  111. package/templates/.caws/templates/working-spec.template.yml +10 -4
  112. package/templates/.caws/tools/scope-guard.js +66 -15
  113. package/templates/.claude/README.md +1 -1
  114. package/templates/.claude/hooks/block-dangerous.sh +52 -11
  115. package/templates/.claude/hooks/classify_command.py +592 -0
  116. package/templates/.claude/hooks/doc-frontmatter-check.sh +173 -0
  117. package/templates/.claude/hooks/protected-paths.sh +39 -0
  118. package/templates/.claude/hooks/quality-check.sh +23 -10
  119. package/templates/.claude/hooks/scope-guard.sh +136 -55
  120. package/templates/.claude/hooks/session-caws-status.sh +2 -2
  121. package/templates/.claude/hooks/session-log.sh +76 -3
  122. package/templates/.claude/hooks/stop-worktree-check.sh +1 -1
  123. package/templates/.claude/hooks/test_classify_command.py +370 -0
  124. package/templates/.claude/hooks/test_wrapper_smoke.sh +96 -0
  125. package/templates/.claude/hooks/worktree-guard.sh +2 -2
  126. package/templates/.claude/hooks/worktree-write-guard.sh +97 -4
  127. package/templates/.claude/settings.json +31 -0
  128. package/templates/.cursor/hooks/caws-quality-check.sh +4 -4
  129. package/templates/.cursor/hooks/caws-scope-guard.sh +1 -1
  130. package/templates/.cursor/hooks/session-log.sh +924 -0
  131. package/templates/.cursor/hooks.json +25 -0
  132. package/templates/.cursor/rules/02-quality-gates.mdc +3 -5
  133. package/templates/.cursor/rules/10-documentation-quality-standards.mdc +6 -11
  134. package/templates/.cursor/rules/11-scope-management-waivers.mdc +14 -18
  135. package/templates/.cursor/rules/12-implementation-completeness.mdc +4 -4
  136. package/templates/.cursor/rules/13-language-agnostic-standards.mdc +3 -13
  137. package/templates/.github/copilot-instructions.md +5 -5
  138. package/templates/.idea/runConfigurations/CAWS_Evaluate.xml +1 -1
  139. package/templates/.junie/guidelines.md +2 -2
  140. package/templates/.vscode/settings.json +3 -1
  141. package/templates/.windsurf/rules/caws-quality-standards.md +2 -2
  142. package/templates/.windsurf/workflows/caws-guided-development.md +3 -3
  143. package/templates/CLAUDE.md +77 -8
  144. package/templates/{AGENTS.md → agents.md} +50 -9
  145. package/templates/docs/README.md +8 -7
  146. package/templates/scripts/new_feature.sh +80 -0
  147. package/dist/budget-derivation.d.ts +0 -74
  148. package/dist/budget-derivation.d.ts.map +0 -1
  149. package/dist/cicd-optimizer.d.ts +0 -142
  150. package/dist/cicd-optimizer.d.ts.map +0 -1
  151. package/dist/commands/archive.d.ts +0 -51
  152. package/dist/commands/archive.d.ts.map +0 -1
  153. package/dist/commands/burnup.d.ts +0 -6
  154. package/dist/commands/burnup.d.ts.map +0 -1
  155. package/dist/commands/diagnose.d.ts +0 -52
  156. package/dist/commands/diagnose.d.ts.map +0 -1
  157. package/dist/commands/evaluate.d.ts +0 -8
  158. package/dist/commands/evaluate.d.ts.map +0 -1
  159. package/dist/commands/init.d.ts +0 -5
  160. package/dist/commands/init.d.ts.map +0 -1
  161. package/dist/commands/iterate.d.ts +0 -8
  162. package/dist/commands/iterate.d.ts.map +0 -1
  163. package/dist/commands/mode.d.ts +0 -25
  164. package/dist/commands/mode.d.ts.map +0 -1
  165. package/dist/commands/parallel.d.ts +0 -7
  166. package/dist/commands/parallel.d.ts.map +0 -1
  167. package/dist/commands/plan.d.ts +0 -49
  168. package/dist/commands/plan.d.ts.map +0 -1
  169. package/dist/commands/provenance.d.ts +0 -32
  170. package/dist/commands/provenance.d.ts.map +0 -1
  171. package/dist/commands/quality-gates.d.ts +0 -6
  172. package/dist/commands/quality-gates.d.ts.map +0 -1
  173. package/dist/commands/quality-gates.js +0 -444
  174. package/dist/commands/quality-monitor.d.ts +0 -17
  175. package/dist/commands/quality-monitor.d.ts.map +0 -1
  176. package/dist/commands/session.d.ts +0 -7
  177. package/dist/commands/session.d.ts.map +0 -1
  178. package/dist/commands/specs.d.ts +0 -77
  179. package/dist/commands/specs.d.ts.map +0 -1
  180. package/dist/commands/status.d.ts +0 -44
  181. package/dist/commands/status.d.ts.map +0 -1
  182. package/dist/commands/templates.d.ts +0 -74
  183. package/dist/commands/templates.d.ts.map +0 -1
  184. package/dist/commands/tool.d.ts +0 -13
  185. package/dist/commands/tool.d.ts.map +0 -1
  186. package/dist/commands/troubleshoot.d.ts +0 -8
  187. package/dist/commands/troubleshoot.d.ts.map +0 -1
  188. package/dist/commands/troubleshoot.js +0 -104
  189. package/dist/commands/tutorial.d.ts +0 -55
  190. package/dist/commands/tutorial.d.ts.map +0 -1
  191. package/dist/commands/validate.d.ts +0 -15
  192. package/dist/commands/validate.d.ts.map +0 -1
  193. package/dist/commands/waivers.d.ts +0 -8
  194. package/dist/commands/waivers.d.ts.map +0 -1
  195. package/dist/commands/workflow.d.ts +0 -85
  196. package/dist/commands/workflow.d.ts.map +0 -1
  197. package/dist/commands/worktree.d.ts +0 -7
  198. package/dist/commands/worktree.d.ts.map +0 -1
  199. package/dist/config/index.d.ts +0 -29
  200. package/dist/config/index.d.ts.map +0 -1
  201. package/dist/config/lite-scope.d.ts +0 -33
  202. package/dist/config/lite-scope.d.ts.map +0 -1
  203. package/dist/config/modes.d.ts +0 -264
  204. package/dist/config/modes.d.ts.map +0 -1
  205. package/dist/constants/spec-types.d.ts +0 -93
  206. package/dist/constants/spec-types.d.ts.map +0 -1
  207. package/dist/error-handler.d.ts +0 -151
  208. package/dist/error-handler.d.ts.map +0 -1
  209. package/dist/generators/jest-config-generator.d.ts +0 -32
  210. package/dist/generators/jest-config-generator.d.ts.map +0 -1
  211. package/dist/generators/jest-config.d.ts +0 -32
  212. package/dist/generators/jest-config.d.ts.map +0 -1
  213. package/dist/generators/jest-config.js +0 -242
  214. package/dist/generators/working-spec.d.ts +0 -13
  215. package/dist/generators/working-spec.d.ts.map +0 -1
  216. package/dist/index-new.d.ts +0 -5
  217. package/dist/index-new.d.ts.map +0 -1
  218. package/dist/index-new.js +0 -317
  219. package/dist/index.d.ts +0 -5
  220. package/dist/index.d.ts.map +0 -1
  221. package/dist/index.js.backup +0 -4711
  222. package/dist/minimal-cli.d.ts +0 -3
  223. package/dist/minimal-cli.d.ts.map +0 -1
  224. package/dist/parallel/parallel-manager.d.ts +0 -67
  225. package/dist/parallel/parallel-manager.d.ts.map +0 -1
  226. package/dist/policy/PolicyManager.d.ts +0 -104
  227. package/dist/policy/PolicyManager.d.ts.map +0 -1
  228. package/dist/scaffold/claude-hooks.d.ts +0 -28
  229. package/dist/scaffold/claude-hooks.d.ts.map +0 -1
  230. package/dist/scaffold/cursor-hooks.d.ts +0 -7
  231. package/dist/scaffold/cursor-hooks.d.ts.map +0 -1
  232. package/dist/scaffold/git-hooks.d.ts +0 -38
  233. package/dist/scaffold/git-hooks.d.ts.map +0 -1
  234. package/dist/scaffold/index.d.ts +0 -17
  235. package/dist/scaffold/index.d.ts.map +0 -1
  236. package/dist/session/session-manager.d.ts +0 -94
  237. package/dist/session/session-manager.d.ts.map +0 -1
  238. package/dist/spec/SpecFileManager.d.ts +0 -146
  239. package/dist/spec/SpecFileManager.d.ts.map +0 -1
  240. package/dist/templates/.cursor/hooks/caws-tool-validation.sh +0 -121
  241. package/dist/templates/.github/copilot/instructions.md +0 -311
  242. package/dist/test-analysis.d.ts +0 -231
  243. package/dist/test-analysis.d.ts.map +0 -1
  244. package/dist/tool-interface.d.ts +0 -236
  245. package/dist/tool-interface.d.ts.map +0 -1
  246. package/dist/tool-loader.d.ts +0 -77
  247. package/dist/tool-loader.d.ts.map +0 -1
  248. package/dist/tool-validator.d.ts +0 -72
  249. package/dist/tool-validator.d.ts.map +0 -1
  250. package/dist/utils/async-utils.d.ts +0 -73
  251. package/dist/utils/async-utils.d.ts.map +0 -1
  252. package/dist/utils/command-wrapper.d.ts +0 -66
  253. package/dist/utils/command-wrapper.d.ts.map +0 -1
  254. package/dist/utils/detection.d.ts +0 -14
  255. package/dist/utils/detection.d.ts.map +0 -1
  256. package/dist/utils/error-categories.d.ts +0 -52
  257. package/dist/utils/error-categories.d.ts.map +0 -1
  258. package/dist/utils/finalization.d.ts +0 -17
  259. package/dist/utils/finalization.d.ts.map +0 -1
  260. package/dist/utils/git-lock.d.ts +0 -13
  261. package/dist/utils/git-lock.d.ts.map +0 -1
  262. package/dist/utils/gitignore-updater.d.ts +0 -39
  263. package/dist/utils/gitignore-updater.d.ts.map +0 -1
  264. package/dist/utils/ide-detection.d.ts +0 -89
  265. package/dist/utils/ide-detection.d.ts.map +0 -1
  266. package/dist/utils/project-analysis.d.ts +0 -34
  267. package/dist/utils/project-analysis.d.ts.map +0 -1
  268. package/dist/utils/promise-utils.d.ts +0 -30
  269. package/dist/utils/promise-utils.d.ts.map +0 -1
  270. package/dist/utils/quality-gates-utils.d.ts +0 -49
  271. package/dist/utils/quality-gates-utils.d.ts.map +0 -1
  272. package/dist/utils/quality-gates.d.ts +0 -49
  273. package/dist/utils/quality-gates.d.ts.map +0 -1
  274. package/dist/utils/quality-gates.js +0 -402
  275. package/dist/utils/spec-resolver.d.ts +0 -80
  276. package/dist/utils/spec-resolver.d.ts.map +0 -1
  277. package/dist/utils/typescript-detector.d.ts +0 -66
  278. package/dist/utils/typescript-detector.d.ts.map +0 -1
  279. package/dist/utils/yaml-validation.d.ts +0 -32
  280. package/dist/utils/yaml-validation.d.ts.map +0 -1
  281. package/dist/validation/spec-validation.d.ts +0 -43
  282. package/dist/validation/spec-validation.d.ts.map +0 -1
  283. package/dist/waivers-manager.d.ts +0 -167
  284. package/dist/waivers-manager.d.ts.map +0 -1
  285. package/dist/worktree/worktree-manager.d.ts +0 -54
  286. package/dist/worktree/worktree-manager.d.ts.map +0 -1
@@ -8,9 +8,8 @@
8
8
  */
9
9
 
10
10
  const chalk = require('chalk');
11
- const fs = require('fs');
12
11
  const path = require('path');
13
- const yaml = require('js-yaml');
12
+ const { resolveSpec } = require('../utils/spec-resolver');
14
13
 
15
14
  /**
16
15
  * Analyze quality impact of an action
@@ -79,52 +78,58 @@ function analyzeQualityImpact(action, files = [], context = {}) {
79
78
  analysis.recommendations = ['Run CAWS evaluation to assess impact'];
80
79
  }
81
80
 
82
- // Load working spec to check risk tier
83
- try {
84
- const specPath = path.join(process.cwd(), '.caws/working-spec.yaml');
85
- if (fs.existsSync(specPath)) {
86
- const spec = yaml.load(fs.readFileSync(specPath, 'utf8'));
87
- const projectTier = spec.risk_tier;
88
-
89
- analysis.project_tier = projectTier;
90
-
91
- // Add context-specific recommendations for high-risk projects
92
- if (projectTier <= 2) {
93
- analysis.recommendations.unshift(
94
- 'High-risk project (Tier ' + projectTier + '): Run comprehensive validation'
95
- );
96
- if (analysis.risk_level === 'low') {
97
- analysis.risk_level = 'medium';
98
- } else if (analysis.risk_level === 'medium') {
99
- analysis.risk_level = 'high';
100
- }
101
- }
81
+ // Add context-based recommendations
82
+ if (context.project_tier) {
83
+ analysis.project_tier = context.project_tier;
84
+ }
102
85
 
103
- // Add tier-specific quality gates
104
- if (projectTier === 1) {
105
- analysis.quality_gates = [
106
- 'Branch coverage ≥ 90%',
107
- 'Mutation score 70%',
108
- 'All contract tests passing',
109
- 'Manual code review required',
110
- ];
111
- } else if (projectTier === 2) {
112
- analysis.quality_gates = [
113
- 'Branch coverage ≥ 80%',
114
- 'Mutation score ≥ 50%',
115
- 'Contract tests passing (if applicable)',
116
- ];
117
- } else {
118
- analysis.quality_gates = ['Branch coverage ≥ 70%', 'Mutation score ≥ 30%'];
119
- }
86
+ return analysis;
87
+ }
88
+
89
+ /**
90
+ * Enrich analysis with project tier and resolved spec metadata.
91
+ * @param {Object} analysis
92
+ * @param {Object|null} resolvedSpec
93
+ * @returns {Object}
94
+ */
95
+ function applySpecContext(analysis, resolvedSpec) {
96
+ if (!resolvedSpec?.spec) {
97
+ return analysis;
98
+ }
99
+
100
+ const projectTier = resolvedSpec.spec.risk_tier;
101
+ analysis.project_tier = projectTier;
102
+ analysis.spec_id = resolvedSpec.spec.id || null;
103
+ analysis.spec_path = resolvedSpec.path
104
+ ? path.relative(process.cwd(), resolvedSpec.path)
105
+ : null;
106
+
107
+ if (projectTier <= 2) {
108
+ analysis.recommendations.unshift(
109
+ 'High-risk project (Tier ' + projectTier + '): Run comprehensive validation'
110
+ );
111
+ if (analysis.risk_level === 'low') {
112
+ analysis.risk_level = 'medium';
113
+ } else if (analysis.risk_level === 'medium') {
114
+ analysis.risk_level = 'high';
120
115
  }
121
- } catch (error) {
122
- // Ignore if we can't load spec
123
116
  }
124
117
 
125
- // Add context-based recommendations
126
- if (context.project_tier) {
127
- analysis.project_tier = context.project_tier;
118
+ if (projectTier === 1) {
119
+ analysis.quality_gates = [
120
+ 'Branch coverage ≥ 90%',
121
+ 'Mutation score ≥ 70%',
122
+ 'All contract tests passing',
123
+ 'Manual code review required',
124
+ ];
125
+ } else if (projectTier === 2) {
126
+ analysis.quality_gates = [
127
+ 'Branch coverage ≥ 80%',
128
+ 'Mutation score ≥ 50%',
129
+ 'Contract tests passing (if applicable)',
130
+ ];
131
+ } else {
132
+ analysis.quality_gates = ['Branch coverage ≥ 70%', 'Mutation score ≥ 30%'];
128
133
  }
129
134
 
130
135
  return analysis;
@@ -170,7 +175,18 @@ async function qualityMonitorCommand(action, options = {}) {
170
175
  }
171
176
 
172
177
  // Analyze quality impact
173
- const analysis = analyzeQualityImpact(action, files, context);
178
+ let analysis = analyzeQualityImpact(action, files, context);
179
+
180
+ try {
181
+ const resolved = await resolveSpec({
182
+ specId: options.specId,
183
+ warnLegacy: false,
184
+ interactive: false,
185
+ });
186
+ analysis = applySpecContext(analysis, resolved);
187
+ } catch {
188
+ // Best-effort enrichment only
189
+ }
174
190
 
175
191
  // Display results
176
192
  console.log(chalk.bold('\nCAWS Quality Monitor\n'));
@@ -219,6 +235,9 @@ async function qualityMonitorCommand(action, options = {}) {
219
235
  // Project tier
220
236
  if (analysis.project_tier) {
221
237
  console.log(chalk.bold(`\nProject Tier: ${analysis.project_tier}`));
238
+ if (analysis.spec_id) {
239
+ console.log(chalk.gray(`Spec: ${analysis.spec_id}${analysis.spec_path ? ` (${analysis.spec_path})` : ''}`));
240
+ }
222
241
  }
223
242
 
224
243
  // Quality gates
@@ -0,0 +1,264 @@
1
+ /**
2
+ * @fileoverview CAWS Scope CLI Command
3
+ * Inspects and displays effective scope boundaries for the current context
4
+ * @author @darianrosebrook
5
+ */
6
+
7
+ const chalk = require('chalk');
8
+ const path = require('path');
9
+ const fs = require('fs-extra');
10
+ const yaml = require('js-yaml');
11
+ const {
12
+ getRepoRoot,
13
+ loadRegistry,
14
+ findFeatureSpecPath,
15
+ WORKTREES_DIR,
16
+ } = require('../worktree/worktree-manager');
17
+
18
+ /**
19
+ * Handle scope subcommands
20
+ * @param {string} subcommand - Subcommand name
21
+ * @param {Object} options - Command options
22
+ */
23
+ async function scopeCommand(subcommand, options = {}) {
24
+ try {
25
+ switch (subcommand) {
26
+ case 'show':
27
+ return handleShow(options);
28
+ default:
29
+ console.error(chalk.red(`Unknown scope subcommand: ${subcommand}`));
30
+ console.log(chalk.blue('Available: show'));
31
+ process.exit(1);
32
+ }
33
+ } catch (error) {
34
+ console.error(chalk.red(`${error.message}`));
35
+ process.exit(1);
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Detect if current working directory is inside a worktree
41
+ * @param {string} root - Repository root
42
+ * @returns {{ inWorktree: boolean, worktreeName: string|null }}
43
+ */
44
+ function detectWorktreeContext(root) {
45
+ const cwd = process.cwd();
46
+ const worktreesBase = path.join(root, WORKTREES_DIR);
47
+
48
+ if (!cwd.startsWith(worktreesBase + path.sep) && cwd !== worktreesBase) {
49
+ return { inWorktree: false, worktreeName: null };
50
+ }
51
+
52
+ // Extract worktree name: first path segment after the worktrees dir
53
+ const relative = path.relative(worktreesBase, cwd);
54
+ const worktreeName = relative.split(path.sep)[0];
55
+
56
+ if (!worktreeName) {
57
+ return { inWorktree: false, worktreeName: null };
58
+ }
59
+
60
+ return { inWorktree: true, worktreeName };
61
+ }
62
+
63
+ /**
64
+ * Load a spec file and return its parsed contents
65
+ * @param {string} specPath - Absolute path to spec YAML
66
+ * @returns {Object|null}
67
+ */
68
+ function loadSpec(specPath) {
69
+ try {
70
+ if (!fs.existsSync(specPath)) return null;
71
+ const content = fs.readFileSync(specPath, 'utf8');
72
+ return yaml.load(content);
73
+ } catch {
74
+ return null;
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Find all active spec files in .caws/specs/
80
+ * @param {string} root - Repository root
81
+ * @returns {Array<{ id: string, path: string, data: Object }>}
82
+ */
83
+ function findAllActiveSpecs(root) {
84
+ const specsDir = path.join(root, '.caws', 'specs');
85
+ if (!fs.existsSync(specsDir)) return [];
86
+
87
+ const files = fs.readdirSync(specsDir).filter(f => f.endsWith('.yaml') || f.endsWith('.yml'));
88
+ const specs = [];
89
+
90
+ for (const file of files) {
91
+ const specPath = path.join(specsDir, file);
92
+ const data = loadSpec(specPath);
93
+ if (!data) continue;
94
+
95
+ // Skip closed/archived specs
96
+ const status = (data.status || '').toLowerCase();
97
+ if (status === 'closed' || status === 'archived') continue;
98
+
99
+ const id = path.basename(file, path.extname(file));
100
+ specs.push({ id, path: specPath, data });
101
+ }
102
+
103
+ return specs;
104
+ }
105
+
106
+ /**
107
+ * Print scope patterns for a spec
108
+ * @param {Object} data - Parsed spec YAML
109
+ * @param {string} indent - Indentation prefix
110
+ */
111
+ function printScopePatterns(data, indent = ' ') {
112
+ const scope = data.scope || {};
113
+ const scopeIn = scope.in || scope.include || [];
114
+ const scopeOut = scope.out || scope.exclude || [];
115
+
116
+ if (scopeIn.length > 0) {
117
+ console.log(chalk.green(`${indent}scope.in:`));
118
+ for (const pattern of scopeIn) {
119
+ console.log(chalk.gray(`${indent} - ${pattern}`));
120
+ }
121
+ } else {
122
+ console.log(chalk.yellow(`${indent}scope.in: (none defined)`));
123
+ }
124
+
125
+ if (scopeOut.length > 0) {
126
+ console.log(chalk.red(`${indent}scope.out:`));
127
+ for (const pattern of scopeOut) {
128
+ console.log(chalk.gray(`${indent} - ${pattern}`));
129
+ }
130
+ } else {
131
+ console.log(chalk.gray(`${indent}scope.out: (none)`));
132
+ }
133
+ }
134
+
135
+ /**
136
+ * Handle the 'show' subcommand
137
+ * @param {Object} options - Command options
138
+ */
139
+ function handleShow(_options) {
140
+ const root = getRepoRoot();
141
+ const { inWorktree, worktreeName } = detectWorktreeContext(root);
142
+
143
+ console.log(chalk.bold.cyan('CAWS Scope Inspector'));
144
+ console.log(chalk.cyan('='.repeat(50)));
145
+ console.log('');
146
+
147
+ if (inWorktree) {
148
+ return handleAuthoritativeMode(root, worktreeName);
149
+ } else {
150
+ return handleUnionMode(root);
151
+ }
152
+ }
153
+
154
+ /**
155
+ * Handle authoritative mode: agent is inside a worktree with a bound spec
156
+ * @param {string} root - Repository root
157
+ * @param {string} worktreeName - Name of the worktree
158
+ */
159
+ function handleAuthoritativeMode(root, worktreeName) {
160
+ console.log(chalk.white(`Worktree: ${chalk.bold(worktreeName)}`));
161
+
162
+ const registry = loadRegistry(root);
163
+ const entry = registry.worktrees ? registry.worktrees[worktreeName] : null;
164
+
165
+ if (!entry) {
166
+ console.log(chalk.red(`Worktree '${worktreeName}' not found in registry.`));
167
+ console.log(chalk.yellow('The scope guard is operating in union mode (all active specs).'));
168
+ return handleUnionMode(root);
169
+ }
170
+
171
+ const specId = entry.specId;
172
+
173
+ if (!specId) {
174
+ console.log(chalk.yellow('Mode: union (no spec bound to this worktree)'));
175
+ console.log('');
176
+ console.log(chalk.yellow('This worktree has no spec binding. The scope guard checks'));
177
+ console.log(chalk.yellow('against the union of all active specs.'));
178
+ console.log('');
179
+ console.log(chalk.blue('To bind a spec: caws worktree bind <spec-id>'));
180
+ console.log('');
181
+ return handleUnionMode(root);
182
+ }
183
+
184
+ // Load the spec
185
+ const specPath = findFeatureSpecPath(root, specId);
186
+ if (!specPath) {
187
+ console.log(chalk.red(`Bound spec '${specId}' not found on disk.`));
188
+ console.log(chalk.yellow('Fix: recreate the spec or rebind with a valid spec ID.'));
189
+ console.log(chalk.blue(` caws worktree bind <valid-spec-id>`));
190
+ return;
191
+ }
192
+
193
+ const specData = loadSpec(specPath);
194
+ if (!specData) {
195
+ console.log(chalk.red(`Failed to parse spec file: ${specPath}`));
196
+ return;
197
+ }
198
+
199
+ console.log(chalk.green(`Mode: authoritative (single bound spec)`));
200
+ console.log(chalk.white(`Spec: ${chalk.bold(specId)}`));
201
+ if (specData.title) {
202
+ console.log(chalk.gray(`Title: ${specData.title}`));
203
+ }
204
+ console.log('');
205
+
206
+ // Print scope patterns
207
+ printScopePatterns(specData);
208
+ console.log('');
209
+
210
+ // Check binding health: mutual reference
211
+ const specWorktreeRef = specData.worktree || null;
212
+ const registrySpecRef = specId;
213
+
214
+ if (specWorktreeRef !== worktreeName) {
215
+ console.log(chalk.yellow('Binding health: BROKEN'));
216
+ console.log(chalk.yellow(` Registry points to spec '${registrySpecRef}'`));
217
+ console.log(chalk.yellow(` Spec 'worktree' field: ${specWorktreeRef || '(missing)'} (expected: ${worktreeName})`));
218
+ console.log('');
219
+ console.log(chalk.blue(`Fix: caws worktree bind ${specId}`));
220
+ } else {
221
+ console.log(chalk.green('Binding health: OK'));
222
+ console.log(chalk.gray(` Registry -> spec: ${registrySpecRef}`));
223
+ console.log(chalk.gray(` Spec -> worktree: ${specWorktreeRef}`));
224
+ }
225
+ }
226
+
227
+ /**
228
+ * Handle union mode: no worktree or no spec binding
229
+ * @param {string} root - Repository root
230
+ */
231
+ function handleUnionMode(root) {
232
+ const specs = findAllActiveSpecs(root);
233
+
234
+ if (specs.length === 0) {
235
+ console.log(chalk.gray('Mode: union (no active specs found)'));
236
+ console.log('');
237
+ console.log(chalk.gray('No active feature specs in .caws/specs/.'));
238
+ console.log(chalk.gray('The scope guard has no patterns to enforce.'));
239
+ console.log('');
240
+ console.log(chalk.blue('Create a spec: caws specs create <id> --title "description"'));
241
+ return;
242
+ }
243
+
244
+ console.log(chalk.white('Mode: union (checking all active specs)'));
245
+ console.log(chalk.gray(`Active specs: ${specs.length}`));
246
+ console.log('');
247
+
248
+ for (const spec of specs) {
249
+ const statusLabel = spec.data.status || 'draft';
250
+ console.log(chalk.white(` ${chalk.bold(spec.id)} [${statusLabel}]`));
251
+ if (spec.data.title) {
252
+ console.log(chalk.gray(` Title: ${spec.data.title}`));
253
+ }
254
+ printScopePatterns(spec.data, ' ');
255
+
256
+ // Check if this spec has a worktree binding
257
+ if (spec.data.worktree) {
258
+ console.log(chalk.gray(` worktree: ${spec.data.worktree}`));
259
+ }
260
+ console.log('');
261
+ }
262
+ }
263
+
264
+ module.exports = { scopeCommand };
@@ -0,0 +1,74 @@
1
+ /**
2
+ * @fileoverview CAWS Sidecar Command
3
+ * CLI interface for bounded governance sidecars — advisory analysis modules
4
+ * that consume working state and produce structured recommendations.
5
+ * @author @darianrosebrook
6
+ */
7
+
8
+ const chalk = require('chalk');
9
+ const { resolveSpec } = require('../utils/spec-resolver');
10
+ // EVLOG-002 Phase 2 read flip: sidecars read state from the event log via the
11
+ // pure renderer. loadStateFromEvents matches loadState's null contract exactly,
12
+ // so the existing "state may be null — sidecars handle that" behavior stays.
13
+ const { loadStateFromEvents } = require('../utils/event-renderer');
14
+ const { commandWrapper } = require('../utils/command-wrapper');
15
+ const { SIDECARS, formatSidecarText } = require('../sidecars');
16
+
17
+ /**
18
+ * Run a sidecar analysis.
19
+ * @param {string} subcommand - Sidecar name (drift, gaps, waiver-draft, provenance)
20
+ * @param {Object} options - Command options
21
+ * @param {string} [options.specId] - Target spec ID
22
+ * @param {boolean} [options.json] - Output as JSON
23
+ * @param {string} [options.gate] - Gate name filter (waiver-draft only)
24
+ */
25
+ async function sidecarCommand(subcommand, options = {}) {
26
+ return commandWrapper(
27
+ async () => {
28
+ const sidecar = SIDECARS[subcommand];
29
+ if (!sidecar) {
30
+ const available = Object.keys(SIDECARS).join(', ');
31
+ console.error(chalk.red(`Unknown sidecar: ${subcommand}`));
32
+ console.error(chalk.yellow(`Available: ${available}`));
33
+ process.exit(1);
34
+ }
35
+
36
+ // Resolve spec
37
+ let spec = null;
38
+ try {
39
+ const resolved = await resolveSpec({
40
+ specId: options.specId,
41
+ warnLegacy: false,
42
+ quiet: Boolean(options.json),
43
+ });
44
+ spec = resolved.spec;
45
+ } catch (err) {
46
+ console.error(chalk.red(`Could not resolve spec: ${err.message}`));
47
+ console.error(chalk.yellow('Use --spec-id <id> to target a specific spec.'));
48
+ process.exit(1);
49
+ }
50
+
51
+ // Load working state (may be null — sidecars handle that; EVLOG-002: from event log)
52
+ const state = loadStateFromEvents(spec.id);
53
+
54
+ // Build sidecar-specific options
55
+ const sidecarOptions = {};
56
+ if (options.gate) sidecarOptions.gateName = options.gate;
57
+
58
+ // Run sidecar
59
+ const result = sidecar.fn(state, spec, sidecarOptions);
60
+
61
+ // Output
62
+ if (options.json) {
63
+ console.log(JSON.stringify(result, null, 2));
64
+ } else {
65
+ console.log(formatSidecarText(result));
66
+ }
67
+
68
+ return result;
69
+ },
70
+ { commandName: `sidecar ${subcommand}` }
71
+ );
72
+ }
73
+
74
+ module.exports = { sidecarCommand };