@paths.design/caws-cli 9.3.2 → 10.0.1

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 (273) hide show
  1. package/README.md +58 -27
  2. package/dist/commands/archive.js +67 -28
  3. package/dist/commands/burnup.js +20 -11
  4. package/dist/commands/diagnose.js +34 -22
  5. package/dist/commands/evaluate.js +27 -15
  6. package/dist/commands/gates.js +122 -0
  7. package/dist/commands/init.js +143 -15
  8. package/dist/commands/iterate.js +77 -4
  9. package/dist/commands/parallel.js +4 -0
  10. package/dist/commands/plan.js +9 -19
  11. package/dist/commands/provenance.js +53 -17
  12. package/dist/commands/quality-monitor.js +64 -45
  13. package/dist/commands/sidecar.js +71 -0
  14. package/dist/commands/specs.js +233 -44
  15. package/dist/commands/status.js +113 -9
  16. package/dist/commands/tutorial.js +10 -9
  17. package/dist/commands/validate.js +49 -6
  18. package/dist/commands/verify-acs.js +35 -78
  19. package/dist/commands/waivers.js +69 -12
  20. package/dist/commands/worktree.js +50 -25
  21. package/dist/error-handler.js +2 -13
  22. package/dist/gates/budget-limit.js +116 -0
  23. package/dist/gates/feedback.js +260 -0
  24. package/dist/gates/format.js +179 -0
  25. package/dist/gates/god-object.js +117 -0
  26. package/dist/gates/pipeline.js +167 -0
  27. package/dist/gates/scope-boundary.js +93 -0
  28. package/dist/gates/spec-completeness.js +102 -0
  29. package/dist/gates/todo-detection.js +205 -0
  30. package/dist/index.js +130 -151
  31. package/dist/parallel/parallel-manager.js +3 -3
  32. package/dist/policy/PolicyManager.js +42 -10
  33. package/dist/scaffold/claude-hooks.js +24 -1
  34. package/dist/scaffold/git-hooks.js +45 -102
  35. package/dist/scaffold/index.js +4 -3
  36. package/dist/session/session-manager.js +71 -14
  37. package/dist/sidecars/index.js +33 -0
  38. package/dist/sidecars/listeners.js +40 -0
  39. package/dist/sidecars/provenance-summary.js +238 -0
  40. package/dist/sidecars/quality-gaps.js +258 -0
  41. package/dist/sidecars/schema.js +149 -0
  42. package/dist/sidecars/spec-drift.js +151 -0
  43. package/dist/sidecars/waiver-draft.js +176 -0
  44. package/dist/templates/.caws/schemas/policy.schema.json +50 -0
  45. package/dist/templates/.caws/schemas/waivers.schema.json +30 -24
  46. package/dist/templates/.caws/schemas/working-spec.schema.json +51 -8
  47. package/dist/templates/.caws/schemas/worktrees.schema.json +3 -1
  48. package/dist/templates/.caws/templates/working-spec.template.yml +7 -3
  49. package/dist/templates/.claude/hooks/audit.sh +0 -0
  50. package/dist/templates/.claude/hooks/block-dangerous.sh +52 -11
  51. package/dist/templates/.claude/hooks/classify_command.py +592 -0
  52. package/dist/templates/.claude/hooks/doc-frontmatter-check.sh +173 -0
  53. package/dist/templates/.claude/hooks/quality-check.sh +23 -10
  54. package/dist/templates/.claude/hooks/scope-guard.sh +34 -32
  55. package/dist/templates/.claude/hooks/session-caws-status.sh +2 -2
  56. package/dist/templates/.claude/hooks/session-log.sh +76 -3
  57. package/dist/templates/.claude/hooks/stop-worktree-check.sh +1 -1
  58. package/dist/templates/.claude/hooks/test_classify_command.py +370 -0
  59. package/dist/templates/.claude/hooks/test_wrapper_smoke.sh +96 -0
  60. package/dist/templates/.claude/hooks/worktree-guard.sh +2 -2
  61. package/dist/templates/.claude/hooks/worktree-write-guard.sh +1 -1
  62. package/dist/templates/.claude/settings.json +26 -0
  63. package/dist/templates/.cursor/hooks/caws-quality-check.sh +4 -4
  64. package/dist/templates/.cursor/hooks/caws-scope-guard.sh +1 -1
  65. package/dist/templates/.cursor/hooks/session-log.sh +924 -0
  66. package/dist/templates/.cursor/hooks.json +25 -0
  67. package/dist/templates/.cursor/rules/02-quality-gates.mdc +3 -5
  68. package/dist/templates/.cursor/rules/10-documentation-quality-standards.mdc +6 -11
  69. package/dist/templates/.cursor/rules/11-scope-management-waivers.mdc +14 -18
  70. package/dist/templates/.cursor/rules/12-implementation-completeness.mdc +4 -4
  71. package/dist/templates/.cursor/rules/13-language-agnostic-standards.mdc +3 -13
  72. package/dist/templates/.github/copilot-instructions.md +5 -5
  73. package/dist/templates/.idea/runConfigurations/CAWS_Evaluate.xml +1 -1
  74. package/dist/templates/.junie/guidelines.md +2 -2
  75. package/dist/templates/.vscode/settings.json +3 -1
  76. package/dist/templates/.windsurf/rules/caws-quality-standards.md +2 -2
  77. package/dist/templates/.windsurf/workflows/caws-guided-development.md +3 -3
  78. package/dist/templates/CLAUDE.md +43 -8
  79. package/dist/templates/agents.md +29 -9
  80. package/dist/templates/docs/README.md +8 -7
  81. package/dist/templates/scripts/new_feature.sh +80 -0
  82. package/dist/test-analysis.js +43 -30
  83. package/dist/tool-loader.js +1 -1
  84. package/dist/utils/agent-session.js +202 -0
  85. package/dist/utils/detection.js +8 -2
  86. package/dist/utils/finalization.js +7 -6
  87. package/dist/utils/gitignore-updater.js +3 -0
  88. package/dist/utils/lifecycle-events.js +94 -0
  89. package/dist/utils/quality-gates-utils.js +29 -44
  90. package/dist/utils/schema-validator.js +42 -0
  91. package/dist/utils/spec-resolver.js +93 -21
  92. package/dist/utils/working-state.js +505 -0
  93. package/dist/validation/spec-validation.js +92 -22
  94. package/dist/waivers-manager.js +60 -6
  95. package/dist/worktree/worktree-manager.js +390 -93
  96. package/package.json +6 -6
  97. package/templates/.caws/schemas/policy.schema.json +50 -0
  98. package/templates/.caws/schemas/waivers.schema.json +30 -24
  99. package/templates/.caws/schemas/working-spec.schema.json +51 -8
  100. package/templates/.caws/schemas/worktrees.schema.json +3 -1
  101. package/templates/.caws/templates/working-spec.template.yml +7 -3
  102. package/templates/.claude/hooks/block-dangerous.sh +52 -11
  103. package/templates/.claude/hooks/classify_command.py +592 -0
  104. package/templates/.claude/hooks/doc-frontmatter-check.sh +173 -0
  105. package/templates/.claude/hooks/quality-check.sh +23 -10
  106. package/templates/.claude/hooks/scope-guard.sh +34 -32
  107. package/templates/.claude/hooks/session-caws-status.sh +2 -2
  108. package/templates/.claude/hooks/session-log.sh +76 -3
  109. package/templates/.claude/hooks/stop-worktree-check.sh +1 -1
  110. package/templates/.claude/hooks/test_classify_command.py +370 -0
  111. package/templates/.claude/hooks/test_wrapper_smoke.sh +96 -0
  112. package/templates/.claude/hooks/worktree-guard.sh +2 -2
  113. package/templates/.claude/hooks/worktree-write-guard.sh +1 -1
  114. package/templates/.claude/settings.json +26 -0
  115. package/templates/.cursor/hooks/caws-quality-check.sh +4 -4
  116. package/templates/.cursor/hooks/caws-scope-guard.sh +1 -1
  117. package/templates/.cursor/hooks/session-log.sh +924 -0
  118. package/templates/.cursor/hooks.json +25 -0
  119. package/templates/.cursor/rules/02-quality-gates.mdc +3 -5
  120. package/templates/.cursor/rules/10-documentation-quality-standards.mdc +6 -11
  121. package/templates/.cursor/rules/11-scope-management-waivers.mdc +14 -18
  122. package/templates/.cursor/rules/12-implementation-completeness.mdc +4 -4
  123. package/templates/.cursor/rules/13-language-agnostic-standards.mdc +3 -13
  124. package/templates/.github/copilot-instructions.md +5 -5
  125. package/templates/.idea/runConfigurations/CAWS_Evaluate.xml +1 -1
  126. package/templates/.junie/guidelines.md +2 -2
  127. package/templates/.vscode/settings.json +3 -1
  128. package/templates/.windsurf/rules/caws-quality-standards.md +2 -2
  129. package/templates/.windsurf/workflows/caws-guided-development.md +3 -3
  130. package/templates/CLAUDE.md +43 -8
  131. package/templates/{AGENTS.md → agents.md} +29 -9
  132. package/templates/docs/README.md +8 -7
  133. package/templates/scripts/new_feature.sh +80 -0
  134. package/dist/budget-derivation.d.ts +0 -74
  135. package/dist/budget-derivation.d.ts.map +0 -1
  136. package/dist/cicd-optimizer.d.ts +0 -142
  137. package/dist/cicd-optimizer.d.ts.map +0 -1
  138. package/dist/commands/archive.d.ts +0 -51
  139. package/dist/commands/archive.d.ts.map +0 -1
  140. package/dist/commands/burnup.d.ts +0 -6
  141. package/dist/commands/burnup.d.ts.map +0 -1
  142. package/dist/commands/diagnose.d.ts +0 -52
  143. package/dist/commands/diagnose.d.ts.map +0 -1
  144. package/dist/commands/evaluate.d.ts +0 -8
  145. package/dist/commands/evaluate.d.ts.map +0 -1
  146. package/dist/commands/init.d.ts +0 -5
  147. package/dist/commands/init.d.ts.map +0 -1
  148. package/dist/commands/iterate.d.ts +0 -8
  149. package/dist/commands/iterate.d.ts.map +0 -1
  150. package/dist/commands/mode.d.ts +0 -25
  151. package/dist/commands/mode.d.ts.map +0 -1
  152. package/dist/commands/parallel.d.ts +0 -7
  153. package/dist/commands/parallel.d.ts.map +0 -1
  154. package/dist/commands/plan.d.ts +0 -49
  155. package/dist/commands/plan.d.ts.map +0 -1
  156. package/dist/commands/provenance.d.ts +0 -32
  157. package/dist/commands/provenance.d.ts.map +0 -1
  158. package/dist/commands/quality-gates.d.ts +0 -6
  159. package/dist/commands/quality-gates.d.ts.map +0 -1
  160. package/dist/commands/quality-gates.js +0 -444
  161. package/dist/commands/quality-monitor.d.ts +0 -17
  162. package/dist/commands/quality-monitor.d.ts.map +0 -1
  163. package/dist/commands/session.d.ts +0 -7
  164. package/dist/commands/session.d.ts.map +0 -1
  165. package/dist/commands/specs.d.ts +0 -77
  166. package/dist/commands/specs.d.ts.map +0 -1
  167. package/dist/commands/status.d.ts +0 -44
  168. package/dist/commands/status.d.ts.map +0 -1
  169. package/dist/commands/templates.d.ts +0 -74
  170. package/dist/commands/templates.d.ts.map +0 -1
  171. package/dist/commands/tool.d.ts +0 -13
  172. package/dist/commands/tool.d.ts.map +0 -1
  173. package/dist/commands/troubleshoot.d.ts +0 -8
  174. package/dist/commands/troubleshoot.d.ts.map +0 -1
  175. package/dist/commands/troubleshoot.js +0 -104
  176. package/dist/commands/tutorial.d.ts +0 -55
  177. package/dist/commands/tutorial.d.ts.map +0 -1
  178. package/dist/commands/validate.d.ts +0 -15
  179. package/dist/commands/validate.d.ts.map +0 -1
  180. package/dist/commands/waivers.d.ts +0 -8
  181. package/dist/commands/waivers.d.ts.map +0 -1
  182. package/dist/commands/workflow.d.ts +0 -85
  183. package/dist/commands/workflow.d.ts.map +0 -1
  184. package/dist/commands/worktree.d.ts +0 -7
  185. package/dist/commands/worktree.d.ts.map +0 -1
  186. package/dist/config/index.d.ts +0 -29
  187. package/dist/config/index.d.ts.map +0 -1
  188. package/dist/config/lite-scope.d.ts +0 -33
  189. package/dist/config/lite-scope.d.ts.map +0 -1
  190. package/dist/config/modes.d.ts +0 -264
  191. package/dist/config/modes.d.ts.map +0 -1
  192. package/dist/constants/spec-types.d.ts +0 -93
  193. package/dist/constants/spec-types.d.ts.map +0 -1
  194. package/dist/error-handler.d.ts +0 -151
  195. package/dist/error-handler.d.ts.map +0 -1
  196. package/dist/generators/jest-config-generator.d.ts +0 -32
  197. package/dist/generators/jest-config-generator.d.ts.map +0 -1
  198. package/dist/generators/jest-config.d.ts +0 -32
  199. package/dist/generators/jest-config.d.ts.map +0 -1
  200. package/dist/generators/jest-config.js +0 -242
  201. package/dist/generators/working-spec.d.ts +0 -13
  202. package/dist/generators/working-spec.d.ts.map +0 -1
  203. package/dist/index-new.d.ts +0 -5
  204. package/dist/index-new.d.ts.map +0 -1
  205. package/dist/index-new.js +0 -317
  206. package/dist/index.d.ts +0 -5
  207. package/dist/index.d.ts.map +0 -1
  208. package/dist/index.js.backup +0 -4711
  209. package/dist/minimal-cli.d.ts +0 -3
  210. package/dist/minimal-cli.d.ts.map +0 -1
  211. package/dist/parallel/parallel-manager.d.ts +0 -67
  212. package/dist/parallel/parallel-manager.d.ts.map +0 -1
  213. package/dist/policy/PolicyManager.d.ts +0 -104
  214. package/dist/policy/PolicyManager.d.ts.map +0 -1
  215. package/dist/scaffold/claude-hooks.d.ts +0 -28
  216. package/dist/scaffold/claude-hooks.d.ts.map +0 -1
  217. package/dist/scaffold/cursor-hooks.d.ts +0 -7
  218. package/dist/scaffold/cursor-hooks.d.ts.map +0 -1
  219. package/dist/scaffold/git-hooks.d.ts +0 -38
  220. package/dist/scaffold/git-hooks.d.ts.map +0 -1
  221. package/dist/scaffold/index.d.ts +0 -17
  222. package/dist/scaffold/index.d.ts.map +0 -1
  223. package/dist/session/session-manager.d.ts +0 -94
  224. package/dist/session/session-manager.d.ts.map +0 -1
  225. package/dist/spec/SpecFileManager.d.ts +0 -146
  226. package/dist/spec/SpecFileManager.d.ts.map +0 -1
  227. package/dist/templates/.cursor/hooks/caws-tool-validation.sh +0 -121
  228. package/dist/templates/.github/copilot/instructions.md +0 -311
  229. package/dist/test-analysis.d.ts +0 -231
  230. package/dist/test-analysis.d.ts.map +0 -1
  231. package/dist/tool-interface.d.ts +0 -236
  232. package/dist/tool-interface.d.ts.map +0 -1
  233. package/dist/tool-loader.d.ts +0 -77
  234. package/dist/tool-loader.d.ts.map +0 -1
  235. package/dist/tool-validator.d.ts +0 -72
  236. package/dist/tool-validator.d.ts.map +0 -1
  237. package/dist/utils/async-utils.d.ts +0 -73
  238. package/dist/utils/async-utils.d.ts.map +0 -1
  239. package/dist/utils/command-wrapper.d.ts +0 -66
  240. package/dist/utils/command-wrapper.d.ts.map +0 -1
  241. package/dist/utils/detection.d.ts +0 -14
  242. package/dist/utils/detection.d.ts.map +0 -1
  243. package/dist/utils/error-categories.d.ts +0 -52
  244. package/dist/utils/error-categories.d.ts.map +0 -1
  245. package/dist/utils/finalization.d.ts +0 -17
  246. package/dist/utils/finalization.d.ts.map +0 -1
  247. package/dist/utils/git-lock.d.ts +0 -13
  248. package/dist/utils/git-lock.d.ts.map +0 -1
  249. package/dist/utils/gitignore-updater.d.ts +0 -39
  250. package/dist/utils/gitignore-updater.d.ts.map +0 -1
  251. package/dist/utils/ide-detection.d.ts +0 -89
  252. package/dist/utils/ide-detection.d.ts.map +0 -1
  253. package/dist/utils/project-analysis.d.ts +0 -34
  254. package/dist/utils/project-analysis.d.ts.map +0 -1
  255. package/dist/utils/promise-utils.d.ts +0 -30
  256. package/dist/utils/promise-utils.d.ts.map +0 -1
  257. package/dist/utils/quality-gates-utils.d.ts +0 -49
  258. package/dist/utils/quality-gates-utils.d.ts.map +0 -1
  259. package/dist/utils/quality-gates.d.ts +0 -49
  260. package/dist/utils/quality-gates.d.ts.map +0 -1
  261. package/dist/utils/quality-gates.js +0 -402
  262. package/dist/utils/spec-resolver.d.ts +0 -80
  263. package/dist/utils/spec-resolver.d.ts.map +0 -1
  264. package/dist/utils/typescript-detector.d.ts +0 -66
  265. package/dist/utils/typescript-detector.d.ts.map +0 -1
  266. package/dist/utils/yaml-validation.d.ts +0 -32
  267. package/dist/utils/yaml-validation.d.ts.map +0 -1
  268. package/dist/validation/spec-validation.d.ts +0 -43
  269. package/dist/validation/spec-validation.d.ts.map +0 -1
  270. package/dist/waivers-manager.d.ts +0 -167
  271. package/dist/waivers-manager.d.ts.map +0 -1
  272. package/dist/worktree/worktree-manager.d.ts +0 -54
  273. package/dist/worktree/worktree-manager.d.ts.map +0 -1
