@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
@@ -10,6 +10,7 @@ const yaml = require('js-yaml');
10
10
  const chalk = require('chalk');
11
11
  const { execSync } = require('child_process');
12
12
  const { safeAsync, outputResult } = require('../error-handler');
13
+ const { findProjectRoot } = require('../utils/detection');
13
14
 
14
15
  // Import spec resolution system
15
16
  const { resolveSpec } = require('../utils/spec-resolver');
@@ -20,7 +21,8 @@ const { resolveSpec } = require('../utils/spec-resolver');
20
21
  * @returns {Promise<Object|null>} Change data or null
21
22
  */
22
23
  async function loadChange(changeId) {
23
- const changesDir = '.caws/changes';
24
+ const projectRoot = findProjectRoot();
25
+ const changesDir = path.join(projectRoot, '.caws/changes');
24
26
  const changePath = path.join(changesDir, changeId);
25
27
 
26
28
  if (!(await fs.pathExists(changePath))) {
@@ -44,10 +46,11 @@ async function loadChange(changeId) {
44
46
  path: changePath,
45
47
  metadata,
46
48
  workingSpec,
49
+ workingSpecPath: (await fs.pathExists(workingSpecPath)) ? workingSpecPath : null,
47
50
  exists: true,
48
51
  };
49
52
  } catch (error) {
50
- return null;
53
+ throw new Error(`Failed to load change '${changeId}': ${error.message}`);
51
54
  }
52
55
  }
53
56
 
