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