@paths.design/caws-cli 9.3.2 → 10.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +71 -32
- package/dist/budget-derivation.js +221 -74
- package/dist/commands/archive.js +67 -28
- package/dist/commands/burnup.js +20 -11
- package/dist/commands/diagnose.js +34 -22
- package/dist/commands/evaluate.js +41 -15
- package/dist/commands/gates.js +149 -0
- package/dist/commands/init.js +150 -19
- package/dist/commands/iterate.js +81 -4
- package/dist/commands/parallel.js +4 -0
- package/dist/commands/plan.js +9 -19
- package/dist/commands/provenance.js +53 -17
- package/dist/commands/quality-monitor.js +64 -45
- package/dist/commands/scope.js +264 -0
- package/dist/commands/sidecar.js +74 -0
- package/dist/commands/specs.js +381 -45
- package/dist/commands/status.js +117 -9
- package/dist/commands/templates.js +0 -8
- package/dist/commands/tutorial.js +10 -9
- package/dist/commands/validate.js +70 -6
- package/dist/commands/verify-acs.js +48 -76
- package/dist/commands/waivers.js +212 -13
- package/dist/commands/worktree.js +131 -26
- package/dist/error-handler.js +2 -13
- package/dist/gates/budget-limit.js +121 -0
- package/dist/gates/feedback.js +260 -0
- package/dist/gates/format.js +179 -0
- package/dist/gates/god-object.js +117 -0
- package/dist/gates/pipeline.js +167 -0
- package/dist/gates/scope-boundary.js +93 -0
- package/dist/gates/spec-completeness.js +109 -0
- package/dist/gates/todo-detection.js +205 -0
- package/dist/index.js +157 -151
- package/dist/parallel/parallel-manager.js +3 -3
- package/dist/policy/PolicyManager.js +51 -17
- package/dist/scaffold/claude-hooks.js +24 -1
- package/dist/scaffold/git-hooks.js +45 -102
- package/dist/scaffold/index.js +4 -3
- package/dist/session/session-manager.js +105 -14
- package/dist/sidecars/index.js +33 -0
- package/dist/sidecars/listeners.js +40 -0
- package/dist/sidecars/provenance-summary.js +238 -0
- package/dist/sidecars/quality-gaps.js +258 -0
- package/dist/sidecars/schema.js +149 -0
- package/dist/sidecars/spec-drift.js +151 -0
- package/dist/sidecars/waiver-draft.js +176 -0
- package/dist/templates/.caws/schemas/policy.schema.json +112 -0
- package/dist/templates/.caws/schemas/scope.schema.json +3 -3
- package/dist/templates/.caws/schemas/waivers.schema.json +96 -20
- package/dist/templates/.caws/schemas/working-spec.schema.json +264 -57
- package/dist/templates/.caws/schemas/worktrees.schema.json +3 -1
- package/dist/templates/.caws/templates/working-spec.template.yml +10 -4
- package/dist/templates/.caws/tools/scope-guard.js +66 -15
- package/dist/templates/.claude/README.md +1 -1
- package/dist/templates/.claude/hooks/audit.sh +0 -0
- package/dist/templates/.claude/hooks/block-dangerous.sh +52 -11
- package/dist/templates/.claude/hooks/classify_command.py +592 -0
- package/dist/templates/.claude/hooks/doc-frontmatter-check.sh +173 -0
- package/dist/templates/.claude/hooks/protected-paths.sh +39 -0
- package/dist/templates/.claude/hooks/quality-check.sh +23 -10
- package/dist/templates/.claude/hooks/scope-guard.sh +136 -55
- package/dist/templates/.claude/hooks/session-caws-status.sh +2 -2
- package/dist/templates/.claude/hooks/session-log.sh +76 -3
- package/dist/templates/.claude/hooks/stop-worktree-check.sh +1 -1
- package/dist/templates/.claude/hooks/test_classify_command.py +370 -0
- package/dist/templates/.claude/hooks/test_wrapper_smoke.sh +96 -0
- package/dist/templates/.claude/hooks/worktree-guard.sh +2 -2
- package/dist/templates/.claude/hooks/worktree-write-guard.sh +97 -4
- package/dist/templates/.claude/settings.json +31 -0
- package/dist/templates/.cursor/hooks/caws-quality-check.sh +4 -4
- package/dist/templates/.cursor/hooks/caws-scope-guard.sh +1 -1
- package/dist/templates/.cursor/hooks/session-log.sh +924 -0
- package/dist/templates/.cursor/hooks.json +25 -0
- package/dist/templates/.cursor/rules/02-quality-gates.mdc +3 -5
- package/dist/templates/.cursor/rules/10-documentation-quality-standards.mdc +6 -11
- package/dist/templates/.cursor/rules/11-scope-management-waivers.mdc +14 -18
- package/dist/templates/.cursor/rules/12-implementation-completeness.mdc +4 -4
- package/dist/templates/.cursor/rules/13-language-agnostic-standards.mdc +3 -13
- package/dist/templates/.github/copilot-instructions.md +5 -5
- package/dist/templates/.idea/runConfigurations/CAWS_Evaluate.xml +1 -1
- package/dist/templates/.junie/guidelines.md +2 -2
- package/dist/templates/.vscode/settings.json +3 -1
- package/dist/templates/.windsurf/rules/caws-quality-standards.md +2 -2
- package/dist/templates/.windsurf/workflows/caws-guided-development.md +3 -3
- package/dist/templates/CLAUDE.md +77 -8
- package/dist/templates/agents.md +50 -9
- package/dist/templates/docs/README.md +8 -7
- package/dist/templates/scripts/new_feature.sh +80 -0
- package/dist/test-analysis.js +43 -30
- package/dist/tool-loader.js +1 -1
- package/dist/utils/agent-session.js +202 -0
- package/dist/utils/detection.js +8 -2
- package/dist/utils/event-log.js +584 -0
- package/dist/utils/event-renderer.js +521 -0
- package/dist/utils/finalization.js +7 -6
- package/dist/utils/gitignore-updater.js +3 -0
- package/dist/utils/lifecycle-events.js +94 -0
- package/dist/utils/quality-gates-utils.js +29 -44
- package/dist/utils/schema-validator.js +50 -0
- package/dist/utils/spec-resolver.js +93 -21
- package/dist/utils/working-state.js +530 -0
- package/dist/validation/spec-validation.js +191 -31
- package/dist/waivers-manager.js +144 -6
- package/dist/worktree/worktree-manager.js +598 -95
- package/package.json +9 -8
- package/templates/.caws/schemas/policy.schema.json +112 -0
- package/templates/.caws/schemas/scope.schema.json +3 -3
- package/templates/.caws/schemas/waivers.schema.json +96 -20
- package/templates/.caws/schemas/working-spec.schema.json +264 -57
- package/templates/.caws/schemas/worktrees.schema.json +3 -1
- package/templates/.caws/templates/working-spec.template.yml +10 -4
- package/templates/.caws/tools/scope-guard.js +66 -15
- package/templates/.claude/README.md +1 -1
- package/templates/.claude/hooks/block-dangerous.sh +52 -11
- package/templates/.claude/hooks/classify_command.py +592 -0
- package/templates/.claude/hooks/doc-frontmatter-check.sh +173 -0
- package/templates/.claude/hooks/protected-paths.sh +39 -0
- package/templates/.claude/hooks/quality-check.sh +23 -10
- package/templates/.claude/hooks/scope-guard.sh +136 -55
- package/templates/.claude/hooks/session-caws-status.sh +2 -2
- package/templates/.claude/hooks/session-log.sh +76 -3
- package/templates/.claude/hooks/stop-worktree-check.sh +1 -1
- package/templates/.claude/hooks/test_classify_command.py +370 -0
- package/templates/.claude/hooks/test_wrapper_smoke.sh +96 -0
- package/templates/.claude/hooks/worktree-guard.sh +2 -2
- package/templates/.claude/hooks/worktree-write-guard.sh +97 -4
- package/templates/.claude/settings.json +31 -0
- package/templates/.cursor/hooks/caws-quality-check.sh +4 -4
- package/templates/.cursor/hooks/caws-scope-guard.sh +1 -1
- package/templates/.cursor/hooks/session-log.sh +924 -0
- package/templates/.cursor/hooks.json +25 -0
- package/templates/.cursor/rules/02-quality-gates.mdc +3 -5
- package/templates/.cursor/rules/10-documentation-quality-standards.mdc +6 -11
- package/templates/.cursor/rules/11-scope-management-waivers.mdc +14 -18
- package/templates/.cursor/rules/12-implementation-completeness.mdc +4 -4
- package/templates/.cursor/rules/13-language-agnostic-standards.mdc +3 -13
- package/templates/.github/copilot-instructions.md +5 -5
- package/templates/.idea/runConfigurations/CAWS_Evaluate.xml +1 -1
- package/templates/.junie/guidelines.md +2 -2
- package/templates/.vscode/settings.json +3 -1
- package/templates/.windsurf/rules/caws-quality-standards.md +2 -2
- package/templates/.windsurf/workflows/caws-guided-development.md +3 -3
- package/templates/CLAUDE.md +77 -8
- package/templates/{AGENTS.md → agents.md} +50 -9
- package/templates/docs/README.md +8 -7
- package/templates/scripts/new_feature.sh +80 -0
- 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 -51
- 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 -25
- package/dist/commands/mode.d.ts.map +0 -1
- package/dist/commands/parallel.d.ts +0 -7
- package/dist/commands/parallel.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 -6
- package/dist/commands/quality-gates.d.ts.map +0 -1
- package/dist/commands/quality-gates.js +0 -444
- package/dist/commands/quality-monitor.d.ts +0 -17
- package/dist/commands/quality-monitor.d.ts.map +0 -1
- package/dist/commands/session.d.ts +0 -7
- package/dist/commands/session.d.ts.map +0 -1
- package/dist/commands/specs.d.ts +0 -77
- 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/troubleshoot.js +0 -104
- 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/commands/worktree.d.ts +0 -7
- package/dist/commands/worktree.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/lite-scope.d.ts +0 -33
- package/dist/config/lite-scope.d.ts.map +0 -1
- package/dist/config/modes.d.ts +0 -264
- package/dist/config/modes.d.ts.map +0 -1
- package/dist/constants/spec-types.d.ts +0 -93
- package/dist/constants/spec-types.d.ts.map +0 -1
- package/dist/error-handler.d.ts +0 -151
- package/dist/error-handler.d.ts.map +0 -1
- package/dist/generators/jest-config-generator.d.ts +0 -32
- package/dist/generators/jest-config-generator.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/jest-config.js +0 -242
- 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/parallel/parallel-manager.d.ts +0 -67
- package/dist/parallel/parallel-manager.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/claude-hooks.d.ts +0 -28
- package/dist/scaffold/claude-hooks.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 -38
- package/dist/scaffold/git-hooks.d.ts.map +0 -1
- package/dist/scaffold/index.d.ts +0 -17
- package/dist/scaffold/index.d.ts.map +0 -1
- package/dist/session/session-manager.d.ts +0 -94
- package/dist/session/session-manager.d.ts.map +0 -1
- package/dist/spec/SpecFileManager.d.ts +0 -146
- package/dist/spec/SpecFileManager.d.ts.map +0 -1
- package/dist/templates/.cursor/hooks/caws-tool-validation.sh +0 -121
- package/dist/templates/.github/copilot/instructions.md +0 -311
- package/dist/test-analysis.d.ts +0 -231
- 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/async-utils.d.ts +0 -73
- package/dist/utils/async-utils.d.ts.map +0 -1
- package/dist/utils/command-wrapper.d.ts +0 -66
- package/dist/utils/command-wrapper.d.ts.map +0 -1
- package/dist/utils/detection.d.ts +0 -14
- package/dist/utils/detection.d.ts.map +0 -1
- package/dist/utils/error-categories.d.ts +0 -52
- package/dist/utils/error-categories.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/git-lock.d.ts +0 -13
- package/dist/utils/git-lock.d.ts.map +0 -1
- package/dist/utils/gitignore-updater.d.ts +0 -39
- package/dist/utils/gitignore-updater.d.ts.map +0 -1
- package/dist/utils/ide-detection.d.ts +0 -89
- package/dist/utils/ide-detection.d.ts.map +0 -1
- package/dist/utils/project-analysis.d.ts +0 -34
- package/dist/utils/project-analysis.d.ts.map +0 -1
- package/dist/utils/promise-utils.d.ts +0 -30
- package/dist/utils/promise-utils.d.ts.map +0 -1
- package/dist/utils/quality-gates-utils.d.ts +0 -49
- package/dist/utils/quality-gates-utils.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/quality-gates.js +0 -402
- package/dist/utils/spec-resolver.d.ts +0 -80
- package/dist/utils/spec-resolver.d.ts.map +0 -1
- package/dist/utils/typescript-detector.d.ts +0 -66
- package/dist/utils/typescript-detector.d.ts.map +0 -1
- package/dist/utils/yaml-validation.d.ts +0 -32
- package/dist/utils/yaml-validation.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/dist/worktree/worktree-manager.d.ts +0 -54
- package/dist/worktree/worktree-manager.d.ts.map +0 -1
|
@@ -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
|
|
39
|
+
// Check for crisis response in any active spec (feature specs then legacy)
|
|
77
40
|
() => {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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', {
|
|
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,50 @@
|
|
|
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
|
+
// Order: flat repo layout (`.caws/<name>.schema.json`) wins so
|
|
38
|
+
// repos that tightened a schema in-place (e.g. CAWSFIX-03) are
|
|
39
|
+
// the authoritative source. Nested `.caws/schemas/<name>.schema.json`
|
|
40
|
+
// is the legacy layout kept for back-compat. Bundled template is
|
|
41
|
+
// the last-resort fallback used by globally-installed CLIs and
|
|
42
|
+
// projects without a local copy.
|
|
43
|
+
const flatPath = path.join(projectRoot, '.caws', schemaName);
|
|
44
|
+
if (fs.existsSync(flatPath)) return flatPath;
|
|
45
|
+
const nestedPath = path.join(projectRoot, '.caws', 'schemas', schemaName);
|
|
46
|
+
if (fs.existsSync(nestedPath)) return nestedPath;
|
|
47
|
+
return path.join(__dirname, '../../templates/.caws/schemas', schemaName);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
module.exports = { createValidator, getSchemaPath };
|
|
@@ -13,6 +13,34 @@ const chalk = require('chalk');
|
|
|
13
13
|
// Import SPEC_TYPES from constants for consistent display
|
|
14
14
|
const { SPEC_TYPES } = require('../constants/spec-types');
|
|
15
15
|
const { findProjectRoot } = require('./detection');
|
|
16
|
+
const { createValidator, getSchemaPath } = require('./schema-validator');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Validate a spec object against the working-spec schema.
|
|
20
|
+
* Throws with schema errors included in the message.
|
|
21
|
+
* @param {Object} spec - Parsed spec object
|
|
22
|
+
* @param {string} specPath - Path to the spec file (for error context)
|
|
23
|
+
*/
|
|
24
|
+
function validateSpecSchema(spec, specPath) {
|
|
25
|
+
try {
|
|
26
|
+
const schemaPath = getSchemaPath('working-spec.schema.json', getProjectRoot());
|
|
27
|
+
const validate = createValidator(schemaPath);
|
|
28
|
+
const result = validate(spec);
|
|
29
|
+
if (!result.valid) {
|
|
30
|
+
const errorDetails = result.errors
|
|
31
|
+
.map(e => ` ${e.path}: ${e.message}`)
|
|
32
|
+
.join('\n');
|
|
33
|
+
// Schema violations are warnings, not fatal — the spec is still loadable.
|
|
34
|
+
// Commands like validate will report these; other commands shouldn't be blocked.
|
|
35
|
+
if (process.env.CAWS_QUIET !== '1') {
|
|
36
|
+
console.warn(`Schema warnings for ${specPath}:\n${errorDetails}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
} catch (schemaErr) {
|
|
40
|
+
// Schema loading/compilation errors are non-fatal — warn and continue
|
|
41
|
+
console.warn('Could not validate spec schema:', schemaErr.message);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
16
44
|
|
|
17
45
|
/**
|
|
18
46
|
* Spec resolution priority:
|
|
@@ -42,10 +70,11 @@ function getProjectRoot() {
|
|
|
42
70
|
* @param {string} [options.specFile] - Explicit file path override
|
|
43
71
|
* @param {boolean} [options.warnLegacy=true] - Warn when falling back to legacy spec
|
|
44
72
|
* @param {boolean} [options.interactive=false] - Use interactive spec selection for multiple specs
|
|
73
|
+
* @param {boolean} [options.quiet=false] - Suppress informational logging for machine-readable output
|
|
45
74
|
* @returns {Promise<{path: string, type: 'feature' | 'legacy', spec: Object}>}
|
|
46
75
|
*/
|
|
47
76
|
async function resolveSpec(options = {}) {
|
|
48
|
-
const { specId, specFile, warnLegacy = true, interactive = false } = options;
|
|
77
|
+
const { specId, specFile, warnLegacy = true, interactive = false, quiet = false } = options;
|
|
49
78
|
|
|
50
79
|
// 1. Explicit file path takes highest priority
|
|
51
80
|
if (specFile) {
|
|
@@ -54,7 +83,13 @@ async function resolveSpec(options = {}) {
|
|
|
54
83
|
if (await fs.pathExists(explicitPath)) {
|
|
55
84
|
const yaml = require('js-yaml');
|
|
56
85
|
const content = await fs.readFile(explicitPath, 'utf8');
|
|
57
|
-
|
|
86
|
+
let spec;
|
|
87
|
+
try {
|
|
88
|
+
spec = yaml.load(content);
|
|
89
|
+
} catch (yamlError) {
|
|
90
|
+
throw new Error(`Invalid YAML in spec file ${explicitPath}: ${yamlError.message}`);
|
|
91
|
+
}
|
|
92
|
+
validateSpecSchema(spec, explicitPath);
|
|
58
93
|
|
|
59
94
|
return {
|
|
60
95
|
path: explicitPath,
|
|
@@ -74,8 +109,11 @@ async function resolveSpec(options = {}) {
|
|
|
74
109
|
const yaml = require('js-yaml');
|
|
75
110
|
const content = await fs.readFile(featurePath, 'utf8');
|
|
76
111
|
const spec = yaml.load(content);
|
|
112
|
+
validateSpecSchema(spec, featurePath);
|
|
77
113
|
|
|
78
|
-
|
|
114
|
+
if (!quiet) {
|
|
115
|
+
console.log(chalk.green(`Using feature-specific spec: ${specId}`));
|
|
116
|
+
}
|
|
79
117
|
|
|
80
118
|
return {
|
|
81
119
|
path: featurePath,
|
|
@@ -102,8 +140,11 @@ async function resolveSpec(options = {}) {
|
|
|
102
140
|
const yaml = require('js-yaml');
|
|
103
141
|
const content = await fs.readFile(singleSpecPath, 'utf8');
|
|
104
142
|
const spec = yaml.load(content);
|
|
143
|
+
validateSpecSchema(spec, singleSpecPath);
|
|
105
144
|
|
|
106
|
-
|
|
145
|
+
if (!quiet) {
|
|
146
|
+
console.log(chalk.blue(`Auto-detected single spec: ${singleSpecId}`));
|
|
147
|
+
}
|
|
107
148
|
|
|
108
149
|
return {
|
|
109
150
|
path: singleSpecPath,
|
|
@@ -113,19 +154,23 @@ async function resolveSpec(options = {}) {
|
|
|
113
154
|
}
|
|
114
155
|
} else if (specIds.length > 1) {
|
|
115
156
|
// Multiple specs - require explicit selection with enhanced guidance
|
|
116
|
-
|
|
157
|
+
if (!quiet) {
|
|
158
|
+
console.error(chalk.red('Multiple specs detected. Please specify which one:'));
|
|
159
|
+
}
|
|
117
160
|
|
|
118
161
|
// Show specs with details
|
|
119
162
|
const specsInfo = [];
|
|
120
163
|
for (const id of specIds) {
|
|
121
|
-
const specPath = path.join(SPECS_DIR, registry.specs[id].path);
|
|
164
|
+
const specPath = path.join(getProjectRoot(), SPECS_DIR, registry.specs[id].path);
|
|
122
165
|
try {
|
|
123
166
|
const content = await fs.readFile(specPath, 'utf8');
|
|
124
167
|
let spec;
|
|
125
168
|
try {
|
|
126
169
|
spec = yaml.load(content);
|
|
127
170
|
} catch (yamlError) {
|
|
128
|
-
|
|
171
|
+
if (!quiet) {
|
|
172
|
+
console.log(chalk.yellow(` - ${id} (YAML syntax error: ${yamlError.message})`));
|
|
173
|
+
}
|
|
129
174
|
specsInfo.push({ id, type: 'unknown', status: 'unknown', title: 'YAML error' });
|
|
130
175
|
continue;
|
|
131
176
|
}
|
|
@@ -135,14 +180,18 @@ async function resolveSpec(options = {}) {
|
|
|
135
180
|
status === 'active' ? chalk.green : status === 'completed' ? chalk.blue : chalk.yellow;
|
|
136
181
|
const typeColor = SPEC_TYPES[type] ? SPEC_TYPES[type].color : chalk.white;
|
|
137
182
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
183
|
+
if (!quiet) {
|
|
184
|
+
console.log(
|
|
185
|
+
chalk.yellow(
|
|
186
|
+
` - ${id} ${typeColor(`(${type})`)} ${statusColor(`[${status}]`)} - ${spec.title || 'Untitled'}`
|
|
187
|
+
)
|
|
188
|
+
);
|
|
189
|
+
}
|
|
143
190
|
specsInfo.push({ id, type, status, title: spec.title || 'Untitled' });
|
|
144
191
|
} catch (error) {
|
|
145
|
-
|
|
192
|
+
if (!quiet) {
|
|
193
|
+
console.log(chalk.yellow(` - ${id} (error loading details: ${error.message})`));
|
|
194
|
+
}
|
|
146
195
|
specsInfo.push({ id, type: 'unknown', status: 'unknown', title: 'Error loading' });
|
|
147
196
|
}
|
|
148
197
|
}
|
|
@@ -157,14 +206,17 @@ async function resolveSpec(options = {}) {
|
|
|
157
206
|
specId: selectedSpecId,
|
|
158
207
|
warnLegacy,
|
|
159
208
|
interactive: false, // Prevent infinite recursion
|
|
209
|
+
quiet,
|
|
160
210
|
});
|
|
161
211
|
} catch (error) {
|
|
162
212
|
throw new Error(`Interactive selection failed: ${error.message}`);
|
|
163
213
|
}
|
|
164
214
|
}
|
|
165
215
|
|
|
166
|
-
|
|
167
|
-
|
|
216
|
+
if (!quiet) {
|
|
217
|
+
console.log(chalk.blue('\n Usage: caws <command> --spec-id <spec-id>'));
|
|
218
|
+
console.log(chalk.gray(` Example: caws validate --spec-id ${specIds[0]}`));
|
|
219
|
+
}
|
|
168
220
|
|
|
169
221
|
// Suggest most likely spec (active first, then by type priority)
|
|
170
222
|
const priorityOrder = { active: 0, draft: 1, completed: 2 };
|
|
@@ -182,11 +234,15 @@ async function resolveSpec(options = {}) {
|
|
|
182
234
|
return aTypePriority - bTypePriority;
|
|
183
235
|
});
|
|
184
236
|
|
|
185
|
-
|
|
186
|
-
|
|
237
|
+
if (!quiet) {
|
|
238
|
+
console.log(chalk.green('\nQuick suggestion:'));
|
|
239
|
+
console.log(chalk.gray(` Try: caws <command> --spec-id ${sortedSpecs[0]}`));
|
|
240
|
+
}
|
|
187
241
|
|
|
188
242
|
// Interactive mode suggestion
|
|
189
|
-
|
|
243
|
+
if (!quiet) {
|
|
244
|
+
console.log(chalk.blue('\n Interactive mode: caws <command> --interactive-spec-selection'));
|
|
245
|
+
}
|
|
190
246
|
|
|
191
247
|
throw new Error('Spec ID required when multiple specs exist');
|
|
192
248
|
}
|
|
@@ -198,8 +254,9 @@ async function resolveSpec(options = {}) {
|
|
|
198
254
|
const yaml = require('js-yaml');
|
|
199
255
|
const content = await fs.readFile(legacyPath, 'utf8');
|
|
200
256
|
const spec = yaml.load(content);
|
|
257
|
+
validateSpecSchema(spec, legacyPath);
|
|
201
258
|
|
|
202
|
-
if (warnLegacy) {
|
|
259
|
+
if (warnLegacy && !quiet) {
|
|
203
260
|
console.log(chalk.yellow('Using legacy working-spec.yaml'));
|
|
204
261
|
console.log(chalk.gray(' For multi-agent workflows, use feature-specific specs:'));
|
|
205
262
|
console.log(chalk.blue(' caws specs create <feature-id>'));
|
|
@@ -236,7 +293,19 @@ async function loadSpecsRegistry() {
|
|
|
236
293
|
|
|
237
294
|
try {
|
|
238
295
|
const registry = await fs.readJson(registryPath);
|
|
239
|
-
|
|
296
|
+
const sanitizedSpecs = {};
|
|
297
|
+
|
|
298
|
+
for (const [id, entry] of Object.entries(registry.specs || {})) {
|
|
299
|
+
const specPath = path.join(getProjectRoot(), SPECS_DIR, entry.path);
|
|
300
|
+
if (await fs.pathExists(specPath)) {
|
|
301
|
+
sanitizedSpecs[id] = entry;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return {
|
|
306
|
+
...registry,
|
|
307
|
+
specs: sanitizedSpecs,
|
|
308
|
+
};
|
|
240
309
|
} catch (error) {
|
|
241
310
|
return {
|
|
242
311
|
version: '1.0.0',
|
|
@@ -379,7 +448,10 @@ async function checkScopeConflicts(specIds) {
|
|
|
379
448
|
|
|
380
449
|
// Load all specs and their scopes
|
|
381
450
|
for (const id of specIds) {
|
|
382
|
-
const
|
|
451
|
+
const entry = registry.specs[id];
|
|
452
|
+
if (!entry) continue;
|
|
453
|
+
|
|
454
|
+
const specPath = path.join(getProjectRoot(), SPECS_DIR, entry.path);
|
|
383
455
|
|
|
384
456
|
try {
|
|
385
457
|
const content = await fs.readFile(specPath, 'utf8');
|