@paths.design/caws-cli 7.0.2 → 8.0.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 (217) hide show
  1. package/dist/budget-derivation.js +5 -4
  2. package/dist/commands/diagnose.js +24 -19
  3. package/dist/commands/init.js +51 -4
  4. package/dist/commands/quality-gates.js +147 -9
  5. package/dist/commands/specs.js +148 -14
  6. package/dist/commands/status.js +2 -2
  7. package/dist/commands/tool.js +2 -4
  8. package/dist/config/index.js +17 -8
  9. package/dist/generators/working-spec.js +19 -6
  10. package/dist/scaffold/git-hooks.js +245 -46
  11. package/dist/scaffold/index.js +53 -7
  12. package/dist/templates/.caws/tools/README.md +21 -0
  13. package/dist/templates/.cursor/README.md +311 -0
  14. package/dist/templates/.cursor/hooks/audit.sh +55 -0
  15. package/dist/templates/.cursor/hooks/block-dangerous.sh +83 -0
  16. package/dist/templates/.cursor/hooks/caws-quality-check.sh +52 -0
  17. package/dist/templates/.cursor/hooks/caws-scope-guard.sh +130 -0
  18. package/dist/templates/.cursor/hooks/caws-tool-validation.sh +121 -0
  19. package/dist/templates/.cursor/hooks/format.sh +38 -0
  20. package/dist/templates/.cursor/hooks/naming-check.sh +64 -0
  21. package/dist/templates/.cursor/hooks/scan-secrets.sh +46 -0
  22. package/dist/templates/.cursor/hooks/scope-guard.sh +52 -0
  23. package/dist/templates/.cursor/hooks/validate-spec.sh +83 -0
  24. package/dist/templates/.cursor/hooks.json +59 -0
  25. package/dist/templates/.cursor/rules/00-claims-verification.mdc +144 -0
  26. package/dist/templates/.cursor/rules/01-working-style.mdc +50 -0
  27. package/dist/templates/.cursor/rules/02-quality-gates.mdc +370 -0
  28. package/dist/templates/.cursor/rules/03-naming-and-refactor.mdc +33 -0
  29. package/dist/templates/.cursor/rules/04-logging-language-style.mdc +23 -0
  30. package/dist/templates/.cursor/rules/05-safe-defaults-guards.mdc +23 -0
  31. package/dist/templates/.cursor/rules/06-typescript-conventions.mdc +36 -0
  32. package/dist/templates/.cursor/rules/07-process-ops.mdc +20 -0
  33. package/dist/templates/.cursor/rules/08-solid-and-architecture.mdc +16 -0
  34. package/dist/templates/.cursor/rules/09-docstrings.mdc +89 -0
  35. package/dist/templates/.cursor/rules/10-documentation-quality-standards.mdc +390 -0
  36. package/dist/templates/.cursor/rules/11-scope-management-waivers.mdc +385 -0
  37. package/dist/templates/.cursor/rules/12-implementation-completeness.mdc +516 -0
  38. package/dist/templates/.cursor/rules/13-language-agnostic-standards.mdc +588 -0
  39. package/dist/templates/.cursor/rules/README.md +148 -0
  40. package/dist/templates/.github/copilot/instructions.md +311 -0
  41. package/dist/templates/.idea/runConfigurations/CAWS_Evaluate.xml +5 -0
  42. package/dist/templates/.idea/runConfigurations/CAWS_Validate.xml +5 -0
  43. package/dist/templates/.vscode/launch.json +56 -0
  44. package/dist/templates/.vscode/settings.json +93 -0
  45. package/dist/templates/.windsurf/workflows/caws-guided-development.md +92 -0
  46. package/dist/templates/COMMIT_CONVENTIONS.md +86 -0
  47. package/dist/templates/OIDC_SETUP.md +300 -0
  48. package/dist/templates/agents.md +1047 -0
  49. package/dist/templates/codemod/README.md +1 -0
  50. package/dist/templates/codemod/test.js +93 -0
  51. package/dist/templates/docs/README.md +150 -0
  52. package/dist/templates/scripts/quality-gates/check-god-objects.js +146 -0
  53. package/dist/templates/scripts/quality-gates/run-quality-gates.js +50 -0
  54. package/dist/templates/scripts/v3/analysis/todo_analyzer.py +1997 -0
  55. package/dist/tool-loader.js +6 -1
  56. package/dist/tool-validator.js +8 -2
  57. package/dist/utils/detection.js +4 -3
  58. package/dist/utils/git-lock.js +119 -0
  59. package/dist/utils/gitignore-updater.js +148 -0
  60. package/dist/utils/project-analysis.js +176 -16
  61. package/dist/utils/quality-gates.js +48 -7
  62. package/dist/utils/spec-resolver.js +27 -3
  63. package/dist/utils/yaml-validation.js +156 -0
  64. package/dist/validation/spec-validation.js +81 -2
  65. package/package.json +2 -2
  66. package/templates/.caws/schemas/waivers.schema.json +30 -0
  67. package/templates/.caws/schemas/working-spec.schema.json +133 -0
  68. package/templates/.caws/templates/working-spec.template.yml +74 -0
  69. package/templates/.caws/tools/README.md +21 -0
  70. package/templates/.caws/tools/scope-guard.js +208 -0
  71. package/templates/.caws/tools-allow.json +331 -0
  72. package/templates/.caws/waivers.yml +19 -0
  73. package/templates/.cursor/hooks/scope-guard.sh +2 -2
  74. package/templates/.cursor/hooks/validate-spec.sh +42 -7
  75. package/dist/budget-derivation.d.ts +0 -74
  76. package/dist/budget-derivation.d.ts.map +0 -1
  77. package/dist/cicd-optimizer.d.ts +0 -142
  78. package/dist/cicd-optimizer.d.ts.map +0 -1
  79. package/dist/commands/archive.d.ts +0 -50
  80. package/dist/commands/archive.d.ts.map +0 -1
  81. package/dist/commands/burnup.d.ts +0 -6
  82. package/dist/commands/burnup.d.ts.map +0 -1
  83. package/dist/commands/diagnose.d.ts +0 -52
  84. package/dist/commands/diagnose.d.ts.map +0 -1
  85. package/dist/commands/evaluate.d.ts +0 -8
  86. package/dist/commands/evaluate.d.ts.map +0 -1
  87. package/dist/commands/init.d.ts +0 -5
  88. package/dist/commands/init.d.ts.map +0 -1
  89. package/dist/commands/iterate.d.ts +0 -8
  90. package/dist/commands/iterate.d.ts.map +0 -1
  91. package/dist/commands/mode.d.ts +0 -24
  92. package/dist/commands/mode.d.ts.map +0 -1
  93. package/dist/commands/plan.d.ts +0 -49
  94. package/dist/commands/plan.d.ts.map +0 -1
  95. package/dist/commands/provenance.d.ts +0 -32
  96. package/dist/commands/provenance.d.ts.map +0 -1
  97. package/dist/commands/quality-gates.d.ts +0 -52
  98. package/dist/commands/quality-gates.d.ts.map +0 -1
  99. package/dist/commands/quality-monitor.d.ts +0 -17
  100. package/dist/commands/quality-monitor.d.ts.map +0 -1
  101. package/dist/commands/specs.d.ts +0 -71
  102. package/dist/commands/specs.d.ts.map +0 -1
  103. package/dist/commands/status.d.ts +0 -44
  104. package/dist/commands/status.d.ts.map +0 -1
  105. package/dist/commands/templates.d.ts +0 -74
  106. package/dist/commands/templates.d.ts.map +0 -1
  107. package/dist/commands/tool.d.ts +0 -13
  108. package/dist/commands/tool.d.ts.map +0 -1
  109. package/dist/commands/troubleshoot.d.ts +0 -8
  110. package/dist/commands/troubleshoot.d.ts.map +0 -1
  111. package/dist/commands/tutorial.d.ts +0 -55
  112. package/dist/commands/tutorial.d.ts.map +0 -1
  113. package/dist/commands/validate.d.ts +0 -15
  114. package/dist/commands/validate.d.ts.map +0 -1
  115. package/dist/commands/waivers.d.ts +0 -8
  116. package/dist/commands/waivers.d.ts.map +0 -1
  117. package/dist/commands/workflow.d.ts +0 -85
  118. package/dist/commands/workflow.d.ts.map +0 -1
  119. package/dist/config/index.d.ts +0 -29
  120. package/dist/config/index.d.ts.map +0 -1
  121. package/dist/config/modes.d.ts +0 -225
  122. package/dist/config/modes.d.ts.map +0 -1
  123. package/dist/constants/spec-types.d.ts +0 -41
  124. package/dist/constants/spec-types.d.ts.map +0 -1
  125. package/dist/error-handler.d.ts +0 -164
  126. package/dist/error-handler.d.ts.map +0 -1
  127. package/dist/generators/jest-config.d.ts +0 -32
  128. package/dist/generators/jest-config.d.ts.map +0 -1
  129. package/dist/generators/working-spec.d.ts +0 -13
  130. package/dist/generators/working-spec.d.ts.map +0 -1
  131. package/dist/index-new.d.ts +0 -5
  132. package/dist/index-new.d.ts.map +0 -1
  133. package/dist/index-new.js +0 -317
  134. package/dist/index.d.ts +0 -5
  135. package/dist/index.d.ts.map +0 -1
  136. package/dist/index.js.backup +0 -4711
  137. package/dist/minimal-cli.d.ts +0 -3
  138. package/dist/minimal-cli.d.ts.map +0 -1
  139. package/dist/policy/PolicyManager.d.ts +0 -104
  140. package/dist/policy/PolicyManager.d.ts.map +0 -1
  141. package/dist/scaffold/cursor-hooks.d.ts +0 -7
  142. package/dist/scaffold/cursor-hooks.d.ts.map +0 -1
  143. package/dist/scaffold/git-hooks.d.ts +0 -20
  144. package/dist/scaffold/git-hooks.d.ts.map +0 -1
  145. package/dist/scaffold/index.d.ts +0 -20
  146. package/dist/scaffold/index.d.ts.map +0 -1
  147. package/dist/spec/SpecFileManager.d.ts +0 -146
  148. package/dist/spec/SpecFileManager.d.ts.map +0 -1
  149. package/dist/test-analysis.d.ts +0 -182
  150. package/dist/test-analysis.d.ts.map +0 -1
  151. package/dist/tool-interface.d.ts +0 -236
  152. package/dist/tool-interface.d.ts.map +0 -1
  153. package/dist/tool-loader.d.ts +0 -77
  154. package/dist/tool-loader.d.ts.map +0 -1
  155. package/dist/tool-validator.d.ts +0 -72
  156. package/dist/tool-validator.d.ts.map +0 -1
  157. package/dist/utils/detection.d.ts +0 -7
  158. package/dist/utils/detection.d.ts.map +0 -1
  159. package/dist/utils/finalization.d.ts +0 -17
  160. package/dist/utils/finalization.d.ts.map +0 -1
  161. package/dist/utils/project-analysis.d.ts +0 -14
  162. package/dist/utils/project-analysis.d.ts.map +0 -1
  163. package/dist/utils/quality-gates.d.ts +0 -49
  164. package/dist/utils/quality-gates.d.ts.map +0 -1
  165. package/dist/utils/spec-resolver.d.ts +0 -88
  166. package/dist/utils/spec-resolver.d.ts.map +0 -1
  167. package/dist/utils/typescript-detector.d.ts +0 -63
  168. package/dist/utils/typescript-detector.d.ts.map +0 -1
  169. package/dist/validation/spec-validation.d.ts +0 -43
  170. package/dist/validation/spec-validation.d.ts.map +0 -1
  171. package/dist/waivers-manager.d.ts +0 -167
  172. package/dist/waivers-manager.d.ts.map +0 -1
  173. package/templates/apps/tools/caws/COMPLETION_REPORT.md +0 -331
  174. package/templates/apps/tools/caws/MIGRATION_SUMMARY.md +0 -360
  175. package/templates/apps/tools/caws/README.md +0 -463
  176. package/templates/apps/tools/caws/TEST_STATUS.md +0 -365
  177. package/templates/apps/tools/caws/attest.js +0 -357
  178. package/templates/apps/tools/caws/ci-optimizer.js +0 -642
  179. package/templates/apps/tools/caws/config.ts +0 -245
  180. package/templates/apps/tools/caws/cross-functional.js +0 -876
  181. package/templates/apps/tools/caws/dashboard.js +0 -1112
  182. package/templates/apps/tools/caws/flake-detector.ts +0 -362
  183. package/templates/apps/tools/caws/gates.js +0 -198
  184. package/templates/apps/tools/caws/gates.ts +0 -271
  185. package/templates/apps/tools/caws/language-adapters.ts +0 -381
  186. package/templates/apps/tools/caws/language-support.d.ts +0 -367
  187. package/templates/apps/tools/caws/language-support.d.ts.map +0 -1
  188. package/templates/apps/tools/caws/language-support.js +0 -585
  189. package/templates/apps/tools/caws/legacy-assessment.ts +0 -408
  190. package/templates/apps/tools/caws/legacy-assessor.js +0 -764
  191. package/templates/apps/tools/caws/mutant-analyzer.js +0 -734
  192. package/templates/apps/tools/caws/perf-budgets.ts +0 -349
  193. package/templates/apps/tools/caws/prompt-lint.js.backup +0 -274
  194. package/templates/apps/tools/caws/property-testing.js +0 -707
  195. package/templates/apps/tools/caws/provenance.d.ts +0 -14
  196. package/templates/apps/tools/caws/provenance.d.ts.map +0 -1
  197. package/templates/apps/tools/caws/provenance.js +0 -132
  198. package/templates/apps/tools/caws/provenance.js.backup +0 -73
  199. package/templates/apps/tools/caws/provenance.ts +0 -211
  200. package/templates/apps/tools/caws/security-provenance.ts +0 -483
  201. package/templates/apps/tools/caws/shared/base-tool.ts +0 -281
  202. package/templates/apps/tools/caws/shared/config-manager.ts +0 -366
  203. package/templates/apps/tools/caws/shared/gate-checker.ts +0 -849
  204. package/templates/apps/tools/caws/shared/types.ts +0 -444
  205. package/templates/apps/tools/caws/shared/validator.ts +0 -305
  206. package/templates/apps/tools/caws/shared/waivers-manager.ts +0 -174
  207. package/templates/apps/tools/caws/spec-test-mapper.ts +0 -391
  208. package/templates/apps/tools/caws/test-quality.js +0 -578
  209. package/templates/apps/tools/caws/validate.js +0 -76
  210. package/templates/apps/tools/caws/validate.ts +0 -228
  211. package/templates/apps/tools/caws/waivers.js +0 -344
  212. /package/{templates/apps/tools/caws → dist/templates/.caws}/schemas/waivers.schema.json +0 -0
  213. /package/{templates/apps/tools/caws → dist/templates/.caws}/schemas/working-spec.schema.json +0 -0
  214. /package/{templates/apps/tools/caws → dist/templates/.caws}/templates/working-spec.template.yml +0 -0
  215. /package/{templates/apps/tools/caws → dist/templates/.caws/tools}/scope-guard.js +0 -0
  216. /package/{templates/apps/tools/caws → dist/templates/.caws}/tools-allow.json +0 -0
  217. /package/{templates/apps/tools/caws → dist/templates/.caws}/waivers.yml +0 -0