@@ -57,18 +60,24 @@ async function loadChange(changeId) {
57
60
  * @returns {Promise<Object>} Validation result
58
61
  */
59
62
  async function validateAcceptanceCriteria(workingSpec) {
60
- if (!workingSpec || !workingSpec.acceptance_criteria) {
63
+ const criteria = Array.isArray(workingSpec?.acceptance_criteria)
64
+ ? workingSpec.acceptance_criteria
65
+ : Array.isArray(workingSpec?.acceptance)
66
+ ? workingSpec.acceptance
67
+ : [];
68
+
69
+ if (!workingSpec || criteria.length === 0) {
61
70
  return {
62
71
  valid: false,
63
72
  message: 'No acceptance criteria found in working spec',
64
73
  };
65
74
  }
66
75
 
67
- const criteria = workingSpec.acceptance_criteria;
76
+ const hasCompletionTracking = criteria.some((criterion) => criterion.completed !== undefined);
68
77
  const incomplete = [];
69
78
 
70
79
  for (const criterion of criteria) {
71
- if (!criterion.completed) {
80
+ if (criterion.completed === false) {
72
81
  incomplete.push(criterion.id || 'unknown');
73
82
  }
74
83
  }
@@ -80,6 +89,13 @@ async function validateAcceptanceCriteria(workingSpec) {
80
89
  };
81
90
  }
82
91
 
92
+ if (!hasCompletionTracking) {
93
+ return {
94
+ valid: true,
95
+ message: `Acceptance criteria present (${criteria.length}); no explicit completion flags found`,
96
+ };
97
+ }
98
+
83
99
  return {
84
100
  valid: true,
85
101
  message: `All ${criteria.length} acceptance criteria completed`,
@@ -212,8 +228,8 @@ async function validateQualityGates(_changeId) {
212
228
  * @param {Object} change - Change data
213
229
  * @returns {Promise<string>} Summary text
214
230
  */
215
- async function generateChangeSummary(change) {
216
- const { workingSpec, metadata } = change;
231
+ async function generateChangeSummary(change, workingSpec) {
232
+ const { metadata } = change;
217
233
 
218
234
  let summary = `# Change Summary: ${change.id}\n\n`;
219
235
 
@@ -267,7 +283,7 @@ async function archiveChange(change) {
267
283
  * @param {Object} change - Change data
268
284
  * @returns {Promise<void>}
269
285
  */
270
- async function updateProvenance(change) {
286
+ async function updateProvenance(change, specSelection) {
271
287
  const provenanceDir = '.caws/provenance';
272
288
  const chainPath = path.join(provenanceDir, 'chain.json');
273
289
 
@@ -284,8 +300,11 @@ async function updateProvenance(change) {
284
300
  action: 'change_completed',
285
301
  change_id: change.id,
286
302
  metadata: {
287
- title: change.workingSpec?.title,
288
- risk_tier: change.workingSpec?.risk_tier,
303
+ title: specSelection?.spec?.title || change.workingSpec?.title,
304
+ risk_tier: specSelection?.spec?.risk_tier || change.workingSpec?.risk_tier,
305
+ spec_id: specSelection?.spec?.id || change.workingSpec?.id || null,
306
+ spec_path: specSelection?.path || change.workingSpecPath || null,
307
+ spec_type: specSelection?.type || (change.workingSpecPath ? 'change-snapshot' : null),
289
308
  files_changed: change.metadata?.files_changed || 0,
290
309
  lines_added: change.metadata?.lines_added || 0,
291
310
  lines_removed: change.metadata?.lines_removed || 0,
@@ -309,10 +328,20 @@ async function updateProvenance(change) {
309
328
  * @param {Object} validation - Validation result
310
329
  * @param {Object} qualityGates - Quality gates result
311
330
  */
312
- function displayArchiveResults(change, validation, qualityGates) {
331
+ function displayArchiveResults(change, validation, qualityGates, specSelection) {
313
332
  console.log(chalk.bold.cyan(`\nArchiving Change: ${change.id}`));
314
333
  console.log(chalk.cyan('==============================================\n'));
315
334
 
335
+ if (specSelection?.spec) {
336
+ console.log(chalk.blue('Spec Context:'));
337
+ console.log(
338
+ chalk.gray(
339
+ ` ${specSelection.spec.id || 'unknown'} (${specSelection.type}) -> ${specSelection.path}`
340
+ )
341
+ );
342
+ console.log('');
343
+ }
344
+
316
345
  // Validation status
317
346
  if (validation.valid) {
318
347
  console.log(chalk.green('Acceptance Criteria'));
@@ -363,22 +392,27 @@ async function archiveCommand(changeId, options = {}) {
363
392
  }
364
393
 
365
394
  // Resolve spec using priority system
366
- let workingSpec = change.workingSpec;
367
- if (!workingSpec && options.specId) {
368
- // If change doesn't have a working spec but spec-id is provided, load it
369
- try {
370
- const resolved = await resolveSpec({
371
- specId: options.specId,
372
- warnLegacy: false,
373
- });
374
- workingSpec = resolved.spec;
375
- } catch (error) {
376
- console.log(
377
- chalk.yellow(`Could not load spec '${options.specId}': ${error.message}`)
378
- );
379
- }
395
+ let specSelection = null;
396
+ if (options.specId || options.specFile) {
397
+ specSelection = await resolveSpec({
398
+ specId: options.specId,
399
+ specFile: options.specFile,
400
+ warnLegacy: false,
401
+ });
402
+ } else if (change.workingSpec) {
403
+ specSelection = {
404
+ path: change.workingSpecPath || path.join(change.path, 'working-spec.yaml'),
405
+ type: 'change-snapshot',
406
+ spec: change.workingSpec,
407
+ };
408
+ } else {
409
+ specSelection = await resolveSpec({
410
+ warnLegacy: false,
411
+ });
380
412
  }
381
413
 
414
+ const workingSpec = specSelection.spec;
415
+
382
416
  // Validate acceptance criteria
383
417
  const validation = await validateAcceptanceCriteria(workingSpec);
384
418
 
@@ -386,7 +420,7 @@ async function archiveCommand(changeId, options = {}) {
386
420
  const qualityGates = await validateQualityGates(changeId);
387
421
 
388
422
  // Display results
389
- displayArchiveResults(change, validation, qualityGates);
423
+ displayArchiveResults(change, validation, qualityGates, specSelection);
390
424
 
391
425
  // Check if we should proceed with archival
392
426
  if (!validation.valid) {
@@ -423,7 +457,7 @@ async function archiveCommand(changeId, options = {}) {
423
457
  change.metadata.archived = true;
424
458
 
425
459
  // Generate and save summary
426
- const summary = await generateChangeSummary(change);
460
+ const summary = await generateChangeSummary(change, workingSpec);
427
461
  const summaryPath = path.join(change.path, 'archive-summary.md');
428
462
  await fs.writeFile(summaryPath, summary);
429
463
 
@@ -431,7 +465,7 @@ async function archiveCommand(changeId, options = {}) {
431
465
  await archiveChange(change);
432
466
 
433
467
  // Update provenance
434
- await updateProvenance(change);
468
+ await updateProvenance(change, specSelection);
435
469
 
436
470
  console.log(chalk.green(`\nSuccessfully archived change: ${changeId}`));
437
471
 
@@ -439,6 +473,11 @@ async function archiveCommand(changeId, options = {}) {
439
473
  command: 'archive',
440
474
  change: changeId,
441
475
  archived: true,
476
+ specSelection: {
477
+ id: workingSpec?.id || null,
478
+ path: specSelection?.path || null,
479
+ type: specSelection?.type || null,
480
+ },
442
481
  validation: validation.valid,
443
482
  qualityGates: qualityGates.valid,
444
483
  summary: summary,
@@ -11,6 +11,7 @@ const chalk = require('chalk');
11
11
  const { execSync } = require('child_process');
12
12
 
13
13
  const { deriveBudget, generateBurnupReport } = require('../budget-derivation');
14
+ const { resolveSpec } = require('../utils/spec-resolver');
14
15
 
15
16
  /**
16
17
  * Get actual git change statistics from the repository
@@ -86,24 +87,32 @@ function getGitChangeStats(specDir) {
86
87
 
87
88
  /**
88
89
  * Burn-up command handler
89
- * @param {string} specFile - Path to spec file
90
+ * @param {string} specFile - Path to spec file (positional, optional)
91
+ * @param {object} options - Command options including --spec-id
90
92
  */
91
- async function burnupCommand(specFile) {
93
+ async function burnupCommand(specFile, options = {}) {
92
94
  try {
93
- let specPath = specFile || path.join('.caws', 'working-spec.yaml');
94
-
95
- if (!fs.existsSync(specPath)) {
96
- console.error(chalk.red(`Spec file not found: ${specPath}`));
97
- process.exit(1);
95
+ let specPath;
96
+ let spec;
97
+
98
+ // Resolve spec: explicit file > --spec-id > resolver default
99
+ if (specFile) {
100
+ specPath = specFile;
101
+ if (!fs.existsSync(specPath)) {
102
+ console.error(chalk.red(`Spec file not found: ${specPath}`));
103
+ process.exit(1);
104
+ }
105
+ spec = yaml.load(fs.readFileSync(specPath, 'utf8'));
106
+ } else {
107
+ const resolved = await resolveSpec({ specId: options.specId });
108
+ specPath = resolved.path;
109
+ spec = resolved.spec;
98
110
  }
99
111
 
100
- const specContent = fs.readFileSync(specPath, 'utf8');
101
- const spec = yaml.load(specContent);
102
-
103
112
  console.log(chalk.cyan('Generating CAWS budget burn-up report...'));
104
113
 
105
114
  // Derive budget
106
- const derivedBudget = deriveBudget(spec, path.dirname(specPath));
115
+ const derivedBudget = await deriveBudget(spec, path.dirname(specPath));
107
116
 
108
117
  // Get actual git change statistics
109
118
  const gitStats = getGitChangeStats(path.dirname(specPath));
@@ -6,8 +6,8 @@
6
6
 
7
7
  const fs = require('fs-extra');
8
8
  const path = require('path');
9
- const yaml = require('js-yaml');
10
9
  const chalk = require('chalk');
10
+ const { resolveSpec } = require('../utils/spec-resolver');
11
11
 
12
12
  // Import utilities
13
13
  const { checkTypeScriptTestConfig } = require('../utils/typescript-detector');
@@ -17,29 +17,23 @@ const { configureJestForTypeScript } = require('../generators/jest-config-genera
17
17
  * Health check: Working spec validity
18
18
  * @returns {Promise<Object>} Check result
19
19
  */
20
- async function checkWorkingSpec() {
21
- const specPath = '.caws/working-spec.yaml';
22
-
23
- if (!(await fs.pathExists(specPath))) {
24
- return {
25
- passed: false,
26
- severity: 'high',
27
- message: 'Working spec not found',
28
- fix: 'Initialize CAWS: caws init .',
29
- autoFixable: false,
30
- };
31
- }
32
-
20
+ async function checkWorkingSpec(options = {}) {
33
21
  try {
34
- const content = await fs.readFile(specPath, 'utf8');
35
- const spec = yaml.load(content);
22
+ const resolved = await resolveSpec({
23
+ specId: options.specId,
24
+ specFile: options.spec,
25
+ warnLegacy: false,
26
+ interactive: false,
27
+ });
28
+ const spec = resolved.spec;
29
+ const specPath = path.relative(process.cwd(), resolved.path);
36
30
 
37
31
  // Basic validation
38
32
  if (!spec.id || !spec.title || !spec.risk_tier) {
39
33
  return {
40
34
  passed: false,
41
35
  severity: 'high',
42
- message: 'Working spec missing required fields',
36
+ message: `Spec missing required fields (${specPath})`,
43
37
  fix: 'Run: caws validate for details',
44
38
  autoFixable: false,
45
39
  };
@@ -47,13 +41,31 @@ async function checkWorkingSpec() {
47
41
 
48
42
  return {
49
43
  passed: true,
50
- message: 'Working spec is valid',
44
+ message: `Spec is valid (${specPath})`,
51
45
  };
52
46
  } catch (error) {
47
+ if (error.message === 'Spec ID required when multiple specs exist' && !options.specId) {
48
+ return {
49
+ passed: false,
50
+ severity: 'high',
51
+ message: 'Multiple specs detected, but no --spec-id was provided',
52
+ fix: 'Run: caws diagnose --spec-id <id>',
53
+ autoFixable: false,
54
+ };
55
+ }
56
+ if (error.message.includes('No CAWS spec found')) {
57
+ return {
58
+ passed: false,
59
+ severity: 'high',
60
+ message: 'No CAWS spec found',
61
+ fix: 'Initialize CAWS: caws init . or create a feature spec with caws specs create <id>',
62
+ autoFixable: false,
63
+ };
64
+ }
53
65
  return {
54
66
  passed: false,
55
67
  severity: 'high',
56
- message: `Working spec has errors: ${error.message}`,
68
+ message: `Spec has errors: ${error.message}`,
57
69
  fix: 'Run: caws validate for details',
58
70
  autoFixable: false,
59
71
  };
@@ -285,9 +297,9 @@ async function checkCAWSTools() {
285
297
  * Run all health checks
286
298
  * @returns {Promise<Object>} Diagnosis results
287
299
  */
288
- async function runDiagnosis() {
300
+ async function runDiagnosis(options = {}) {
289
301
  const checks = [
290
- { name: 'Working spec validity', fn: checkWorkingSpec },
302
+ { name: 'Working spec validity', fn: () => checkWorkingSpec(options) },
291
303
  { name: 'Git repository', fn: checkGitSetup },
292
304
  { name: 'Git hooks', fn: checkGitHooks },
293
305
  { name: 'TypeScript configuration', fn: checkTypeScriptConfig },
@@ -447,7 +459,7 @@ async function applyAutoFixes(results) {
447
459
  async function diagnoseCommand(options = {}) {
448
460
  try {
449
461
  // Run all health checks
450
- const results = await runDiagnosis();
462
+ const results = await runDiagnosis(options);
451
463
 
452
464
  // Display results
453
465
  displayResults(results);
@@ -7,11 +7,12 @@
7
7
  * @author @darianrosebrook
8
8
  */
9
9
 
10
- const fs = require('fs');
11
10
  const path = require('path');
12
- const yaml = require('js-yaml');
13
11
  const chalk = require('chalk');
14
12
  const { initializeGlobalSetup } = require('../config');
13
+ const { resolveSpec } = require('../utils/spec-resolver');
14
+ const { recordEvaluation } = require('../utils/working-state');
15
+ const { appendEvent } = require('../utils/event-log');
15
16
 
16
17
  /**
17
18
  * Evaluate command handler
@@ -19,30 +20,29 @@ const { initializeGlobalSetup } = require('../config');
19
20
  * @param {string} specFile - Path to working spec file
20
21
  * @param {object} options - Command options
21
22
  */
22
- async function evaluateCommand(specFile = '.caws/working-spec.yaml', options = {}) {
23
+ async function evaluateCommand(specFile, options = {}) {
23
24
  try {
24
25
  console.log('Detecting CAWS setup...');
25
26
  const setup = initializeGlobalSetup();
26
27
 
27
28
  if (setup.hasWorkingSpec) {
28
- console.log(`Detected ${setup.setupType} CAWS setup`);
29
+ console.log(`Detected ${setup.type} CAWS setup`);
29
30
  console.log(` Capabilities: ${setup.capabilities.join(', ')}`);
30
31
  }
31
32
 
32
- // Load working spec
33
- const specPath = path.isAbsolute(specFile) ? specFile : path.join(process.cwd(), specFile);
34
-
35
- if (!fs.existsSync(specPath)) {
36
- console.error(chalk.red(`\nWorking spec not found: ${specFile}`));
37
- console.error(chalk.yellow('Run: caws init to create a working spec'));
38
- process.exit(1);
39
- }
40
-
41
- const specContent = fs.readFileSync(specPath, 'utf8');
42
- const spec = yaml.load(specContent);
33
+ const resolved = await resolveSpec({
34
+ specId: options.specId,
35
+ specFile: specFile || undefined,
36
+ warnLegacy: false,
37
+ interactive: false,
38
+ });
39
+ const specPath = resolved.path;
40
+ const spec = resolved.spec;
43
41
 
44
42
  console.log(chalk.blue('\nEvaluating CAWS Quality Standards\n'));
45
43
  console.log('-'.repeat(60));
44
+ console.log(chalk.gray(`Spec: ${path.relative(process.cwd(), specPath)}`));
45
+ console.log(chalk.gray(`Type: ${resolved.type}`));
46
46
 
47
47
  // Evaluation results
48
48
  const results = {
@@ -203,6 +203,32 @@ async function evaluateCommand(specFile = '.caws/working-spec.yaml', options = {
203
203
  ? 'D'
204
204
  : 'F';
205
205
 
206
+ // Record to working state (Phase 1 dual-write: state layer + event log)
207
+ const checksPassed = results.checks.filter(c => c.status === 'pass').length;
208
+ const evaluationPayload = {
209
+ score: results.score,
210
+ max_score: results.maxScore,
211
+ percentage,
212
+ grade,
213
+ checks_passed: checksPassed,
214
+ checks_total: results.checks.length,
215
+ };
216
+ // CAWSFIX-02: guard recordEvaluation with `spec && spec.id` check to
217
+ // prevent the .caws/state/undefined.json bug class. Matches the pattern
218
+ // gates.js already uses and the appendEvent call below.
219
+ if (spec && spec.id) {
220
+ try {
221
+ recordEvaluation(spec.id, evaluationPayload);
222
+ } catch { /* non-fatal */ }
223
+ }
224
+
225
+ // EVLOG-001: emit evaluation_completed event alongside state write.
226
+ if (spec && spec.id) {
227
+ await appendEvent(
228
+ { actor: 'cli', event: 'evaluation_completed', spec_id: spec.id, data: evaluationPayload }
229
+ );
230
+ }
231
+
206
232
  console.log('\n' + '-'.repeat(60));
207
233
  console.log(
208
234
  chalk.bold(
@@ -0,0 +1,149 @@
1
+ /**
2
+ * @fileoverview Unified quality gates CLI command
3
+ * Delegates to the v2 gate pipeline (src/gates/pipeline.js) for evaluation
4
+ * and formatting (src/gates/format.js) for output.
5
+ * @author @darianrosebrook
6
+ */
7
+
8
+ const { evaluateGates } = require('../gates/pipeline');
9
+ const { formatText, formatJson, formatEnrichedText } = require('../gates/format');
10
+ const { enrichGateResults } = require('../gates/feedback');
11
+ const { resolveSpec } = require('../utils/spec-resolver');
12
+ const { commandWrapper } = require('../utils/command-wrapper');
13
+ // EVLOG-002 Phase 2 read flip: gates still writes via recordGates (state layer)
14
+ // AND emits a gates_evaluated event (from Phase 1), but the feedback-enrichment
15
+ // READ at line ~116 now goes through the event log renderer instead of loadState.
16
+ // The write path is deliberately unchanged in Phase 2 — only reads flip.
17
+ const { recordGates } = require('../utils/working-state');
18
+ const { loadStateFromEvents } = require('../utils/event-renderer');
19
+ const { appendEvent } = require('../utils/event-log');
20
+
21
+ /**
22
+ * Run quality gates via the v2 pipeline
23
+ * @param {Object} options - Command options
24
+ * @param {string} [options.context] - Execution context (cli, commit, edit)
25
+ * @param {string} [options.specId] - Target spec ID
26
+ * @param {string} [options.specFile] - Explicit spec file path
27
+ * @param {string} [options.file] - Single file to check (for edit context)
28
+ * @param {boolean} [options.json] - Output as JSON
29
+ * @param {string} [options.format] - Output format (text, json)
30
+ * @param {boolean} [options.quiet] - Minimal output
31
+ */
32
+ async function gatesCommand(options = {}) {
33
+ return commandWrapper(
34
+ async () => {
35
+ const projectRoot = process.cwd();
36
+ const context = options.context || 'cli';
37
+
38
+ // Resolve spec (working-spec or feature spec)
39
+ let spec = null;
40
+ try {
41
+ const resolved = await resolveSpec({
42
+ specId: options.specId,
43
+ specFile: options.specFile,
44
+ warnLegacy: false,
45
+ interactive: false,
46
+ });
47
+ spec = resolved.spec;
48
+ } catch {
49
+ // No spec available — gates that need it will handle gracefully
50
+ }
51
+
52
+ // Get file list based on context
53
+ let stagedFiles = [];
54
+ const { execSync } = require('child_process');
55
+ if (context === 'commit') {
56
+ try {
57
+ stagedFiles = execSync('git diff --cached --name-only', {
58
+ cwd: projectRoot,
59
+ encoding: 'utf8',
60
+ stdio: ['ignore', 'pipe', 'pipe'],
61
+ })
62
+ .trim()
63
+ .split('\n')
64
+ .filter(Boolean);
65
+ } catch {
66
+ /* no staged files */
67
+ }
68
+ } else if (options.file) {
69
+ stagedFiles = [options.file];
70
+ } else if (context === 'cli') {
71
+ // For CLI context, use all tracked files so gates can provide meaningful analysis
72
+ try {
73
+ stagedFiles = execSync('git ls-files', {
74
+ cwd: projectRoot,
75
+ encoding: 'utf8',
76
+ maxBuffer: 10 * 1024 * 1024,
77
+ stdio: ['ignore', 'pipe', 'pipe'],
78
+ })
79
+ .trim()
80
+ .split('\n')
81
+ .filter(Boolean);
82
+ } catch {
83
+ /* not a git repo or git unavailable */
84
+ }
85
+ }
86
+
87
+ const report = await evaluateGates({ projectRoot, stagedFiles, spec, context });
88
+
89
+ // Record to working state (Phase 1 dual-write: state layer + event log)
90
+ if (spec && spec.id) {
91
+ try { recordGates(spec.id, report, context, projectRoot); } catch { /* non-fatal */ }
92
+
93
+ // EVLOG-001: emit gates_evaluated event alongside state write.
94
+ // Payload shape mirrors the `gates` field produced by recordGates.
95
+ await appendEvent(
96
+ {
97
+ actor: 'cli',
98
+ event: 'gates_evaluated',
99
+ spec_id: spec.id,
100
+ data: {
101
+ context,
102
+ passed: report.passed,
103
+ summary: report.summary || {},
104
+ gates: (report.gates || []).map((g) => ({
105
+ name: g.name,
106
+ status: g.status,
107
+ mode: g.mode,
108
+ })),
109
+ },
110
+ },
111
+ { projectRoot }
112
+ );
113
+ }
114
+
115
+ if (options.json || options.format === 'json') {
116
+ console.log(formatJson(report));
117
+ } else if (!options.quiet) {
118
+ // Enrich feedback on failure or --verbose (EVLOG-002: from event log)
119
+ if (!report.passed || options.verbose) {
120
+ try {
121
+ const state = spec?.id ? loadStateFromEvents(spec.id, { projectRoot }) : null;
122
+ const enrichments = enrichGateResults(report, { spec, state, projectRoot });
123
+ if (enrichments.size > 0) {
124
+ console.log(formatEnrichedText(report, enrichments));
125
+ } else {
126
+ console.log(formatText(report));
127
+ }
128
+ } catch {
129
+ console.log(formatText(report));
130
+ }
131
+ } else {
132
+ console.log(formatText(report));
133
+ }
134
+ }
135
+
136
+ // Exit with appropriate code
137
+ if (!report.passed) {
138
+ process.exit(1);
139
+ }
140
+ },
141
+ {
142
+ commandName: 'gates',
143
+ context: { options },
144
+ exitOnError: true,
145
+ }
146
+ );
147
+ }
148
+
149
+ module.exports = { gatesCommand };