@paths.design/caws-cli 9.3.2 → 10.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -27
- 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 +27 -15
- package/dist/commands/gates.js +122 -0
- package/dist/commands/init.js +143 -15
- package/dist/commands/iterate.js +77 -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/sidecar.js +71 -0
- package/dist/commands/specs.js +233 -44
- package/dist/commands/status.js +113 -9
- package/dist/commands/tutorial.js +10 -9
- package/dist/commands/validate.js +49 -6
- package/dist/commands/verify-acs.js +35 -78
- package/dist/commands/waivers.js +69 -12
- package/dist/commands/worktree.js +50 -25
- package/dist/error-handler.js +2 -13
- package/dist/gates/budget-limit.js +116 -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 +102 -0
- package/dist/gates/todo-detection.js +205 -0
- package/dist/index.js +130 -151
- package/dist/parallel/parallel-manager.js +3 -3
- package/dist/policy/PolicyManager.js +42 -10
- 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 +71 -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 +50 -0
- package/dist/templates/.caws/schemas/waivers.schema.json +30 -24
- package/dist/templates/.caws/schemas/working-spec.schema.json +51 -8
- package/dist/templates/.caws/schemas/worktrees.schema.json +3 -1
- package/dist/templates/.caws/templates/working-spec.template.yml +7 -3
- 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/quality-check.sh +23 -10
- package/dist/templates/.claude/hooks/scope-guard.sh +34 -32
- 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 +1 -1
- package/dist/templates/.claude/settings.json +26 -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 +43 -8
- package/dist/templates/agents.md +29 -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/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 +42 -0
- package/dist/utils/spec-resolver.js +93 -21
- package/dist/utils/working-state.js +505 -0
- package/dist/validation/spec-validation.js +92 -22
- package/dist/waivers-manager.js +60 -6
- package/dist/worktree/worktree-manager.js +390 -93
- package/package.json +6 -6
- package/templates/.caws/schemas/policy.schema.json +50 -0
- package/templates/.caws/schemas/waivers.schema.json +30 -24
- package/templates/.caws/schemas/working-spec.schema.json +51 -8
- package/templates/.caws/schemas/worktrees.schema.json +3 -1
- package/templates/.caws/templates/working-spec.template.yml +7 -3
- 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/quality-check.sh +23 -10
- package/templates/.claude/hooks/scope-guard.sh +34 -32
- 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 +1 -1
- package/templates/.claude/settings.json +26 -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 +43 -8
- package/templates/{AGENTS.md → agents.md} +29 -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
package/dist/commands/status.js
CHANGED
|
@@ -11,6 +11,8 @@ const chalk = require('chalk');
|
|
|
11
11
|
// child_process removed - execSync no longer used directly
|
|
12
12
|
const { safeAsync, outputResult } = require('../error-handler');
|
|
13
13
|
const { parallel } = require('../utils/async-utils');
|
|
14
|
+
const { resolveSpec } = require('../utils/spec-resolver');
|
|
15
|
+
const { loadState } = require('../utils/working-state');
|
|
14
16
|
|
|
15
17
|
/**
|
|
16
18
|
* Load working specification (legacy single file approach)
|
|
@@ -30,6 +32,41 @@ async function loadWorkingSpec(specPath = '.caws/working-spec.yaml') {
|
|
|
30
32
|
}
|
|
31
33
|
}
|
|
32
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Resolve the primary spec for status display when explicitly targeted
|
|
37
|
+
* or when the resolver can identify a single canonical spec.
|
|
38
|
+
* Returns null when no spec can be auto-selected.
|
|
39
|
+
* @param {Object} options
|
|
40
|
+
* @returns {Promise<Object|null>}
|
|
41
|
+
*/
|
|
42
|
+
async function loadResolvedStatusSpec(options = {}) {
|
|
43
|
+
try {
|
|
44
|
+
const resolved = await resolveSpec({
|
|
45
|
+
specId: options.specId,
|
|
46
|
+
specFile: options.spec,
|
|
47
|
+
warnLegacy: false,
|
|
48
|
+
interactive: false,
|
|
49
|
+
quiet: Boolean(options.json),
|
|
50
|
+
});
|
|
51
|
+
return {
|
|
52
|
+
...resolved,
|
|
53
|
+
selected: true,
|
|
54
|
+
};
|
|
55
|
+
} catch (error) {
|
|
56
|
+
if (
|
|
57
|
+
error.message === 'Spec ID required when multiple specs exist' &&
|
|
58
|
+
!options.specId &&
|
|
59
|
+
!options.spec
|
|
60
|
+
) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
if (error.message.includes('No CAWS spec found')) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
33
70
|
/**
|
|
34
71
|
* Load specs from the new multi-spec system
|
|
35
72
|
* @returns {Promise<Array>} Array of spec objects
|
|
@@ -317,7 +354,7 @@ function getTimeSince(timestamp) {
|
|
|
317
354
|
* @param {Object} data - Status data
|
|
318
355
|
*/
|
|
319
356
|
function displayStatus(data) {
|
|
320
|
-
const { spec, hooks, provenance, waivers, gates } = data;
|
|
357
|
+
const { spec, specSelection, hooks, provenance, waivers, gates } = data;
|
|
321
358
|
|
|
322
359
|
console.log(chalk.bold.cyan('\nCAWS Project Status'));
|
|
323
360
|
console.log(chalk.cyan('==============================================\n'));
|
|
@@ -327,6 +364,9 @@ function displayStatus(data) {
|
|
|
327
364
|
console.log(chalk.green('Working Spec'));
|
|
328
365
|
console.log(chalk.gray(` ID: ${spec.id} | Tier: ${spec.risk_tier} | Mode: ${spec.mode}`));
|
|
329
366
|
console.log(chalk.gray(` Title: ${spec.title}`));
|
|
367
|
+
if (specSelection?.specType) {
|
|
368
|
+
console.log(chalk.gray(` Source: ${specSelection.specType} (${specSelection.specPath})`));
|
|
369
|
+
}
|
|
330
370
|
} else {
|
|
331
371
|
console.log(chalk.red('Working Spec'));
|
|
332
372
|
console.log(chalk.gray(' No working spec found'));
|
|
@@ -335,6 +375,47 @@ function displayStatus(data) {
|
|
|
335
375
|
|
|
336
376
|
console.log('');
|
|
337
377
|
|
|
378
|
+
// Working State
|
|
379
|
+
if (spec && spec.id) {
|
|
380
|
+
let ws = null;
|
|
381
|
+
try { ws = loadState(spec.id); } catch { /* non-fatal */ }
|
|
382
|
+
if (ws && ws.phase !== 'not-started') {
|
|
383
|
+
const phaseLabels = {
|
|
384
|
+
'not-started': 'Not Started',
|
|
385
|
+
'spec-authoring': 'Spec Authoring',
|
|
386
|
+
'implementation': 'Implementation',
|
|
387
|
+
'verification': 'Verification',
|
|
388
|
+
'complete': 'Complete',
|
|
389
|
+
};
|
|
390
|
+
console.log(chalk.green('Working State'));
|
|
391
|
+
console.log(chalk.gray(` Phase: ${phaseLabels[ws.phase] || ws.phase}`));
|
|
392
|
+
if (ws.validation) {
|
|
393
|
+
const vIcon = ws.validation.passed ? chalk.green('pass') : chalk.red('fail');
|
|
394
|
+
console.log(chalk.gray(` Validation: ${vIcon}${ws.validation.grade ? ` (Grade ${ws.validation.grade})` : ''}`));
|
|
395
|
+
}
|
|
396
|
+
if (ws.evaluation) {
|
|
397
|
+
console.log(chalk.gray(` Evaluation: ${ws.evaluation.percentage}% (Grade ${ws.evaluation.grade})`));
|
|
398
|
+
}
|
|
399
|
+
if (ws.gates) {
|
|
400
|
+
const gIcon = ws.gates.passed ? chalk.green('pass') : chalk.red('blocked');
|
|
401
|
+
const s = ws.gates.summary;
|
|
402
|
+
console.log(chalk.gray(` Gates: ${gIcon} (${s.passed} pass, ${s.blocked} blocked, ${s.warned} warned)`));
|
|
403
|
+
}
|
|
404
|
+
if (ws.acceptance_criteria) {
|
|
405
|
+
const ac = ws.acceptance_criteria;
|
|
406
|
+
console.log(chalk.gray(` ACs: ${ac.pass}/${ac.total} pass, ${ac.fail} fail, ${ac.unchecked} unchecked`));
|
|
407
|
+
}
|
|
408
|
+
if (ws.files_touched && ws.files_touched.length > 0) {
|
|
409
|
+
console.log(chalk.gray(` Files touched: ${ws.files_touched.length}`));
|
|
410
|
+
}
|
|
411
|
+
if (ws.blockers && ws.blockers.length > 0) {
|
|
412
|
+
console.log(chalk.red(` Blockers: ${ws.blockers.length}`));
|
|
413
|
+
ws.blockers.forEach(b => console.log(chalk.red(` - ${b.message}`)));
|
|
414
|
+
}
|
|
415
|
+
console.log('');
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
338
419
|
// Git Hooks Status
|
|
339
420
|
if (hooks.installed) {
|
|
340
421
|
console.log(chalk.green(`Git Hooks`));
|
|
@@ -894,14 +975,23 @@ async function statusCommand(options = {}) {
|
|
|
894
975
|
const currentMode = await modes.getCurrentMode();
|
|
895
976
|
|
|
896
977
|
// Load all status data in parallel for better performance
|
|
897
|
-
const [
|
|
898
|
-
(
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
978
|
+
const [resolvedSpec, specs, hooks, provenance, waivers, gates] = await parallel([
|
|
979
|
+
loadResolvedStatusSpec(options),
|
|
980
|
+
loadSpecsFromMultiSpec(),
|
|
981
|
+
checkGitHooks(),
|
|
982
|
+
loadProvenanceChain(),
|
|
983
|
+
loadWaiverStatus(),
|
|
984
|
+
checkQualityGates(),
|
|
904
985
|
]);
|
|
986
|
+
const spec = resolvedSpec?.spec || null;
|
|
987
|
+
const specSelection = resolvedSpec
|
|
988
|
+
? {
|
|
989
|
+
specPath: resolvedSpec.path
|
|
990
|
+
? path.relative(process.cwd(), resolvedSpec.path)
|
|
991
|
+
: null,
|
|
992
|
+
specType: resolvedSpec.type,
|
|
993
|
+
}
|
|
994
|
+
: null;
|
|
905
995
|
|
|
906
996
|
// Display status (visual mode if requested)
|
|
907
997
|
if (options.visual || options.json) {
|
|
@@ -932,6 +1022,8 @@ async function statusCommand(options = {}) {
|
|
|
932
1022
|
title: spec.title,
|
|
933
1023
|
riskTier: spec.risk_tier,
|
|
934
1024
|
mode: spec.mode,
|
|
1025
|
+
specType: specSelection?.specType || null,
|
|
1026
|
+
specPath: specSelection?.specPath || null,
|
|
935
1027
|
acceptanceCriteria: spec.acceptance_criteria?.length || 0,
|
|
936
1028
|
completedCriteria:
|
|
937
1029
|
spec.acceptance_criteria?.filter((c) => c.completed).length || 0,
|
|
@@ -960,8 +1052,10 @@ async function statusCommand(options = {}) {
|
|
|
960
1052
|
passed: gates.passed,
|
|
961
1053
|
message: gates.message,
|
|
962
1054
|
},
|
|
1055
|
+
workingState: spec && spec.id ? (loadState(spec.id) || null) : null,
|
|
963
1056
|
overallProgress: calculateOverallProgress({
|
|
964
1057
|
spec,
|
|
1058
|
+
specSelection,
|
|
965
1059
|
specs,
|
|
966
1060
|
hooks,
|
|
967
1061
|
provenance,
|
|
@@ -971,6 +1065,7 @@ async function statusCommand(options = {}) {
|
|
|
971
1065
|
};
|
|
972
1066
|
|
|
973
1067
|
console.log(JSON.stringify(result, null, 2));
|
|
1068
|
+
return result;
|
|
974
1069
|
} else {
|
|
975
1070
|
// Visual output
|
|
976
1071
|
await displayVisualStatus(
|
|
@@ -989,6 +1084,7 @@ async function statusCommand(options = {}) {
|
|
|
989
1084
|
// Original text-based output
|
|
990
1085
|
displayStatus({
|
|
991
1086
|
spec,
|
|
1087
|
+
specSelection,
|
|
992
1088
|
hooks,
|
|
993
1089
|
provenance,
|
|
994
1090
|
waivers,
|
|
@@ -996,6 +1092,13 @@ async function statusCommand(options = {}) {
|
|
|
996
1092
|
});
|
|
997
1093
|
}
|
|
998
1094
|
|
|
1095
|
+
if (options.json) {
|
|
1096
|
+
return {
|
|
1097
|
+
command: 'status',
|
|
1098
|
+
mode: 'json',
|
|
1099
|
+
};
|
|
1100
|
+
}
|
|
1101
|
+
|
|
999
1102
|
const result = outputResult({
|
|
1000
1103
|
command: 'status',
|
|
1001
1104
|
mode: options.visual ? 'visual' : options.json ? 'json' : 'text',
|
|
@@ -1027,13 +1130,14 @@ async function statusCommand(options = {}) {
|
|
|
1027
1130
|
return result;
|
|
1028
1131
|
},
|
|
1029
1132
|
'status check',
|
|
1030
|
-
|
|
1133
|
+
!options.json
|
|
1031
1134
|
);
|
|
1032
1135
|
}
|
|
1033
1136
|
|
|
1034
1137
|
module.exports = {
|
|
1035
1138
|
statusCommand,
|
|
1036
1139
|
loadWorkingSpec,
|
|
1140
|
+
loadResolvedStatusSpec,
|
|
1037
1141
|
checkGitHooks,
|
|
1038
1142
|
loadProvenanceChain,
|
|
1039
1143
|
loadWaiverStatus,
|
|
@@ -115,9 +115,10 @@ Follow this proven TDD workflow:
|
|
|
115
115
|
7. **Archive**: Complete and archive finished work
|
|
116
116
|
|
|
117
117
|
Key commands:
|
|
118
|
-
- \`caws
|
|
119
|
-
- \`caws
|
|
120
|
-
- \`caws
|
|
118
|
+
- \`caws validate --spec-id FEAT-001\` - Validate current work
|
|
119
|
+
- \`caws status --spec-id FEAT-001\` - Check progress
|
|
120
|
+
- \`caws evaluate --spec-id FEAT-001\` - Quality/readiness scoring
|
|
121
|
+
- \`caws burnup --spec-id FEAT-001\` - Budget burn-up report
|
|
121
122
|
- \`caws archive <change-id>\` - Complete work
|
|
122
123
|
`,
|
|
123
124
|
action: 'Try: caws status --visual',
|
|
@@ -235,18 +236,18 @@ Let's get you started!
|
|
|
235
236
|
First, ensure CAWS is properly initialized:
|
|
236
237
|
|
|
237
238
|
1. Initialize CAWS: \`caws init .\`
|
|
238
|
-
2.
|
|
239
|
+
2. Create a feature spec: \`caws specs create FEAT-001 --type feature --title "My Feature"\`
|
|
239
240
|
3. Set up git hooks: \`caws hooks install\`
|
|
240
241
|
4. Initialize provenance: \`caws provenance init\`
|
|
241
242
|
|
|
242
243
|
For existing projects, use: \`caws scaffold\`
|
|
243
244
|
|
|
244
|
-
|
|
245
|
-
-
|
|
246
|
-
-
|
|
247
|
-
-
|
|
245
|
+
All commands support \`--spec-id\` to target a specific feature spec:
|
|
246
|
+
- \`caws validate --spec-id FEAT-001\`
|
|
247
|
+
- \`caws status --spec-id FEAT-001\`
|
|
248
|
+
- \`caws evaluate --spec-id FEAT-001\`
|
|
248
249
|
`,
|
|
249
|
-
action: 'Try: caws
|
|
250
|
+
action: 'Try: caws specs list',
|
|
250
251
|
verify: 'mode_setup',
|
|
251
252
|
},
|
|
252
253
|
{
|
|
@@ -14,7 +14,13 @@ const {
|
|
|
14
14
|
} = require('../validation/spec-validation');
|
|
15
15
|
|
|
16
16
|
// Import spec resolution system
|
|
17
|
-
const {
|
|
17
|
+
const {
|
|
18
|
+
resolveSpec,
|
|
19
|
+
suggestMigration,
|
|
20
|
+
loadSpecsRegistry,
|
|
21
|
+
} = require('../utils/spec-resolver');
|
|
22
|
+
const { recordValidation } = require('../utils/working-state');
|
|
23
|
+
const { lifecycle, EVENTS } = require('../utils/lifecycle-events');
|
|
18
24
|
|
|
19
25
|
/**
|
|
20
26
|
* Validate command handler
|
|
@@ -75,12 +81,14 @@ async function validateCommand(specFile, options = {}) {
|
|
|
75
81
|
// Check scope conflicts (if multiple specs exist)
|
|
76
82
|
const { checkMultiSpecStatus } = require('../utils/spec-resolver');
|
|
77
83
|
const multiSpecStatus = await checkMultiSpecStatus();
|
|
84
|
+
const registry =
|
|
85
|
+
multiSpecStatus.registry ||
|
|
86
|
+
await loadSpecsRegistry();
|
|
87
|
+
const specIds = Object.keys(registry.specs || {});
|
|
78
88
|
|
|
79
|
-
if (
|
|
89
|
+
if (specIds.length > 1) {
|
|
80
90
|
const { checkScopeConflicts } = require('../utils/spec-resolver');
|
|
81
|
-
const conflicts = await checkScopeConflicts(
|
|
82
|
-
Object.keys(multiSpecStatus.registry?.specs || {})
|
|
83
|
-
);
|
|
91
|
+
const conflicts = await checkScopeConflicts(specIds);
|
|
84
92
|
|
|
85
93
|
if (conflicts.length > 0) {
|
|
86
94
|
const myConflicts = conflicts.filter((c) => c.spec1 === spec.id || c.spec2 === spec.id);
|
|
@@ -135,6 +143,40 @@ async function validateCommand(specFile, options = {}) {
|
|
|
135
143
|
|
|
136
144
|
const finalResult = enhancedValidation;
|
|
137
145
|
|
|
146
|
+
// Record to working state
|
|
147
|
+
try {
|
|
148
|
+
const grade = finalResult.complianceScore !== undefined
|
|
149
|
+
? getComplianceGrade(finalResult.complianceScore)
|
|
150
|
+
: null;
|
|
151
|
+
recordValidation(spec.id, {
|
|
152
|
+
passed: finalResult.valid,
|
|
153
|
+
compliance_score: finalResult.complianceScore ?? null,
|
|
154
|
+
grade,
|
|
155
|
+
error_count: (finalResult.errors || []).length,
|
|
156
|
+
warning_count: (finalResult.warnings || []).length,
|
|
157
|
+
});
|
|
158
|
+
} catch { /* non-fatal */ }
|
|
159
|
+
|
|
160
|
+
// Emit lifecycle event
|
|
161
|
+
try {
|
|
162
|
+
const grade = finalResult.complianceScore !== undefined
|
|
163
|
+
? getComplianceGrade(finalResult.complianceScore)
|
|
164
|
+
: null;
|
|
165
|
+
if (finalResult.valid) {
|
|
166
|
+
lifecycle.emit(EVENTS.VALIDATION_PASSED, {
|
|
167
|
+
specId: spec.id, grade, complianceScore: finalResult.complianceScore,
|
|
168
|
+
timestamp: new Date().toISOString(),
|
|
169
|
+
});
|
|
170
|
+
} else {
|
|
171
|
+
lifecycle.emit(EVENTS.VALIDATION_FAILED, {
|
|
172
|
+
specId: spec.id, errors: finalResult.errors || [],
|
|
173
|
+
errorCount: (finalResult.errors || []).length,
|
|
174
|
+
warningCount: (finalResult.warnings || []).length,
|
|
175
|
+
timestamp: new Date().toISOString(),
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
} catch { /* non-fatal */ }
|
|
179
|
+
|
|
138
180
|
// Format output based on requested format
|
|
139
181
|
if (options.format === 'json') {
|
|
140
182
|
// Structured JSON output matching CAWSValidationResult
|
|
@@ -236,7 +278,8 @@ async function validateCommand(specFile, options = {}) {
|
|
|
236
278
|
if (error.message === 'Spec ID required when multiple specs exist' && !options.specId) {
|
|
237
279
|
const { checkMultiSpecStatus } = require('../utils/spec-resolver');
|
|
238
280
|
const status = await checkMultiSpecStatus();
|
|
239
|
-
const
|
|
281
|
+
const registry = status.registry || await loadSpecsRegistry();
|
|
282
|
+
const specIds = Object.keys(registry.specs || {});
|
|
240
283
|
|
|
241
284
|
if (specIds.length === 0) {
|
|
242
285
|
console.error(chalk.red('No specs found in registry'));
|
|
@@ -7,13 +7,11 @@
|
|
|
7
7
|
|
|
8
8
|
const fs = require('fs-extra');
|
|
9
9
|
const path = require('path');
|
|
10
|
-
const yaml = require('js-yaml');
|
|
11
10
|
const chalk = require('chalk');
|
|
12
11
|
const { execFileSync } = require('child_process');
|
|
13
12
|
const { findProjectRoot } = require('../utils/detection');
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
const TERMINAL_STATUSES = new Set(['completed', 'closed', 'archived']);
|
|
13
|
+
const { resolveSpec } = require('../utils/spec-resolver');
|
|
14
|
+
const { recordACVerification } = require('../utils/working-state');
|
|
17
15
|
|
|
18
16
|
/**
|
|
19
17
|
* Detect the project's test runner from config files.
|
|
@@ -318,55 +316,6 @@ function verifySpec(spec, projectRoot, options = {}) {
|
|
|
318
316
|
};
|
|
319
317
|
}
|
|
320
318
|
|
|
321
|
-
/**
|
|
322
|
-
* Load all active specs from .caws/specs/
|
|
323
|
-
* @param {string} projectRoot
|
|
324
|
-
* @param {string} [targetSpecId] - If provided, only load this spec
|
|
325
|
-
* @returns {Array<{path: string, spec: Object}>}
|
|
326
|
-
*/
|
|
327
|
-
function loadSpecs(projectRoot, targetSpecId) {
|
|
328
|
-
const specs = [];
|
|
329
|
-
const specsDir = path.join(projectRoot, SPECS_DIR);
|
|
330
|
-
|
|
331
|
-
if (targetSpecId) {
|
|
332
|
-
const specPath = path.join(specsDir, `${targetSpecId}.yaml`);
|
|
333
|
-
if (!fs.existsSync(specPath)) {
|
|
334
|
-
const ymlPath = path.join(specsDir, `${targetSpecId}.yml`);
|
|
335
|
-
if (!fs.existsSync(ymlPath)) {
|
|
336
|
-
throw new Error(`Spec '${targetSpecId}' not found at ${specPath}`);
|
|
337
|
-
}
|
|
338
|
-
specs.push({ path: ymlPath, spec: yaml.load(fs.readFileSync(ymlPath, 'utf8')) });
|
|
339
|
-
} else {
|
|
340
|
-
specs.push({ path: specPath, spec: yaml.load(fs.readFileSync(specPath, 'utf8')) });
|
|
341
|
-
}
|
|
342
|
-
return specs;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
// Also check working-spec.yaml
|
|
346
|
-
const workingSpec = path.join(projectRoot, '.caws/working-spec.yaml');
|
|
347
|
-
if (fs.existsSync(workingSpec)) {
|
|
348
|
-
try {
|
|
349
|
-
const s = yaml.load(fs.readFileSync(workingSpec, 'utf8'));
|
|
350
|
-
if (s && !TERMINAL_STATUSES.has(s.status)) {
|
|
351
|
-
specs.push({ path: workingSpec, spec: s });
|
|
352
|
-
}
|
|
353
|
-
} catch (_) { /* ignore unreadable config */ }
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
if (fs.existsSync(specsDir)) {
|
|
357
|
-
for (const f of fs.readdirSync(specsDir).filter(f => f.endsWith('.yaml') || f.endsWith('.yml'))) {
|
|
358
|
-
try {
|
|
359
|
-
const s = yaml.load(fs.readFileSync(path.join(specsDir, f), 'utf8'));
|
|
360
|
-
if (s && !TERMINAL_STATUSES.has(s.status)) {
|
|
361
|
-
specs.push({ path: path.join(specsDir, f), spec: s });
|
|
362
|
-
}
|
|
363
|
-
} catch (_) { /* ignore unreadable config */ }
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
return specs;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
319
|
/**
|
|
371
320
|
* Main command handler
|
|
372
321
|
*/
|
|
@@ -379,35 +328,39 @@ async function verifyAcsCommand(options = {}) {
|
|
|
379
328
|
process.exit(1);
|
|
380
329
|
}
|
|
381
330
|
|
|
382
|
-
const
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
return;
|
|
387
|
-
}
|
|
331
|
+
const resolved = await resolveSpec({
|
|
332
|
+
specId: options.specId,
|
|
333
|
+
warnLegacy: false,
|
|
334
|
+
});
|
|
388
335
|
|
|
389
336
|
const allResults = [];
|
|
390
337
|
let totalAcs = 0, totalMechanical = 0, totalPass = 0, totalFail = 0, totalUnchecked = 0;
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
totalAcs++;
|
|
405
|
-
if (r.status === 'PASS') { totalPass++; totalMechanical++; }
|
|
406
|
-
else if (r.status === 'FAIL') { totalFail++; totalMechanical++; }
|
|
407
|
-
else { totalUnchecked++; }
|
|
408
|
-
}
|
|
338
|
+
const result = verifySpec(resolved.spec, projectRoot, {
|
|
339
|
+
run: options.run || false,
|
|
340
|
+
runner: options.runner,
|
|
341
|
+
});
|
|
342
|
+
result.path = resolved.path;
|
|
343
|
+
result.type = resolved.type;
|
|
344
|
+
allResults.push(result);
|
|
345
|
+
|
|
346
|
+
for (const r of result.results) {
|
|
347
|
+
totalAcs++;
|
|
348
|
+
if (r.status === 'PASS') { totalPass++; totalMechanical++; }
|
|
349
|
+
else if (r.status === 'FAIL') { totalFail++; totalMechanical++; }
|
|
350
|
+
else { totalUnchecked++; }
|
|
409
351
|
}
|
|
410
352
|
|
|
353
|
+
// Record to working state
|
|
354
|
+
try {
|
|
355
|
+
recordACVerification(resolved.spec.id, {
|
|
356
|
+
total: totalAcs,
|
|
357
|
+
pass: totalPass,
|
|
358
|
+
fail: totalFail,
|
|
359
|
+
unchecked: totalUnchecked,
|
|
360
|
+
results: result.results,
|
|
361
|
+
}, projectRoot);
|
|
362
|
+
} catch { /* non-fatal */ }
|
|
363
|
+
|
|
411
364
|
// Output
|
|
412
365
|
if (options.format === 'json') {
|
|
413
366
|
console.log(JSON.stringify({
|
|
@@ -421,7 +374,11 @@ async function verifyAcsCommand(options = {}) {
|
|
|
421
374
|
// Table output
|
|
422
375
|
for (const result of allResults) {
|
|
423
376
|
console.log(chalk.cyan(`\n## ${result.specId} — ${result.title}`));
|
|
424
|
-
console.log(
|
|
377
|
+
console.log(
|
|
378
|
+
chalk.gray(
|
|
379
|
+
` Test runner: ${result.runner} | Mode: ${options.run ? 'run' : 'collect-only'} | Spec: ${result.type} -> ${result.path}`
|
|
380
|
+
)
|
|
381
|
+
);
|
|
425
382
|
console.log();
|
|
426
383
|
|
|
427
384
|
const widths = { id: 8, desc: 40, method: 14, status: 10 };
|
package/dist/commands/waivers.js
CHANGED
|
@@ -13,6 +13,7 @@ const path = require('path');
|
|
|
13
13
|
const yaml = require('js-yaml');
|
|
14
14
|
const chalk = require('chalk');
|
|
15
15
|
const { initializeGlobalSetup } = require('../config');
|
|
16
|
+
const { getAgentSessionId } = require('../utils/agent-session');
|
|
16
17
|
const WaiversManager = require('../waivers-manager');
|
|
17
18
|
const { commandWrapper, Output } = require('../utils/command-wrapper');
|
|
18
19
|
|
|
@@ -24,16 +25,12 @@ const WAIVER_DIR = '.caws/waivers';
|
|
|
24
25
|
* within the check-*.mjs files.
|
|
25
26
|
*/
|
|
26
27
|
const VALID_GATES = [
|
|
27
|
-
'
|
|
28
|
-
'
|
|
29
|
-
'
|
|
30
|
-
'
|
|
31
|
-
'
|
|
32
|
-
'
|
|
33
|
-
'simplification',
|
|
34
|
-
'hidden-todo',
|
|
35
|
-
'documentation',
|
|
36
|
-
'*',
|
|
28
|
+
'budget_limit',
|
|
29
|
+
'spec_completeness',
|
|
30
|
+
'scope_boundary',
|
|
31
|
+
'god_object',
|
|
32
|
+
'todo_detection',
|
|
33
|
+
'*', // wildcard — waiver applies to all gates
|
|
37
34
|
];
|
|
38
35
|
|
|
39
36
|
/**
|
|
@@ -49,7 +46,7 @@ async function waiversCommand(subcommand = 'list', options = {}) {
|
|
|
49
46
|
const setup = initializeGlobalSetup();
|
|
50
47
|
|
|
51
48
|
if (setup.hasWorkingSpec) {
|
|
52
|
-
Output.success(`Detected ${setup.
|
|
49
|
+
Output.success(`Detected ${setup.type} CAWS setup`, {
|
|
53
50
|
capabilities: setup.capabilities,
|
|
54
51
|
});
|
|
55
52
|
}
|
|
@@ -138,6 +135,24 @@ async function createWaiver(options) {
|
|
|
138
135
|
process.exit(1);
|
|
139
136
|
}
|
|
140
137
|
|
|
138
|
+
// Self-approval prevention: creator cannot be approver
|
|
139
|
+
// Uses strict equality — the previous .includes() check was an asymmetric
|
|
140
|
+
// substring match that produced false positives (blocking legitimate approvers
|
|
141
|
+
// whose name happened to contain the session ID) while missing the reverse
|
|
142
|
+
// case (approver is a prefix of the session ID).
|
|
143
|
+
// When CLAUDE_SESSION_ID is unset or empty, we can't identify the creator,
|
|
144
|
+
// so self-approval prevention is skipped ('' || null → null → falsy guard).
|
|
145
|
+
const creatorSession = getAgentSessionId(process.cwd());
|
|
146
|
+
if (creatorSession && options.approvedBy) {
|
|
147
|
+
if (options.approvedBy === creatorSession) {
|
|
148
|
+
throw new Error(
|
|
149
|
+
'Waiver creator cannot be the approver.\n' +
|
|
150
|
+
'A different agent or human must approve this waiver.\n' +
|
|
151
|
+
`Creator session: ${creatorSession}`
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
141
156
|
// Generate waiver ID
|
|
142
157
|
const waiverId = `WV-${Date.now().toString().slice(-4)}`;
|
|
143
158
|
const timestamp = new Date().toISOString();
|
|
@@ -155,8 +170,27 @@ async function createWaiver(options) {
|
|
|
155
170
|
impact_level: options.impactLevel,
|
|
156
171
|
mitigation_plan: options.mitigationPlan,
|
|
157
172
|
status: 'active',
|
|
173
|
+
created_by_session: creatorSession,
|
|
158
174
|
};
|
|
159
175
|
|
|
176
|
+
// Validate waiver against schema before persisting
|
|
177
|
+
try {
|
|
178
|
+
const { createValidator, getSchemaPath } = require('../utils/schema-validator');
|
|
179
|
+
const schemaPath = getSchemaPath('waivers.schema.json', process.cwd());
|
|
180
|
+
const validate = createValidator(schemaPath);
|
|
181
|
+
// waivers.schema.json uses patternProperties keyed by waiver ID
|
|
182
|
+
const waiverDoc = { [waiverId]: waiver };
|
|
183
|
+
const result = validate(waiverDoc);
|
|
184
|
+
if (!result.valid) {
|
|
185
|
+
console.warn(chalk.yellow('Waiver has schema violations:'));
|
|
186
|
+
result.errors.forEach((err) => {
|
|
187
|
+
console.warn(chalk.yellow(` ${err.instancePath}: ${err.message}`));
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
} catch (schemaErr) {
|
|
191
|
+
// Non-fatal — don't block waiver creation on schema issues
|
|
192
|
+
}
|
|
193
|
+
|
|
160
194
|
// Save individual waiver file
|
|
161
195
|
const waiverPath = path.join(process.cwd(), WAIVER_DIR, `${waiverId}.yaml`);
|
|
162
196
|
fs.writeFileSync(waiverPath, yaml.dump(waiver, { lineWidth: -1 }));
|
|
@@ -198,9 +232,32 @@ async function listWaivers(_options) {
|
|
|
198
232
|
return;
|
|
199
233
|
}
|
|
200
234
|
|
|
235
|
+
// Load a schema validator once for all waiver files (if available)
|
|
236
|
+
let waiverValidate = null;
|
|
237
|
+
try {
|
|
238
|
+
const { createValidator, getSchemaPath } = require('../utils/schema-validator');
|
|
239
|
+
const schemaPath = getSchemaPath('waivers.schema.json', process.cwd());
|
|
240
|
+
waiverValidate = createValidator(schemaPath);
|
|
241
|
+
} catch {
|
|
242
|
+
// Schema not available — skip validation
|
|
243
|
+
}
|
|
244
|
+
|
|
201
245
|
const waivers = waiverFiles.map((file) => {
|
|
202
246
|
const content = fs.readFileSync(path.join(waiversDir, file), 'utf8');
|
|
203
|
-
|
|
247
|
+
const waiver = yaml.load(content);
|
|
248
|
+
|
|
249
|
+
// Validate each loaded waiver against schema
|
|
250
|
+
if (waiverValidate && waiver && waiver.id) {
|
|
251
|
+
const result = waiverValidate({ [waiver.id]: waiver });
|
|
252
|
+
if (!result.valid) {
|
|
253
|
+
console.warn(chalk.yellow(`Schema warning for ${file}:`));
|
|
254
|
+
result.errors.forEach((err) => {
|
|
255
|
+
console.warn(chalk.yellow(` ${err.instancePath}: ${err.message}`));
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return waiver;
|
|
204
261
|
});
|
|
205
262
|
|
|
206
263
|
// Filter by status
|