@@ -7,6 +7,7 @@
7
7
  const fs = require('fs-extra');
8
8
  const path = require('path');
9
9
  const yaml = require('js-yaml');
10
+ const { resolveSpec } = require('./utils/spec-resolver');
10
11
 
11
12
  /**
12
13
  * Waiver Pattern Learning Engine
@@ -198,7 +199,7 @@ class WaiverPatternLearner {
198
199
  /**
199
200
  * Analyze budget overrun patterns
200
201
  */
201
- analyzeBudgetOverruns(waivers, specs) {
202
+ analyzeBudgetOverruns(waivers, _specs) {
202
203
  const budgetWaivers = waivers.filter((w) => w.gates?.includes('budget_limit'));
203
204
 
204
205
  if (budgetWaivers.length === 0) {
@@ -266,7 +267,7 @@ class WaiverPatternLearner {
266
267
  /**
267
268
  * Identify risk factors from waiver patterns
268
269
  */
269
- identifyRiskFactors(waivers, specs) {
270
+ identifyRiskFactors(waivers, _specs) {
270
271
  // Simple risk factor identification based on waiver frequency
271
272
  const riskFactors = [];
272
273
 
@@ -595,17 +596,17 @@ class BudgetPredictor {
595
596
  /**
596
597
  * Main Test Analysis CLI handler
597
598
  */
598
- async function testAnalysisCommand(subcommand, options = []) {
599
+ async function testAnalysisCommand(subcommand, options = [], commandOptions = {}) {
599
600
  const chalk = (await import('chalk')).default;
600
601
 
601
602
  try {
602
603
  switch (subcommand) {
603
604
  case 'assess-budget':
604
- return await handleAssessBudget(options);
605
+ return await handleAssessBudget(options, commandOptions);
605
606
  case 'analyze-patterns':
606
607
  return await handleAnalyzePatterns(options);
607
608
  case 'find-similar':
608
- return await handleFindSimilar(options);
609
+ return await handleFindSimilar(options, commandOptions);
609
610
  default:
610
611
  console.log(chalk.red('Unknown test-analysis subcommand'));
611
612
  console.log('Available commands:');
@@ -619,28 +620,49 @@ async function testAnalysisCommand(subcommand, options = []) {
619
620
  }
620
621
  }
621
622
 
623
+ /**
624
+ * Resolve the current spec for analysis commands.
625
+ * Supports explicit `--spec <path>` for compatibility, but prefers
626
+ * the suite-standard resolver and `--spec-id`.
627
+ * @param {string[]} optionArgs
628
+ * @param {Object} commandOptions
629
+ * @returns {Promise<{spec: Object, specPath: string}>}
630
+ */
631
+ async function resolveAnalysisSpec(optionArgs = [], commandOptions = {}) {
632
+ let specFile = null;
633
+ if (Array.isArray(optionArgs) && optionArgs.includes('--spec')) {
634
+ const specIndex = optionArgs.indexOf('--spec');
635
+ if (specIndex + 1 < optionArgs.length) {
636
+ specFile = optionArgs[specIndex + 1];
637
+ }
638
+ }
639
+
640
+ const resolved = await resolveSpec({
641
+ specId: commandOptions.specId,
642
+ specFile,
643
+ warnLegacy: false,
644
+ interactive: false,
645
+ });
646
+
647
+ return {
648
+ spec: resolved.spec,
649
+ specPath: resolved.path,
650
+ };
651
+ }
652
+
622
653
  /**
623
654
  * Handle budget assessment command
624
655
  */
625
- async function handleAssessBudget(options) {
656
+ async function handleAssessBudget(options, commandOptions = {}) {
626
657
  const chalk = (await import('chalk')).default;
627
658
  const predictor = new BudgetPredictor();
628
659
 
629
- // Load current spec
630
- let specPath = '.caws/working-spec.yaml';
631
- if (options.includes('--spec')) {
632
- const specIndex = options.indexOf('--spec');
633
- if (specIndex + 1 < options.length) {
634
- specPath = options[specIndex + 1];
635
- }
636
- }
637
-
638
660
  try {
639
- const specContent = fs.readFileSync(specPath, 'utf8');
640
- const spec = yaml.load(specContent);
661
+ const { spec, specPath } = await resolveAnalysisSpec(options, commandOptions);
641
662
 
642
663
  console.log(chalk.cyan(`Budget Assessment for ${spec.id}`));
643
664
  console.log('==============================================');
665
+ console.log(chalk.gray(`Spec: ${path.relative(process.cwd(), specPath)}`));
644
666
 
645
667
  const result = predictor.assessBudget(spec);
646
668
 
@@ -681,7 +703,7 @@ async function handleAssessBudget(options) {
681
703
  /**
682
704
  * Handle pattern analysis command
683
705
  */
684
- async function handleAnalyzePatterns(options) {
706
+ async function handleAnalyzePatterns(_options) {
685
707
  const chalk = (await import('chalk')).default;
686
708
  const learner = new WaiverPatternLearner();
687
709
 
@@ -727,25 +749,16 @@ async function handleAnalyzePatterns(options) {
727
749
  /**
728
750
  * Handle find similar projects command
729
751
  */
730
- async function handleFindSimilar(options) {
752
+ async function handleFindSimilar(options, commandOptions = {}) {
731
753
  const chalk = (await import('chalk')).default;
732
754
  const matcher = new ProjectSimilarityMatcher();
733
755
 
734
- // Load current spec
735
- let specPath = '.caws/working-spec.yaml';
736
- if (options.includes('--spec')) {
737
- const specIndex = options.indexOf('--spec');
738
- if (specIndex + 1 < options.length) {
739
- specPath = options[specIndex + 1];
740
- }
741
- }
742
-
743
756
  try {
744
- const specContent = fs.readFileSync(specPath, 'utf8');
745
- const spec = yaml.load(specContent);
757
+ const { spec, specPath } = await resolveAnalysisSpec(options, commandOptions);
746
758
 
747
759
  console.log(chalk.cyan(`Finding projects similar to ${spec.id}`));
748
760
  console.log('==============================================');
761
+ console.log(chalk.gray(`Spec: ${path.relative(process.cwd(), specPath)}`));
749
762
 
750
763
  const similar = matcher.findSimilarProjects(spec);
751
764
 
@@ -128,7 +128,7 @@ class ToolLoader extends EventEmitter {
128
128
  this.emit('tool:loaded', { id: toolId, metadata: tool.metadata });
129
129
 
130
130
  return tool;
131
- }, `Tool loading failed: ${toolId}`);
131
+ }, 'Tool loading failed');
132
132
  }
133
133
 
134
134
  /**
@@ -0,0 +1,202 @@
1
+ /**
2
+ * Agent Session Identity
3
+ *
4
+ * Provides a unified way to identify the current agent session across
5
+ * Claude Code, Cursor, and other IDE agent environments.
6
+ *
7
+ * Sources checked (first match wins):
8
+ * 1. CLAUDE_SESSION_ID — set by Claude Code automatically
9
+ * 2. .caws/agents.json — written by Cursor session-log hook (conversation_id)
10
+ * 3. CURSOR_TRACE_ID — set by Cursor (per-request, not stable, last resort)
11
+ *
12
+ * The agent registry (.caws/agents.json) also tracks active agents for
13
+ * multi-agent coordination. Entries expire after a configurable TTL.
14
+ *
15
+ * @author @darianrosebrook
16
+ */
17
+
18
+ const fs = require('fs');
19
+ const path = require('path');
20
+
21
+ const AGENTS_REGISTRY = '.caws/agents.json';
22
+ const DEFAULT_TTL_MS = 30 * 60 * 1000; // 30 minutes
23
+
24
+ /**
25
+ * Get the current agent's session ID from the best available source.
26
+ * @param {string} [projectRoot] - Project root (for reading agent registry)
27
+ * @returns {string|null} Session ID or null if not in an agent context
28
+ */
29
+ function getAgentSessionId(projectRoot) {
30
+ // 1. Claude Code — most reliable, set automatically
31
+ if (process.env.CLAUDE_SESSION_ID) {
32
+ return process.env.CLAUDE_SESSION_ID;
33
+ }
34
+
35
+ // 2. Agent registry — written by Cursor session-log hook
36
+ if (projectRoot) {
37
+ const registry = loadAgentRegistry(projectRoot);
38
+ const active = findActiveAgent(registry);
39
+ if (active) {
40
+ return active.sessionId;
41
+ }
42
+ }
43
+
44
+ // 3. Cursor trace ID — per-request, not stable, but better than nothing
45
+ if (process.env.CURSOR_TRACE_ID) {
46
+ return `cursor:${process.env.CURSOR_TRACE_ID}`;
47
+ }
48
+
49
+ return null;
50
+ }
51
+
52
+ /**
53
+ * Get the agent platform name.
54
+ * @returns {string} 'claude-code' | 'cursor' | 'unknown'
55
+ */
56
+ function getAgentPlatform() {
57
+ if (process.env.CLAUDE_SESSION_ID) return 'claude-code';
58
+ if (process.env.CURSOR_TRACE_ID) return 'cursor';
59
+ return 'unknown';
60
+ }
61
+
62
+ /**
63
+ * Load the agent registry, pruning stale entries.
64
+ * @param {string} root - Project root
65
+ * @returns {object} Registry with { agents: { [sessionId]: entry } }
66
+ */
67
+ function loadAgentRegistry(root) {
68
+ const registryPath = path.join(root, AGENTS_REGISTRY);
69
+ let registry = { version: 1, agents: {} };
70
+
71
+ if (fs.existsSync(registryPath)) {
72
+ try {
73
+ registry = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
74
+ } catch {
75
+ // Corrupt file — start fresh
76
+ registry = { version: 1, agents: {} };
77
+ }
78
+ }
79
+
80
+ // Prune stale entries on every read
81
+ const now = Date.now();
82
+ let pruned = false;
83
+ for (const [id, entry] of Object.entries(registry.agents || {})) {
84
+ const ttl = entry.ttl || DEFAULT_TTL_MS;
85
+ const lastSeenTime = entry.lastSeen ? new Date(entry.lastSeen).getTime() : 0;
86
+ const lastSeen = isNaN(lastSeenTime) ? 0 : lastSeenTime;
87
+ if (now - lastSeen > ttl) {
88
+ delete registry.agents[id];
89
+ pruned = true;
90
+ }
91
+ }
92
+
93
+ if (pruned) {
94
+ saveAgentRegistry(root, registry);
95
+ }
96
+
97
+ return registry;
98
+ }
99
+
100
+ /**
101
+ * Save the agent registry atomically (write-then-rename).
102
+ * @param {string} root - Project root
103
+ * @param {object} registry - Registry object
104
+ */
105
+ function saveAgentRegistry(root, registry) {
106
+ const registryPath = path.join(root, AGENTS_REGISTRY);
107
+ const dir = path.dirname(registryPath);
108
+ if (!fs.existsSync(dir)) {
109
+ fs.mkdirSync(dir, { recursive: true });
110
+ }
111
+ const tmpPath = registryPath + '.tmp.' + process.pid;
112
+ fs.writeFileSync(tmpPath, JSON.stringify(registry, null, 2));
113
+ fs.renameSync(tmpPath, registryPath);
114
+ }
115
+
116
+ /**
117
+ * Register or heartbeat an agent session.
118
+ * Called by session-log hooks to keep entries fresh.
119
+ * @param {string} root - Project root
120
+ * @param {object} agent - Agent info
121
+ * @param {string} agent.sessionId - Unique session/conversation ID
122
+ * @param {string} agent.platform - 'claude-code' | 'cursor' | 'unknown'
123
+ * @param {string} [agent.model] - Model name if known
124
+ * @param {string} [agent.specId] - Active spec ID if known
125
+ * @param {number} [agent.ttl] - Custom TTL in ms (default 30 min)
126
+ */
127
+ function heartbeatAgent(root, agent) {
128
+ const registry = loadAgentRegistry(root);
129
+ const existing = registry.agents[agent.sessionId] || {};
130
+
131
+ registry.agents[agent.sessionId] = {
132
+ ...existing,
133
+ sessionId: agent.sessionId,
134
+ platform: agent.platform || existing.platform || 'unknown',
135
+ model: agent.model || existing.model || null,
136
+ specId: agent.specId || existing.specId || null,
137
+ ttl: agent.ttl || existing.ttl || DEFAULT_TTL_MS,
138
+ firstSeen: existing.firstSeen || new Date().toISOString(),
139
+ lastSeen: new Date().toISOString(),
140
+ };
141
+
142
+ saveAgentRegistry(root, registry);
143
+ }
144
+
145
+ /**
146
+ * Remove an agent session from the registry.
147
+ * Called on session stop.
148
+ * @param {string} root - Project root
149
+ * @param {string} sessionId - Session to remove
150
+ */
151
+ function removeAgent(root, sessionId) {
152
+ const registry = loadAgentRegistry(root);
153
+ delete registry.agents[sessionId];
154
+ saveAgentRegistry(root, registry);
155
+ }
156
+
157
+ /**
158
+ * Find the most recently active agent for this terminal/process.
159
+ * Prefers agents that match the current environment.
160
+ * @param {object} registry - Loaded registry
161
+ * @returns {object|null} Agent entry or null
162
+ */
163
+ function findActiveAgent(registry) {
164
+ const agents = Object.values(registry.agents || {});
165
+ if (agents.length === 0) return null;
166
+
167
+ // If we're in Cursor, prefer cursor agents
168
+ const isCursor = !!process.env.CURSOR_TRACE_ID;
169
+ const preferred = agents.filter(a => {
170
+ if (isCursor) return a.platform === 'cursor';
171
+ return a.platform === 'claude-code';
172
+ });
173
+
174
+ const pool = preferred.length > 0 ? preferred : agents;
175
+
176
+ // Return most recently seen
177
+ pool.sort((a, b) => new Date(b.lastSeen) - new Date(a.lastSeen));
178
+ return pool[0];
179
+ }
180
+
181
+ /**
182
+ * List all currently active (non-expired) agents.
183
+ * @param {string} root - Project root
184
+ * @returns {object[]} Array of agent entries
185
+ */
186
+ function listActiveAgents(root) {
187
+ const registry = loadAgentRegistry(root);
188
+ return Object.values(registry.agents || {});
189
+ }
190
+
191
+ module.exports = {
192
+ getAgentSessionId,
193
+ getAgentPlatform,
194
+ loadAgentRegistry,
195
+ saveAgentRegistry,
196
+ heartbeatAgent,
197
+ removeAgent,
198
+ findActiveAgent,
199
+ listActiveAgents,
200
+ AGENTS_REGISTRY,
201
+ DEFAULT_TTL_MS,
202
+ };
@@ -33,11 +33,17 @@ function findPackageRoot(startDir = __dirname) {
33
33
  * @returns {Object} Setup information
34
34
  */
35
35
  function detectCAWSSetup(cwd = process.cwd()) {
36
- // Skip logging for version/help commands
36
+ // Skip logging for version/help/quiet commands, or when CAWS_QUIET is set.
37
+ // Verbose detection output contributes to Claude Code context-window
38
+ // exhaustion when agents run many caws commands in a single session.
37
39
  const isQuietCommand =
38
40
  process.argv.includes('--version') ||
39
41
  process.argv.includes('-V') ||
40
- process.argv.includes('--help');
42
+ process.argv.includes('--help') ||
43
+ process.argv.includes('--json') ||
44
+ process.argv.includes('--quiet') ||
45
+ process.argv.includes('-q') ||
46
+ process.env.CAWS_QUIET === '1';
41
47
 
42
48
  if (!isQuietCommand) {
43
49
  console.log(chalk.blue('Detecting CAWS setup...'));
@@ -95,7 +95,7 @@ async function finalizeProject(projectName, options, answers) {
95
95
  ],
96
96
  prompts: Object.keys(answers),
97
97
  commit: null, // Will be set after git init
98
- artifacts: ['.caws/working-spec.yaml'],
98
+ artifacts: ['.caws/specs/', '.caws/working-spec.yaml'],
99
99
  results: {
100
100
  project_id: answers.projectId,
101
101
  project_title: answers.projectTitle,
@@ -212,13 +212,14 @@ function continueToSuccess() {
212
212
  }
213
213
 
214
214
  console.log(chalk.bold('\nNext steps:'));
215
- console.log('1. Customize .caws/working-spec.yaml');
216
- console.log('2. Review added CAWS tools and documentation');
215
+ console.log('1. Customize .caws/specs/<spec-id>.yaml');
216
+ console.log('2. Treat .caws/working-spec.yaml as a compatibility mirror');
217
+ console.log('3. Review added CAWS tools and documentation');
217
218
  if (!isCurrentDir) {
218
- console.log('3. Move CAWS files to your main project if needed');
219
+ console.log('4. Move CAWS files to your main project if needed');
219
220
  }
220
- console.log('4. npm install (if using Node.js)');
221
- console.log('5. Set up your CI/CD pipeline');
221
+ console.log('5. npm install (if using Node.js)');
222
+ console.log('6. Set up your CI/CD pipeline');
222
223
  console.log(chalk.blue('\nFor help: caws --help'));
223
224
  }
224
225
 
@@ -58,6 +58,9 @@ caws-debug.log*
58
58
  caws.local.*
59
59
  .caws/local.*
60
60
 
61
+ # CAWS working state (runtime, not committed)
62
+ .caws/state/
63
+
61
64
  # CAWS Worktrees (local, should not be tracked)
62
65
  .caws/worktrees/
63
66
  .caws/worktrees.json
@@ -0,0 +1,94 @@
1
+ /**
2
+ * @fileoverview Governance Lifecycle Events
3
+ *
4
+ * A lightweight event system for governance-significant moments in CAWS.
5
+ * Events fire synchronously within command execution and are consumed by
6
+ * the working-state layer, feedback enrichment, and future sidecar flows.
7
+ *
8
+ * This is NOT the same as IDE hooks (shell scripts in .claude/hooks/).
9
+ * These are internal CAWS events emitted by CLI commands and gates.
10
+ *
11
+ * @author @darianrosebrook
12
+ */
13
+
14
+ const { EventEmitter } = require('events');
15
+
16
+ /**
17
+ * Singleton lifecycle event emitter.
18
+ * @type {EventEmitter}
19
+ */
20
+ const lifecycle = new EventEmitter();
21
+ lifecycle.setMaxListeners(20);
22
+
23
+ /**
24
+ * Event name constants.
25
+ *
26
+ * @typedef {Object} GatesBlockedPayload
27
+ * @property {string|null} specId
28
+ * @property {string} gateName
29
+ * @property {string} mode
30
+ * @property {string[]} messages
31
+ * @property {string} context - cli | commit | edit
32
+ * @property {string} timestamp - ISO 8601
33
+ *
34
+ * @typedef {Object} GatesPassedPayload
35
+ * @property {string|null} specId
36
+ * @property {Object} summary - { blocked, warned, passed, skipped, waived }
37
+ * @property {string} context
38
+ * @property {string} timestamp
39
+ *
40
+ * @typedef {Object} ValidationFailedPayload
41
+ * @property {string} specId
42
+ * @property {Object[]} errors
43
+ * @property {number} errorCount
44
+ * @property {number} warningCount
45
+ * @property {string} timestamp
46
+ *
47
+ * @typedef {Object} ValidationPassedPayload
48
+ * @property {string} specId
49
+ * @property {string} grade
50
+ * @property {number} complianceScore
51
+ * @property {string} timestamp
52
+ *
53
+ * @typedef {Object} BudgetPressurePayload
54
+ * @property {string|null} specId
55
+ * @property {number} filesUsed
56
+ * @property {number} filesLimit
57
+ * @property {number} locUsed
58
+ * @property {number} locLimit
59
+ * @property {number} percentUsed
60
+ * @property {string} timestamp
61
+ *
62
+ * @typedef {Object} PhaseTransitionPayload
63
+ * @property {string} specId
64
+ * @property {string} oldPhase
65
+ * @property {string} newPhase
66
+ * @property {string} timestamp
67
+ *
68
+ * @typedef {Object} MergePrePayload
69
+ * @property {string} worktreeName
70
+ * @property {string} branch
71
+ * @property {string} baseBranch
72
+ * @property {string[]} conflicts
73
+ * @property {string} timestamp
74
+ *
75
+ * @typedef {Object} MergePostPayload
76
+ * @property {string} worktreeName
77
+ * @property {string} branch
78
+ * @property {string} baseBranch
79
+ * @property {boolean} merged
80
+ * @property {string[]} conflicts
81
+ * @property {string} timestamp
82
+ */
83
+ const EVENTS = {
84
+ GATES_BLOCKED: 'gates:blocked',
85
+ GATES_PASSED: 'gates:passed',
86
+ VALIDATION_FAILED: 'validation:failed',
87
+ VALIDATION_PASSED: 'validation:passed',
88
+ BUDGET_PRESSURE: 'budget:pressure',
89
+ PHASE_TRANSITION: 'phase:transition',
90
+ MERGE_PRE: 'merge:pre',
91
+ MERGE_POST: 'merge:post',
92
+ };
93
+
94
+ module.exports = { lifecycle, EVENTS };
@@ -29,43 +29,6 @@ const CONFIG = {
29
29
  },
30
30
  };
31
31
 
32
- /**
33
- * Check if a waiver applies to the given gate
34
- * @param {string} gate - Gate name to check
35
- * @returns {Object} Waiver check result
36
- */
37
- function checkWaiver(gate) {
38
- try {
39
- const waiversPath = path.join(process.cwd(), '.caws/waivers.yml');
40
- if (!fs.existsSync(waiversPath)) {
41
- return { waived: false, reason: 'No waivers file found' };
42
- }
43
-
44
- const waiversConfig = yaml.load(fs.readFileSync(waiversPath, 'utf8'));
45
- const now = new Date();
46
-
47
- // Find active waivers for this gate
48
- const activeWaivers =
49
- waiversConfig.waivers?.filter((waiver) => {
50
- const expiresAt = new Date(waiver.expires_at);
51
- return waiver.gates.includes(gate) && expiresAt > now && waiver.status === 'active';
52
- }) || [];
53
-
54
- if (activeWaivers.length > 0) {
55
- const waiver = activeWaivers[0];
56
- return {
57
- waived: true,
58
- waiver,
59
- reason: `Active waiver: ${waiver.title} (expires: ${waiver.expires_at})`,
60
- };
61
- }
62
-
63
- return { waived: false, reason: 'No active waivers found' };
64
- } catch (error) {
65
- return { waived: false, reason: `Waiver check failed: ${error.message}` };
66
- }
67
- }
68
-
69
32
  /**
70
33
  * Detect if project is in crisis response mode
71
34
  * @returns {boolean} True if in crisis mode
@@ -73,12 +36,32 @@ function checkWaiver(gate) {
73
36
  function detectCrisisMode() {
74
37
  try {
75
38
  const crisisIndicators = [
76
- // Check for crisis response in working spec
39
+ // Check for crisis response in any active spec (feature specs then legacy)
77
40
  () => {
78
- const specPath = path.join(process.cwd(), '.caws/working-spec.yaml');
79
- if (fs.existsSync(specPath)) {
80
- const spec = yaml.load(fs.readFileSync(specPath, 'utf8'));
81
- return spec.mode === 'crisis' || spec.crisis_mode === true;
41
+ // Check feature specs first
42
+ const specsDir = path.join(process.cwd(), '.caws/specs');
43
+ if (fs.existsSync(specsDir)) {
44
+ try {
45
+ const files = fs.readdirSync(specsDir).filter(f => f.endsWith('.yaml') || f.endsWith('.yml'));
46
+ for (const file of files) {
47
+ const spec = yaml.load(fs.readFileSync(path.join(specsDir, file), 'utf8'));
48
+ if (spec && (spec.mode === 'crisis' || spec.crisis_mode === true)) {
49
+ return true;
50
+ }
51
+ }
52
+ } catch {
53
+ // Fall through to legacy check
54
+ }
55
+ }
56
+ // Legacy fallback
57
+ const legacyPath = path.join(process.cwd(), '.caws/working-spec.yaml');
58
+ if (fs.existsSync(legacyPath)) {
59
+ try {
60
+ const spec = yaml.load(fs.readFileSync(legacyPath, 'utf8'));
61
+ return spec.mode === 'crisis' || spec.crisis_mode === true;
62
+ } catch {
63
+ // Ignore
64
+ }
82
65
  }
83
66
  return false;
84
67
  },
@@ -110,7 +93,10 @@ function detectCrisisMode() {
110
93
  */
111
94
  function getStagedFiles() {
112
95
  try {
113
- const stagedFiles = execSync('git diff --cached --name-only', { encoding: 'utf8' })
96
+ const stagedFiles = execSync('git diff --cached --name-only', {
97
+ encoding: 'utf8',
98
+ stdio: ['ignore', 'pipe', 'pipe'],
99
+ })
114
100
  .trim()
115
101
  .split('\n')
116
102
  .filter((file) => file.trim() !== '');
@@ -395,7 +381,6 @@ module.exports = {
395
381
  getStagedFiles,
396
382
  checkGodObjects,
397
383
  checkHiddenTodos,
398
- checkWaiver,
399
384
  detectCrisisMode,
400
385
  runQualityGates,
401
386
  CONFIG,
@@ -0,0 +1,42 @@
1
+ const Ajv = require('ajv');
2
+ const fs = require('fs-extra');
3
+ const path = require('path');
4
+
5
+ const ajv = new Ajv({ allErrors: true, allowUnionTypes: true, strict: false, validateFormats: false });
6
+ const cache = new Map();
7
+
8
+ function createValidator(schemaPath) {
9
+ const resolved = path.resolve(schemaPath);
10
+ if (cache.has(resolved)) return cache.get(resolved);
11
+ let schema;
12
+ try {
13
+ schema = JSON.parse(fs.readFileSync(resolved, 'utf8'));
14
+ } catch (err) {
15
+ throw new Error(`Failed to parse schema file ${resolved}: ${err.message}`);
16
+ }
17
+ // Remove $schema keyword — Ajv handles validation without it and
18
+ // the 2020-12 meta-schema URI is not bundled with the base Ajv package.
19
+ delete schema.$schema;
20
+ const validate = ajv.compile(schema);
21
+ const validator = (data) => {
22
+ const valid = validate(data);
23
+ return {
24
+ valid,
25
+ errors: valid ? [] : validate.errors.map(e => ({
26
+ path: e.instancePath,
27
+ message: e.message,
28
+ params: e.params,
29
+ })),
30
+ };
31
+ };
32
+ cache.set(resolved, validator);
33
+ return validator;
34
+ }
35
+
36
+ function getSchemaPath(schemaName, projectRoot) {
37
+ const projectPath = path.join(projectRoot, '.caws', 'schemas', schemaName);
38
+ if (fs.existsSync(projectPath)) return projectPath;
39
+ return path.join(__dirname, '../../templates/.caws/schemas', schemaName);
40
+ }
41
+
42
+ module.exports = { createValidator, getSchemaPath };