@@ -19,8 +19,13 @@ const { safeAsync } = require('./error-handler');
19
19
  class ToolLoader extends EventEmitter {
20
20
  constructor(options = {}) {
21
21
  super();
22
+ // Check new location first, fall back to legacy location
23
+ const newToolsDir = path.join(process.cwd(), '.caws/tools');
24
+ const legacyToolsDir = path.join(process.cwd(), 'apps/tools/caws');
25
+ const defaultToolsDir = fs.existsSync(newToolsDir) ? newToolsDir : legacyToolsDir;
26
+
22
27
  this.options = {
23
- toolsDir: options.toolsDir || path.join(process.cwd(), 'apps/tools/caws'),
28
+ toolsDir: options.toolsDir || defaultToolsDir,
24
29
  cacheEnabled: options.cacheEnabled !== false,
25
30
  timeout: options.timeout || 10000,
26
31
  maxTools: options.maxTools || 50,
@@ -15,9 +15,15 @@ const crypto = require('crypto');
15
15
  */
16
16
  class ToolValidator {
17
17
  constructor(options = {}) {
18
+ // Check new location first, fall back to legacy location
19
+ const newAllowlistPath = path.join(process.cwd(), '.caws/tools-allow.json');
20
+ const legacyAllowlistPath = path.join(process.cwd(), 'apps/tools/caws/tools-allow.json');
21
+ const defaultAllowlistPath = fs.existsSync(newAllowlistPath)
22
+ ? newAllowlistPath
23
+ : legacyAllowlistPath;
24
+
18
25
  this.options = {
19
- allowlistPath:
20
- options.allowlistPath || path.join(process.cwd(), 'apps/tools/caws/tools-allow.json'),
26
+ allowlistPath: options.allowlistPath || defaultAllowlistPath,
21
27
  strictMode: options.strictMode !== false,
22
28
  maxFileSize: options.maxFileSize || 1024 * 1024, // 1MB
23
29
  ...options,
@@ -73,9 +73,10 @@ function detectCAWSSetup(cwd = process.cwd()) {
73
73
  const specFiles = files.filter((f) => f.endsWith('-spec.yaml'));
74
74
  const hasMultipleSpecs = specFiles.length > 1;
75
75
 
76
- // Check for tools directory (enhanced setup)
77
- const toolsDir = path.join(cwd, 'apps/tools/caws');
78
- const hasTools = fs.existsSync(toolsDir);
76
+ // Check for tools directory (enhanced setup) - check new location first
77
+ const toolsDir = path.join(cwd, '.caws/tools');
78
+ const legacyToolsDir = path.join(cwd, 'apps/tools/caws');
79
+ const hasTools = fs.existsSync(toolsDir) || fs.existsSync(legacyToolsDir);
79
80
 
80
81
  // Determine setup type
81
82
  let setupType = 'basic';
@@ -0,0 +1,119 @@
1
+ /**
2
+ * @fileoverview Git Lock Detection Utilities
3
+ * Functions for detecting and handling git locks
4
+ * @author @darianrosebrook
5
+ */
6
+
7
+ const fs = require('fs-extra');
8
+ const path = require('path');
9
+
10
+ /**
11
+ * Check for git lock files
12
+ * @param {string} projectRoot - Project root directory
13
+ * @returns {Object} Lock status information
14
+ */
15
+ function checkGitLock(projectRoot) {
16
+ const lockFile = path.join(projectRoot, '.git', 'index.lock');
17
+ const headLockFile = path.join(projectRoot, '.git', 'HEAD.lock');
18
+
19
+ const result = {
20
+ locked: false,
21
+ stale: false,
22
+ lockFiles: [],
23
+ message: null,
24
+ suggestion: null,
25
+ };
26
+
27
+ // Check index.lock
28
+ if (fs.existsSync(lockFile)) {
29
+ const lockAge = Date.now() - fs.statSync(lockFile).mtimeMs;
30
+ const lockAgeMinutes = Math.floor(lockAge / 60000);
31
+
32
+ result.locked = true;
33
+ result.lockFiles.push({
34
+ path: '.git/index.lock',
35
+ age: lockAgeMinutes,
36
+ stale: lockAgeMinutes > 5,
37
+ });
38
+
39
+ if (lockAgeMinutes > 5) {
40
+ // Stale lock (older than 5 minutes)
41
+ result.stale = true;
42
+ result.message = `Stale git lock detected (${lockAgeMinutes} minutes old). This may indicate a crashed git process.`;
43
+ result.suggestion = 'Remove stale lock: rm .git/index.lock';
44
+ } else {
45
+ // Active lock
46
+ result.message =
47
+ 'Git lock detected. Another git process may be running.';
48
+ result.suggestion =
49
+ 'Wait for the other process to complete, or check for running git/editor processes';
50
+ }
51
+ }
52
+
53
+ // Check HEAD.lock
54
+ if (fs.existsSync(headLockFile)) {
55
+ const lockAge = Date.now() - fs.statSync(headLockFile).mtimeMs;
56
+ const lockAgeMinutes = Math.floor(lockAge / 60000);
57
+
58
+ result.locked = true;
59
+ result.lockFiles.push({
60
+ path: '.git/HEAD.lock',
61
+ age: lockAgeMinutes,
62
+ stale: lockAgeMinutes > 5,
63
+ });
64
+
65
+ if (lockAgeMinutes > 5) {
66
+ result.stale = true;
67
+ if (!result.message) {
68
+ result.message = `Stale git lock detected (${lockAgeMinutes} minutes old).`;
69
+ result.suggestion = 'Remove stale lock: rm .git/HEAD.lock';
70
+ }
71
+ }
72
+ }
73
+
74
+ return result;
75
+ }
76
+
77
+ /**
78
+ * Format git lock error message
79
+ * @param {Object} lockStatus - Lock status from checkGitLock
80
+ * @returns {string} Formatted error message
81
+ */
82
+ function formatGitLockError(lockStatus) {
83
+ if (!lockStatus.locked) {
84
+ return null;
85
+ }
86
+
87
+ let message = '⚠️ Git lock detected\n';
88
+ message += ` ${lockStatus.message}\n`;
89
+
90
+ if (lockStatus.lockFiles.length > 0) {
91
+ message += '\n Lock files:\n';
92
+ for (const lockFile of lockStatus.lockFiles) {
93
+ message += ` - ${lockFile.path} (${lockFile.age} minutes old)`;
94
+ if (lockFile.stale) {
95
+ message += ' [STALE]';
96
+ }
97
+ message += '\n';
98
+ }
99
+ }
100
+
101
+ if (lockStatus.suggestion) {
102
+ message += `\n 💡 ${lockStatus.suggestion}\n`;
103
+ }
104
+
105
+ if (lockStatus.stale) {
106
+ message +=
107
+ '\n ⚠️ Warning: Removing stale locks may cause data loss if another process is actually running.\n';
108
+ message += ' Check for running git/editor processes before removing locks.\n';
109
+ }
110
+
111
+ return message;
112
+ }
113
+
114
+ module.exports = {
115
+ checkGitLock,
116
+ formatGitLockError,
117
+ };
118
+
119
+
@@ -0,0 +1,148 @@
1
+ /**
2
+ * @fileoverview Gitignore Updater Utility
3
+ * Updates .gitignore to properly handle CAWS runtime files vs source files
4
+ * @author @darianrosebrook
5
+ */
6
+
7
+ const fs = require('fs-extra');
8
+ const path = require('path');
9
+ const chalk = require('chalk');
10
+
11
+ /**
12
+ * CAWS .gitignore entries
13
+ *
14
+ * Strategy: Track shared/collaborative files, ignore local-only runtime data
15
+ *
16
+ * TRACKED (shared with team):
17
+ * - .caws/working-spec.yaml (main spec)
18
+ * - .caws/specs/*.yaml (feature specs)
19
+ * - .caws/policy.yaml (team policy)
20
+ * - .caws/waivers/*.yaml (project-wide waivers)
21
+ * - .caws/provenance/ (audit trails for compliance)
22
+ * - .caws/changes/ (change tracking for team visibility)
23
+ * - .caws/archive/ (archived changes for history)
24
+ * - .caws/plans/*.md (implementation plans)
25
+ *
26
+ * IGNORED (local-only):
27
+ * - .agent/ (agent runtime tracking, local to each developer)
28
+ * - Temporary files (*.tmp, *.bak)
29
+ * - Logs (caws.log, debug logs)
30
+ * - Local overrides (caws.local.*)
31
+ */
32
+ const CAWS_GITIGNORE_ENTRIES = `
33
+ # CAWS Local Runtime Data (developer-specific, should not be tracked)
34
+ # ====================================================================
35
+ # Note: Specs, policy, waivers, provenance, and plans ARE tracked for team collaboration
36
+ # Only local agent tracking, generated tools, and temporary files are ignored
37
+
38
+ # Agent runtime tracking (local to each developer)
39
+ .agent/
40
+
41
+ # CAWS tools (now in .caws/tools/)
42
+ .caws/tools/
43
+ # Legacy location (for backward compatibility)
44
+ apps/tools/caws/
45
+
46
+ # Temporary CAWS files
47
+ **/*.caws.tmp
48
+ **/*.working-spec.bak
49
+ .caws/*.tmp
50
+ .caws/*.bak
51
+
52
+ # CAWS logs (local debugging)
53
+ caws-debug.log*
54
+ **/caws.log
55
+ .caws/*.log
56
+
57
+ # Local development overrides (developer-specific)
58
+ caws.local.*
59
+ .caws/local.*
60
+ `;
61
+
62
+ /**
63
+ * Update .gitignore to include CAWS runtime file exclusions
64
+ * @param {string} projectRoot - Project root directory
65
+ * @param {Object} options - Options
66
+ * @param {boolean} options.force - Force update even if entries exist
67
+ * @returns {Promise<boolean>} Whether .gitignore was updated
68
+ */
69
+ async function updateGitignore(projectRoot, options = {}) {
70
+ const { force = false } = options;
71
+ const gitignorePath = path.join(projectRoot, '.gitignore');
72
+
73
+ try {
74
+ // Read existing .gitignore or create empty
75
+ let existingContent = '';
76
+ if (await fs.pathExists(gitignorePath)) {
77
+ existingContent = await fs.readFile(gitignorePath, 'utf8');
78
+ }
79
+
80
+ // Check if CAWS entries already exist (check for either old or new header)
81
+ const hasCawsEntries =
82
+ existingContent.includes('# CAWS Local Runtime Data') ||
83
+ existingContent.includes('# CAWS Runtime Data');
84
+
85
+ if (hasCawsEntries && !force) {
86
+ // Already has CAWS entries, skip
87
+ return false;
88
+ }
89
+
90
+ // If old entries exist, replace them with new ones
91
+ if (existingContent.includes('# CAWS Runtime Data') && force) {
92
+ // Remove old CAWS entries (between "# CAWS Runtime Data" and next major section)
93
+ const lines = existingContent.split('\n');
94
+ const startIndex = lines.findIndex((line) => line.includes('# CAWS Runtime Data'));
95
+ if (startIndex !== -1) {
96
+ // Find the end of CAWS section (next major section starting with #)
97
+ let endIndex = startIndex + 1;
98
+ while (
99
+ endIndex < lines.length &&
100
+ (lines[endIndex].trim() === '' ||
101
+ lines[endIndex].startsWith('#') ||
102
+ lines[endIndex].startsWith('.caws/') ||
103
+ lines[endIndex].startsWith('.agent/') ||
104
+ lines[endIndex].includes('caws') ||
105
+ lines[endIndex].includes('CAWS'))
106
+ ) {
107
+ endIndex++;
108
+ }
109
+ // Remove old section and insert new one
110
+ const before = lines.slice(0, startIndex).join('\n');
111
+ const after = lines.slice(endIndex).join('\n');
112
+ existingContent = [before, after].filter(Boolean).join('\n');
113
+ }
114
+ }
115
+
116
+ // Append CAWS entries
117
+ const updatedContent = existingContent.trim() + '\n' + CAWS_GITIGNORE_ENTRIES.trim() + '\n';
118
+
119
+ await fs.writeFile(gitignorePath, updatedContent, 'utf8');
120
+
121
+ return true;
122
+ } catch (error) {
123
+ console.warn(chalk.yellow(`⚠️ Could not update .gitignore: ${error.message}`));
124
+ return false;
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Verify .gitignore has proper CAWS entries
130
+ * @param {string} projectRoot - Project root directory
131
+ * @returns {Promise<boolean>} Whether .gitignore has CAWS entries
132
+ */
133
+ async function verifyGitignore(projectRoot) {
134
+ const gitignorePath = path.join(projectRoot, '.gitignore');
135
+
136
+ if (!(await fs.pathExists(gitignorePath))) {
137
+ return false;
138
+ }
139
+
140
+ const content = await fs.readFile(gitignorePath, 'utf8');
141
+ return content.includes('# CAWS Local Runtime Data') || content.includes('# CAWS Runtime Data');
142
+ }
143
+
144
+ module.exports = {
145
+ updateGitignore,
146
+ verifyGitignore,
147
+ CAWS_GITIGNORE_ENTRIES,
148
+ };
@@ -111,9 +111,7 @@ function detectsPublishing(cwd = process.cwd()) {
111
111
  // Check package.json for npm publishing
112
112
  if (files.includes('package.json')) {
113
113
  try {
114
- const packageJson = JSON.parse(
115
- fs.readFileSync(path.join(cwd, 'package.json'), 'utf8')
116
- );
114
+ const packageJson = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8'));
117
115
 
118
116
  // Indicators of publishing:
119
117
  // - Has publishConfig
@@ -123,9 +121,7 @@ function detectsPublishing(cwd = process.cwd()) {
123
121
  const hasPublishConfig = packageJson.publishConfig;
124
122
  const hasPublishScript =
125
123
  packageJson.scripts &&
126
- Object.keys(packageJson.scripts).some((key) =>
127
- key.toLowerCase().includes('publish')
128
- );
124
+ Object.keys(packageJson.scripts).some((key) => key.toLowerCase().includes('publish'));
129
125
  const hasScopedName = packageJson.name && packageJson.name.startsWith('@');
130
126
  const hasRepository = packageJson.repository;
131
127
 
@@ -140,16 +136,13 @@ function detectsPublishing(cwd = process.cwd()) {
140
136
  // Check pyproject.toml for PyPI publishing
141
137
  if (files.includes('pyproject.toml')) {
142
138
  try {
143
- const pyprojectContent = fs.readFileSync(
144
- path.join(cwd, 'pyproject.toml'),
145
- 'utf8'
146
- );
139
+ const pyprojectContent = fs.readFileSync(path.join(cwd, 'pyproject.toml'), 'utf8');
147
140
 
148
141
  // Check for build system and project metadata (indicates publishable package)
149
142
  const hasBuildSystem = pyprojectContent.includes('[build-system]');
150
143
  const hasProjectMetadata = pyprojectContent.includes('[project]');
151
- const hasToolPublish = pyprojectContent.includes('[tool.publish]') ||
152
- pyprojectContent.includes('[tool.twine]');
144
+ const hasToolPublish =
145
+ pyprojectContent.includes('[tool.publish]') || pyprojectContent.includes('[tool.twine]');
153
146
 
154
147
  if ((hasBuildSystem && hasProjectMetadata) || hasToolPublish) {
155
148
  return true;
@@ -177,10 +170,7 @@ function detectsPublishing(cwd = process.cwd()) {
177
170
  const workflowFiles = fs.readdirSync(workflowsPath);
178
171
  for (const workflowFile of workflowFiles) {
179
172
  if (workflowFile.endsWith('.yml') || workflowFile.endsWith('.yaml')) {
180
- const workflowContent = fs.readFileSync(
181
- path.join(workflowsPath, workflowFile),
182
- 'utf8'
183
- );
173
+ const workflowContent = fs.readFileSync(path.join(workflowsPath, workflowFile), 'utf8');
184
174
  // Check for common publishing actions/commands
185
175
  if (
186
176
  workflowContent.includes('npm publish') ||
@@ -201,8 +191,178 @@ function detectsPublishing(cwd = process.cwd()) {
201
191
  return false;
202
192
  }
203
193
 
194
+ /**
195
+ * Detect primary programming language(s) used in project
196
+ * @param {string} cwd - Current working directory
197
+ * @returns {Object} Language detection result with primary language and indicators
198
+ */
199
+ function detectProjectLanguage(cwd = process.cwd()) {
200
+ const files = fs.readdirSync(cwd);
201
+ const indicators = {
202
+ javascript: false,
203
+ typescript: false,
204
+ python: false,
205
+ rust: false,
206
+ go: false,
207
+ java: false,
208
+ csharp: false,
209
+ php: false,
210
+ };
211
+
212
+ // JavaScript/TypeScript indicators
213
+ if (files.includes('package.json')) {
214
+ indicators.javascript = true;
215
+ try {
216
+ const packageJson = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8'));
217
+ const allDeps = {
218
+ ...(packageJson.dependencies || {}),
219
+ ...(packageJson.devDependencies || {}),
220
+ };
221
+ if ('typescript' in allDeps || files.includes('tsconfig.json')) {
222
+ indicators.typescript = true;
223
+ indicators.javascript = false; // TypeScript supersedes JavaScript
224
+ }
225
+ } catch (e) {
226
+ // Ignore parse errors
227
+ }
228
+ }
229
+
230
+ // Python indicators
231
+ if (
232
+ files.includes('requirements.txt') ||
233
+ files.includes('pyproject.toml') ||
234
+ files.includes('setup.py') ||
235
+ files.includes('Pipfile') ||
236
+ files.includes('poetry.lock') ||
237
+ files.some((f) => f.endsWith('.py'))
238
+ ) {
239
+ indicators.python = true;
240
+ }
241
+
242
+ // Rust indicators
243
+ if (files.includes('Cargo.toml') || files.some((f) => f.endsWith('.rs'))) {
244
+ indicators.rust = true;
245
+ }
246
+
247
+ // Go indicators
248
+ if (
249
+ files.includes('go.mod') ||
250
+ files.includes('go.sum') ||
251
+ files.some((f) => f.endsWith('.go'))
252
+ ) {
253
+ indicators.go = true;
254
+ }
255
+
256
+ // Java indicators
257
+ if (
258
+ files.includes('pom.xml') ||
259
+ files.includes('build.gradle') ||
260
+ files.some((f) => f.endsWith('.java'))
261
+ ) {
262
+ indicators.java = true;
263
+ }
264
+
265
+ // C# indicators
266
+ if (
267
+ files.some((f) => f.endsWith('.csproj')) ||
268
+ files.some((f) => f.endsWith('.sln')) ||
269
+ files.some((f) => f.endsWith('.cs'))
270
+ ) {
271
+ indicators.csharp = true;
272
+ }
273
+
274
+ // PHP indicators
275
+ if (
276
+ files.includes('composer.json') ||
277
+ files.includes('composer.lock') ||
278
+ files.some((f) => f.endsWith('.php'))
279
+ ) {
280
+ indicators.php = true;
281
+ }
282
+
283
+ // Determine primary language (priority order)
284
+ let primaryLanguage = 'unknown';
285
+ if (indicators.typescript) {
286
+ primaryLanguage = 'typescript';
287
+ } else if (indicators.javascript) {
288
+ primaryLanguage = 'javascript';
289
+ } else if (indicators.python) {
290
+ primaryLanguage = 'python';
291
+ } else if (indicators.rust) {
292
+ primaryLanguage = 'rust';
293
+ } else if (indicators.go) {
294
+ primaryLanguage = 'go';
295
+ } else if (indicators.java) {
296
+ primaryLanguage = 'java';
297
+ } else if (indicators.csharp) {
298
+ primaryLanguage = 'csharp';
299
+ } else if (indicators.php) {
300
+ primaryLanguage = 'php';
301
+ }
302
+
303
+ return {
304
+ primary: primaryLanguage,
305
+ indicators,
306
+ hasNodeJs: indicators.javascript || indicators.typescript,
307
+ hasPython: indicators.python,
308
+ };
309
+ }
310
+
311
+ /**
312
+ * Get language-agnostic suggestion for TODO analyzer installation
313
+ * Focuses on runtime availability (Node.js/npx) rather than project language
314
+ * @param {string} cwd - Current working directory
315
+ * @returns {string} Installation suggestion message
316
+ */
317
+ function getTodoAnalyzerSuggestion(cwd = process.cwd()) {
318
+ // Check runtime availability (language-agnostic)
319
+ let hasNodeJs = false;
320
+ let hasNpx = false;
321
+ try {
322
+ const { execSync } = require('child_process');
323
+ execSync('command -v node', { encoding: 'utf8', stdio: 'ignore' });
324
+ hasNodeJs = true;
325
+ execSync('command -v npx', { encoding: 'utf8', stdio: 'ignore' });
326
+ hasNpx = true;
327
+ } catch (e) {
328
+ // Node.js/npx not available
329
+ }
330
+
331
+ const suggestions = [];
332
+
333
+ if (hasNpx) {
334
+ // npx available - works for any language, no installation needed
335
+ suggestions.push(
336
+ ' • Use npx (no installation required): npx --yes @paths.design/quality-gates'
337
+ );
338
+ suggestions.push(' • Install package: npm install --save-dev @paths.design/quality-gates');
339
+ } else if (hasNodeJs) {
340
+ // Node.js available but npx not found (unusual)
341
+ suggestions.push(' • Install package: npm install --save-dev @paths.design/quality-gates');
342
+ suggestions.push(
343
+ ' • Install npx: npm install -g npx (then use: npx --yes @paths.design/quality-gates)'
344
+ );
345
+ } else {
346
+ // Node.js not available - suggest installation
347
+ suggestions.push(
348
+ ' • Install Node.js: https://nodejs.org/ (then use: npx --yes @paths.design/quality-gates)'
349
+ );
350
+ suggestions.push(' • Use CAWS MCP server: caws quality-gates (via MCP)');
351
+ }
352
+
353
+ // Check for project-specific scripts (language-agnostic - if they exist, suggest them)
354
+ const pythonScript = path.join(cwd, 'scripts', 'v3', 'analysis', 'todo_analyzer.py');
355
+ if (fs.existsSync(pythonScript)) {
356
+ suggestions.push(` • Use project script: python3 ${pythonScript}`);
357
+ }
358
+
359
+ return suggestions.join('\n');
360
+ }
361
+
204
362
  module.exports = {
205
363
  detectProjectType,
206
364
  shouldInitInCurrentDirectory,
207
365
  detectsPublishing,
366
+ detectProjectLanguage,
367
+ getTodoAnalyzerSuggestion,
208
368
  };
@@ -11,6 +11,7 @@ const fs = require('fs');
11
11
  const path = require('path');
12
12
  const yaml = require('js-yaml');
13
13
  const { execSync } = require('child_process');
14
+ const { getTodoAnalyzerSuggestion } = require('./project-analysis');
14
15
 
15
16
  /**
16
17
  * Quality Gate Configuration
@@ -196,18 +197,58 @@ function checkHiddenTodos(stagedFiles) {
196
197
  console.log(`📁 Found ${supportedFiles.length} staged files to analyze for TODOs`);
197
198
 
198
199
  try {
199
- // Check if TODO analyzer exists
200
- const analyzerPath = path.join(process.cwd(), 'scripts/v3/analysis/todo_analyzer.py');
201
- if (!fs.existsSync(analyzerPath)) {
200
+ // Find TODO analyzer .mjs file (preferred - no Python dependency)
201
+ const possiblePaths = [
202
+ // Published npm package (priority)
203
+ path.join(
204
+ process.cwd(),
205
+ 'node_modules',
206
+ '@paths.design',
207
+ 'quality-gates',
208
+ 'todo-analyzer.mjs'
209
+ ),
210
+ // Legacy monorepo local copy
211
+ path.join(process.cwd(), 'node_modules', '@caws', 'quality-gates', 'todo-analyzer.mjs'),
212
+ // Monorepo structure (development)
213
+ path.join(process.cwd(), 'packages', 'quality-gates', 'todo-analyzer.mjs'),
214
+ // Local copy in scripts directory (if scaffolded)
215
+ path.join(process.cwd(), 'scripts', 'todo-analyzer.mjs'),
216
+ // Legacy Python analyzer (deprecated)
217
+ path.join(process.cwd(), 'scripts', 'v3', 'analysis', 'todo_analyzer.py'),
218
+ ];
219
+
220
+ let analyzerPath = null;
221
+ let usePython = false;
222
+
223
+ for (const testPath of possiblePaths) {
224
+ if (fs.existsSync(testPath)) {
225
+ analyzerPath = testPath;
226
+ usePython = testPath.endsWith('.py');
227
+ break;
228
+ }
229
+ }
230
+
231
+ if (!analyzerPath) {
202
232
  console.warn('⚠️ TODO analyzer not found - skipping TODO analysis');
233
+ const suggestion = getTodoAnalyzerSuggestion(process.cwd());
234
+ console.warn('💡 Available options for TODO analysis:');
235
+ console.warn(suggestion);
203
236
  return { todos: [], blocking: 0, total: 0 };
204
237
  }
205
238
 
239
+ if (usePython) {
240
+ console.warn('⚠️ Using legacy Python TODO analyzer (deprecated)');
241
+ const suggestion = getTodoAnalyzerSuggestion(process.cwd());
242
+ console.warn('💡 Consider upgrading to Node.js version:');
243
+ console.warn(suggestion);
244
+ }
245
+
206
246
  // Run the TODO analyzer with staged files
207
- const result = execSync(
208
- `python3 ${analyzerPath} --staged-only --min-confidence ${CONFIG.todoConfidenceThreshold}`,
209
- { encoding: 'utf8', cwd: process.cwd() }
210
- );
247
+ const command = usePython
248
+ ? `python3 ${analyzerPath} --staged-only --min-confidence ${CONFIG.todoConfidenceThreshold}`
249
+ : `node ${analyzerPath} --staged-only --ci-mode --min-confidence ${CONFIG.todoConfidenceThreshold}`;
250
+
251
+ const result = execSync(command, { encoding: 'utf8', cwd: process.cwd() });
211
252
 
212
253
  // Parse the output to extract TODO count
213
254
  const lines = result.split('\n');
@@ -108,7 +108,14 @@ async function resolveSpec(options = {}) {
108
108
  const specPath = path.join(SPECS_DIR, registry.specs[id].path);
109
109
  try {
110
110
  const content = await fs.readFile(specPath, 'utf8');
111
- const spec = yaml.load(content);
111
+ let spec;
112
+ try {
113
+ spec = yaml.load(content);
114
+ } catch (yamlError) {
115
+ console.log(chalk.yellow(` - ${id} (YAML syntax error: ${yamlError.message})`));
116
+ specsInfo.push({ id, type: 'unknown', status: 'unknown', title: 'YAML error' });
117
+ continue;
118
+ }
112
119
  const status = spec.status || 'draft';
113
120
  const type = spec.type || 'feature';
114
121
  const statusColor =
@@ -122,7 +129,7 @@ async function resolveSpec(options = {}) {
122
129
  );
123
130
  specsInfo.push({ id, type, status, title: spec.title || 'Untitled' });
124
131
  } catch (error) {
125
- console.log(chalk.yellow(` - ${id} (error loading details)`));
132
+ console.log(chalk.yellow(` - ${id} (error loading details: ${error.message})`));
126
133
  specsInfo.push({ id, type: 'unknown', status: 'unknown', title: 'Error loading' });
127
134
  }
128
135
  }
@@ -363,7 +370,20 @@ async function checkScopeConflicts(specIds) {
363
370
 
364
371
  try {
365
372
  const content = await fs.readFile(specPath, 'utf8');
366
- const spec = yaml.load(content);
373
+ let spec;
374
+ try {
375
+ spec = yaml.load(content);
376
+ } catch (yamlError) {
377
+ const relativePath = path.relative(process.cwd(), specPath);
378
+ throw new Error(
379
+ `Invalid YAML syntax in ${relativePath}: ${yamlError.message}\n` +
380
+ (yamlError.mark
381
+ ? ` Line ${yamlError.mark.line + 1}, Column ${yamlError.mark.column + 1}\n`
382
+ : '') +
383
+ (yamlError.mark?.snippet ? ` ${yamlError.mark.snippet}\n` : '') +
384
+ `💡 Fix YAML syntax errors or use 'caws specs create <id>' for proper structure`
385
+ );
386
+ }
367
387
 
368
388
  specScopes.push({
369
389
  id,
@@ -492,6 +512,10 @@ async function suggestMigration() {
492
512
  function suggestFeatureBreakdown(legacySpec) {
493
513
  const features = [];
494
514
 
515
+ if (!legacySpec) {
516
+ return features;
517
+ }
518
+
495
519
  if (legacySpec.acceptance && legacySpec.acceptance.length > 0) {
496
520
  // Group acceptance criteria by logical features
497
521
  const criteriaByFeature = {};