@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.
- package/dist/budget-derivation.js +5 -4
- package/dist/commands/diagnose.js +24 -19
- package/dist/commands/init.js +51 -4
- package/dist/commands/quality-gates.js +147 -9
- package/dist/commands/specs.js +148 -14
- package/dist/commands/status.js +2 -2
- package/dist/commands/tool.js +2 -4
- package/dist/config/index.js +17 -8
- package/dist/generators/working-spec.js +19 -6
- package/dist/scaffold/git-hooks.js +245 -46
- package/dist/scaffold/index.js +53 -7
- package/dist/templates/.caws/tools/README.md +21 -0
- package/dist/templates/.cursor/README.md +311 -0
- package/dist/templates/.cursor/hooks/audit.sh +55 -0
- package/dist/templates/.cursor/hooks/block-dangerous.sh +83 -0
- package/dist/templates/.cursor/hooks/caws-quality-check.sh +52 -0
- package/dist/templates/.cursor/hooks/caws-scope-guard.sh +130 -0
- package/dist/templates/.cursor/hooks/caws-tool-validation.sh +121 -0
- package/dist/templates/.cursor/hooks/format.sh +38 -0
- package/dist/templates/.cursor/hooks/naming-check.sh +64 -0
- package/dist/templates/.cursor/hooks/scan-secrets.sh +46 -0
- package/dist/templates/.cursor/hooks/scope-guard.sh +52 -0
- package/dist/templates/.cursor/hooks/validate-spec.sh +83 -0
- package/dist/templates/.cursor/hooks.json +59 -0
- package/dist/templates/.cursor/rules/00-claims-verification.mdc +144 -0
- package/dist/templates/.cursor/rules/01-working-style.mdc +50 -0
- package/dist/templates/.cursor/rules/02-quality-gates.mdc +370 -0
- package/dist/templates/.cursor/rules/03-naming-and-refactor.mdc +33 -0
- package/dist/templates/.cursor/rules/04-logging-language-style.mdc +23 -0
- package/dist/templates/.cursor/rules/05-safe-defaults-guards.mdc +23 -0
- package/dist/templates/.cursor/rules/06-typescript-conventions.mdc +36 -0
- package/dist/templates/.cursor/rules/07-process-ops.mdc +20 -0
- package/dist/templates/.cursor/rules/08-solid-and-architecture.mdc +16 -0
- package/dist/templates/.cursor/rules/09-docstrings.mdc +89 -0
- package/dist/templates/.cursor/rules/10-documentation-quality-standards.mdc +390 -0
- package/dist/templates/.cursor/rules/11-scope-management-waivers.mdc +385 -0
- package/dist/templates/.cursor/rules/12-implementation-completeness.mdc +516 -0
- package/dist/templates/.cursor/rules/13-language-agnostic-standards.mdc +588 -0
- package/dist/templates/.cursor/rules/README.md +148 -0
- package/dist/templates/.github/copilot/instructions.md +311 -0
- package/dist/templates/.idea/runConfigurations/CAWS_Evaluate.xml +5 -0
- package/dist/templates/.idea/runConfigurations/CAWS_Validate.xml +5 -0
- package/dist/templates/.vscode/launch.json +56 -0
- package/dist/templates/.vscode/settings.json +93 -0
- package/dist/templates/.windsurf/workflows/caws-guided-development.md +92 -0
- package/dist/templates/COMMIT_CONVENTIONS.md +86 -0
- package/dist/templates/OIDC_SETUP.md +300 -0
- package/dist/templates/agents.md +1047 -0
- package/dist/templates/codemod/README.md +1 -0
- package/dist/templates/codemod/test.js +93 -0
- package/dist/templates/docs/README.md +150 -0
- package/dist/templates/scripts/quality-gates/check-god-objects.js +146 -0
- package/dist/templates/scripts/quality-gates/run-quality-gates.js +50 -0
- package/dist/templates/scripts/v3/analysis/todo_analyzer.py +1997 -0
- package/dist/tool-loader.js +6 -1
- package/dist/tool-validator.js +8 -2
- package/dist/utils/detection.js +4 -3
- package/dist/utils/git-lock.js +119 -0
- package/dist/utils/gitignore-updater.js +148 -0
- package/dist/utils/project-analysis.js +176 -16
- package/dist/utils/quality-gates.js +48 -7
- package/dist/utils/spec-resolver.js +27 -3
- package/dist/utils/yaml-validation.js +156 -0
- package/dist/validation/spec-validation.js +81 -2
- package/package.json +2 -2
- package/templates/.caws/schemas/waivers.schema.json +30 -0
- package/templates/.caws/schemas/working-spec.schema.json +133 -0
- package/templates/.caws/templates/working-spec.template.yml +74 -0
- package/templates/.caws/tools/README.md +21 -0
- package/templates/.caws/tools/scope-guard.js +208 -0
- package/templates/.caws/tools-allow.json +331 -0
- package/templates/.caws/waivers.yml +19 -0
- package/templates/.cursor/hooks/scope-guard.sh +2 -2
- package/templates/.cursor/hooks/validate-spec.sh +42 -7
- package/dist/budget-derivation.d.ts +0 -74
- package/dist/budget-derivation.d.ts.map +0 -1
- package/dist/cicd-optimizer.d.ts +0 -142
- package/dist/cicd-optimizer.d.ts.map +0 -1
- package/dist/commands/archive.d.ts +0 -50
- package/dist/commands/archive.d.ts.map +0 -1
- package/dist/commands/burnup.d.ts +0 -6
- package/dist/commands/burnup.d.ts.map +0 -1
- package/dist/commands/diagnose.d.ts +0 -52
- package/dist/commands/diagnose.d.ts.map +0 -1
- package/dist/commands/evaluate.d.ts +0 -8
- package/dist/commands/evaluate.d.ts.map +0 -1
- package/dist/commands/init.d.ts +0 -5
- package/dist/commands/init.d.ts.map +0 -1
- package/dist/commands/iterate.d.ts +0 -8
- package/dist/commands/iterate.d.ts.map +0 -1
- package/dist/commands/mode.d.ts +0 -24
- package/dist/commands/mode.d.ts.map +0 -1
- package/dist/commands/plan.d.ts +0 -49
- package/dist/commands/plan.d.ts.map +0 -1
- package/dist/commands/provenance.d.ts +0 -32
- package/dist/commands/provenance.d.ts.map +0 -1
- package/dist/commands/quality-gates.d.ts +0 -52
- package/dist/commands/quality-gates.d.ts.map +0 -1
- package/dist/commands/quality-monitor.d.ts +0 -17
- package/dist/commands/quality-monitor.d.ts.map +0 -1
- package/dist/commands/specs.d.ts +0 -71
- package/dist/commands/specs.d.ts.map +0 -1
- package/dist/commands/status.d.ts +0 -44
- package/dist/commands/status.d.ts.map +0 -1
- package/dist/commands/templates.d.ts +0 -74
- package/dist/commands/templates.d.ts.map +0 -1
- package/dist/commands/tool.d.ts +0 -13
- package/dist/commands/tool.d.ts.map +0 -1
- package/dist/commands/troubleshoot.d.ts +0 -8
- package/dist/commands/troubleshoot.d.ts.map +0 -1
- package/dist/commands/tutorial.d.ts +0 -55
- package/dist/commands/tutorial.d.ts.map +0 -1
- package/dist/commands/validate.d.ts +0 -15
- package/dist/commands/validate.d.ts.map +0 -1
- package/dist/commands/waivers.d.ts +0 -8
- package/dist/commands/waivers.d.ts.map +0 -1
- package/dist/commands/workflow.d.ts +0 -85
- package/dist/commands/workflow.d.ts.map +0 -1
- package/dist/config/index.d.ts +0 -29
- package/dist/config/index.d.ts.map +0 -1
- package/dist/config/modes.d.ts +0 -225
- package/dist/config/modes.d.ts.map +0 -1
- package/dist/constants/spec-types.d.ts +0 -41
- package/dist/constants/spec-types.d.ts.map +0 -1
- package/dist/error-handler.d.ts +0 -164
- package/dist/error-handler.d.ts.map +0 -1
- package/dist/generators/jest-config.d.ts +0 -32
- package/dist/generators/jest-config.d.ts.map +0 -1
- package/dist/generators/working-spec.d.ts +0 -13
- package/dist/generators/working-spec.d.ts.map +0 -1
- package/dist/index-new.d.ts +0 -5
- package/dist/index-new.d.ts.map +0 -1
- package/dist/index-new.js +0 -317
- package/dist/index.d.ts +0 -5
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.backup +0 -4711
- package/dist/minimal-cli.d.ts +0 -3
- package/dist/minimal-cli.d.ts.map +0 -1
- package/dist/policy/PolicyManager.d.ts +0 -104
- package/dist/policy/PolicyManager.d.ts.map +0 -1
- package/dist/scaffold/cursor-hooks.d.ts +0 -7
- package/dist/scaffold/cursor-hooks.d.ts.map +0 -1
- package/dist/scaffold/git-hooks.d.ts +0 -20
- package/dist/scaffold/git-hooks.d.ts.map +0 -1
- package/dist/scaffold/index.d.ts +0 -20
- package/dist/scaffold/index.d.ts.map +0 -1
- package/dist/spec/SpecFileManager.d.ts +0 -146
- package/dist/spec/SpecFileManager.d.ts.map +0 -1
- package/dist/test-analysis.d.ts +0 -182
- package/dist/test-analysis.d.ts.map +0 -1
- package/dist/tool-interface.d.ts +0 -236
- package/dist/tool-interface.d.ts.map +0 -1
- package/dist/tool-loader.d.ts +0 -77
- package/dist/tool-loader.d.ts.map +0 -1
- package/dist/tool-validator.d.ts +0 -72
- package/dist/tool-validator.d.ts.map +0 -1
- package/dist/utils/detection.d.ts +0 -7
- package/dist/utils/detection.d.ts.map +0 -1
- package/dist/utils/finalization.d.ts +0 -17
- package/dist/utils/finalization.d.ts.map +0 -1
- package/dist/utils/project-analysis.d.ts +0 -14
- package/dist/utils/project-analysis.d.ts.map +0 -1
- package/dist/utils/quality-gates.d.ts +0 -49
- package/dist/utils/quality-gates.d.ts.map +0 -1
- package/dist/utils/spec-resolver.d.ts +0 -88
- package/dist/utils/spec-resolver.d.ts.map +0 -1
- package/dist/utils/typescript-detector.d.ts +0 -63
- package/dist/utils/typescript-detector.d.ts.map +0 -1
- package/dist/validation/spec-validation.d.ts +0 -43
- package/dist/validation/spec-validation.d.ts.map +0 -1
- package/dist/waivers-manager.d.ts +0 -167
- package/dist/waivers-manager.d.ts.map +0 -1
- package/templates/apps/tools/caws/COMPLETION_REPORT.md +0 -331
- package/templates/apps/tools/caws/MIGRATION_SUMMARY.md +0 -360
- package/templates/apps/tools/caws/README.md +0 -463
- package/templates/apps/tools/caws/TEST_STATUS.md +0 -365
- package/templates/apps/tools/caws/attest.js +0 -357
- package/templates/apps/tools/caws/ci-optimizer.js +0 -642
- package/templates/apps/tools/caws/config.ts +0 -245
- package/templates/apps/tools/caws/cross-functional.js +0 -876
- package/templates/apps/tools/caws/dashboard.js +0 -1112
- package/templates/apps/tools/caws/flake-detector.ts +0 -362
- package/templates/apps/tools/caws/gates.js +0 -198
- package/templates/apps/tools/caws/gates.ts +0 -271
- package/templates/apps/tools/caws/language-adapters.ts +0 -381
- package/templates/apps/tools/caws/language-support.d.ts +0 -367
- package/templates/apps/tools/caws/language-support.d.ts.map +0 -1
- package/templates/apps/tools/caws/language-support.js +0 -585
- package/templates/apps/tools/caws/legacy-assessment.ts +0 -408
- package/templates/apps/tools/caws/legacy-assessor.js +0 -764
- package/templates/apps/tools/caws/mutant-analyzer.js +0 -734
- package/templates/apps/tools/caws/perf-budgets.ts +0 -349
- package/templates/apps/tools/caws/prompt-lint.js.backup +0 -274
- package/templates/apps/tools/caws/property-testing.js +0 -707
- package/templates/apps/tools/caws/provenance.d.ts +0 -14
- package/templates/apps/tools/caws/provenance.d.ts.map +0 -1
- package/templates/apps/tools/caws/provenance.js +0 -132
- package/templates/apps/tools/caws/provenance.js.backup +0 -73
- package/templates/apps/tools/caws/provenance.ts +0 -211
- package/templates/apps/tools/caws/security-provenance.ts +0 -483
- package/templates/apps/tools/caws/shared/base-tool.ts +0 -281
- package/templates/apps/tools/caws/shared/config-manager.ts +0 -366
- package/templates/apps/tools/caws/shared/gate-checker.ts +0 -849
- package/templates/apps/tools/caws/shared/types.ts +0 -444
- package/templates/apps/tools/caws/shared/validator.ts +0 -305
- package/templates/apps/tools/caws/shared/waivers-manager.ts +0 -174
- package/templates/apps/tools/caws/spec-test-mapper.ts +0 -391
- package/templates/apps/tools/caws/test-quality.js +0 -578
- package/templates/apps/tools/caws/validate.js +0 -76
- package/templates/apps/tools/caws/validate.ts +0 -228
- package/templates/apps/tools/caws/waivers.js +0 -344
- /package/{templates/apps/tools/caws → dist/templates/.caws}/schemas/waivers.schema.json +0 -0
- /package/{templates/apps/tools/caws → dist/templates/.caws}/schemas/working-spec.schema.json +0 -0
- /package/{templates/apps/tools/caws → dist/templates/.caws}/templates/working-spec.template.yml +0 -0
- /package/{templates/apps/tools/caws → dist/templates/.caws/tools}/scope-guard.js +0 -0
- /package/{templates/apps/tools/caws → dist/templates/.caws}/tools-allow.json +0 -0
- /package/{templates/apps/tools/caws → dist/templates/.caws}/waivers.yml +0 -0
package/dist/tool-loader.js
CHANGED
|
@@ -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 ||
|
|
28
|
+
toolsDir: options.toolsDir || defaultToolsDir,
|
|
24
29
|
cacheEnabled: options.cacheEnabled !== false,
|
|
25
30
|
timeout: options.timeout || 10000,
|
|
26
31
|
maxTools: options.maxTools || 50,
|
package/dist/tool-validator.js
CHANGED
|
@@ -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,
|
package/dist/utils/detection.js
CHANGED
|
@@ -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, '
|
|
78
|
-
const
|
|
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 =
|
|
152
|
-
|
|
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
|
-
//
|
|
200
|
-
const
|
|
201
|
-
|
|
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
|
|
208
|
-
`python3 ${analyzerPath} --staged-only --min-confidence ${CONFIG.todoConfidenceThreshold}
|
|
209
|
-
{
|
|
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
|
-
|
|
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
|
-
|
|
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 = {};
|