@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
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
|
}
|
|
@@ -69,10 +66,12 @@ async function waiversCommand(subcommand = 'list', options = {}) {
|
|
|
69
66
|
return await showWaiver(options.id, options);
|
|
70
67
|
case 'revoke':
|
|
71
68
|
return await revokeWaiver(options.id, options);
|
|
69
|
+
case 'prune':
|
|
70
|
+
return await pruneWaivers(options);
|
|
72
71
|
default:
|
|
73
72
|
throw new Error(
|
|
74
73
|
`Unknown waiver subcommand: ${subcommand}.\n` +
|
|
75
|
-
'Available subcommands: create, list, show, revoke'
|
|
74
|
+
'Available subcommands: create, list, show, revoke, prune'
|
|
76
75
|
);
|
|
77
76
|
}
|
|
78
77
|
},
|
|
@@ -138,6 +137,24 @@ async function createWaiver(options) {
|
|
|
138
137
|
process.exit(1);
|
|
139
138
|
}
|
|
140
139
|
|
|
140
|
+
// Self-approval prevention: creator cannot be approver
|
|
141
|
+
// Uses strict equality — the previous .includes() check was an asymmetric
|
|
142
|
+
// substring match that produced false positives (blocking legitimate approvers
|
|
143
|
+
// whose name happened to contain the session ID) while missing the reverse
|
|
144
|
+
// case (approver is a prefix of the session ID).
|
|
145
|
+
// When CLAUDE_SESSION_ID is unset or empty, we can't identify the creator,
|
|
146
|
+
// so self-approval prevention is skipped ('' || null → null → falsy guard).
|
|
147
|
+
const creatorSession = getAgentSessionId(process.cwd());
|
|
148
|
+
if (creatorSession && options.approvedBy) {
|
|
149
|
+
if (options.approvedBy === creatorSession) {
|
|
150
|
+
throw new Error(
|
|
151
|
+
'Waiver creator cannot be the approver.\n' +
|
|
152
|
+
'A different agent or human must approve this waiver.\n' +
|
|
153
|
+
`Creator session: ${creatorSession}`
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
141
158
|
// Generate waiver ID
|
|
142
159
|
const waiverId = `WV-${Date.now().toString().slice(-4)}`;
|
|
143
160
|
const timestamp = new Date().toISOString();
|
|
@@ -155,8 +172,26 @@ async function createWaiver(options) {
|
|
|
155
172
|
impact_level: options.impactLevel,
|
|
156
173
|
mitigation_plan: options.mitigationPlan,
|
|
157
174
|
status: 'active',
|
|
175
|
+
created_by_session: creatorSession,
|
|
158
176
|
};
|
|
159
177
|
|
|
178
|
+
// Validate waiver against schema before persisting
|
|
179
|
+
try {
|
|
180
|
+
const { createValidator, getSchemaPath } = require('../utils/schema-validator');
|
|
181
|
+
const schemaPath = getSchemaPath('waivers.schema.json', process.cwd());
|
|
182
|
+
const validate = createValidator(schemaPath);
|
|
183
|
+
// waivers.schema.json validates a single waiver document directly (CAWSFIX-17)
|
|
184
|
+
const result = validate(waiver);
|
|
185
|
+
if (!result.valid) {
|
|
186
|
+
console.warn(chalk.yellow('Waiver has schema violations:'));
|
|
187
|
+
result.errors.forEach((err) => {
|
|
188
|
+
console.warn(chalk.yellow(` ${err.instancePath}: ${err.message}`));
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
} catch (schemaErr) {
|
|
192
|
+
// Non-fatal — don't block waiver creation on schema issues
|
|
193
|
+
}
|
|
194
|
+
|
|
160
195
|
// Save individual waiver file
|
|
161
196
|
const waiverPath = path.join(process.cwd(), WAIVER_DIR, `${waiverId}.yaml`);
|
|
162
197
|
fs.writeFileSync(waiverPath, yaml.dump(waiver, { lineWidth: -1 }));
|
|
@@ -198,9 +233,32 @@ async function listWaivers(_options) {
|
|
|
198
233
|
return;
|
|
199
234
|
}
|
|
200
235
|
|
|
236
|
+
// Load a schema validator once for all waiver files (if available)
|
|
237
|
+
let waiverValidate = null;
|
|
238
|
+
try {
|
|
239
|
+
const { createValidator, getSchemaPath } = require('../utils/schema-validator');
|
|
240
|
+
const schemaPath = getSchemaPath('waivers.schema.json', process.cwd());
|
|
241
|
+
waiverValidate = createValidator(schemaPath);
|
|
242
|
+
} catch {
|
|
243
|
+
// Schema not available — skip validation
|
|
244
|
+
}
|
|
245
|
+
|
|
201
246
|
const waivers = waiverFiles.map((file) => {
|
|
202
247
|
const content = fs.readFileSync(path.join(waiversDir, file), 'utf8');
|
|
203
|
-
|
|
248
|
+
const waiver = yaml.load(content);
|
|
249
|
+
|
|
250
|
+
// Validate each loaded waiver against schema
|
|
251
|
+
if (waiverValidate && waiver && waiver.id) {
|
|
252
|
+
const result = waiverValidate(waiver);
|
|
253
|
+
if (!result.valid) {
|
|
254
|
+
console.warn(chalk.yellow(`Schema warning for ${file}:`));
|
|
255
|
+
result.errors.forEach((err) => {
|
|
256
|
+
console.warn(chalk.yellow(` ${err.instancePath}: ${err.message}`));
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return waiver;
|
|
204
262
|
});
|
|
205
263
|
|
|
206
264
|
// Filter by status
|
|
@@ -348,6 +406,147 @@ async function revokeWaiver(waiverId, options) {
|
|
|
348
406
|
console.log(` Reason: ${waiver.revocation_reason}\n`);
|
|
349
407
|
}
|
|
350
408
|
|
|
409
|
+
/**
|
|
410
|
+
* Prune expired waivers.
|
|
411
|
+
*
|
|
412
|
+
* Behavior per CAWSFIX-04 AC3-A6:
|
|
413
|
+
* --expired : dry run — list prunable waivers, no disk changes,
|
|
414
|
+
* no events emitted. Exit 0.
|
|
415
|
+
* --expired --apply : transition each prunable waiver from
|
|
416
|
+
* `status: active` to `status: expired` in place
|
|
417
|
+
* (file is updated, NOT deleted, to preserve the
|
|
418
|
+
* audit trail) and append a `waiver_pruned` event
|
|
419
|
+
* to the event log for each.
|
|
420
|
+
*
|
|
421
|
+
* A waiver is "prunable" iff `status === 'active'` AND `expires_at < now`.
|
|
422
|
+
* Waivers already `expired` or `revoked` are untouched (A4). Non-expired
|
|
423
|
+
* active waivers are untouched (A5). Empty registries exit 0 with a
|
|
424
|
+
* friendly message (A6).
|
|
425
|
+
*
|
|
426
|
+
* @param {object} options
|
|
427
|
+
* @param {boolean} [options.expired] — currently the only prune criterion
|
|
428
|
+
* @param {boolean} [options.apply] — if false, dry-run (default)
|
|
429
|
+
* @param {boolean} [options.json] — machine-readable output
|
|
430
|
+
*/
|
|
431
|
+
async function pruneWaivers(options = {}) {
|
|
432
|
+
if (!options.expired) {
|
|
433
|
+
console.error(chalk.red('\n`caws waivers prune` requires --expired\n'));
|
|
434
|
+
console.log(chalk.yellow('Usage: caws waivers prune --expired [--apply]\n'));
|
|
435
|
+
process.exit(1);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const waiversManager = new WaiversManager();
|
|
439
|
+
|
|
440
|
+
// Fast path: no waivers directory or no waiver files at all.
|
|
441
|
+
const allWaivers = waiversManager.enumerateWaiverFiles();
|
|
442
|
+
if (allWaivers.length === 0) {
|
|
443
|
+
const msg = 'No active waivers to check.';
|
|
444
|
+
if (options.json) {
|
|
445
|
+
console.log(JSON.stringify({ status: 'ok', pruned: [], message: msg }));
|
|
446
|
+
} else {
|
|
447
|
+
console.log(chalk.yellow(`\n${msg}\n`));
|
|
448
|
+
}
|
|
449
|
+
return { pruned: [], applied: false };
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
const candidates = waiversManager.findExpiredWaivers();
|
|
453
|
+
|
|
454
|
+
// No prunable waivers — report and return.
|
|
455
|
+
if (candidates.length === 0) {
|
|
456
|
+
const msg = 'No expired waivers to prune.';
|
|
457
|
+
if (options.json) {
|
|
458
|
+
console.log(JSON.stringify({ status: 'ok', pruned: [], message: msg }));
|
|
459
|
+
} else {
|
|
460
|
+
console.log(chalk.green(`\n${msg}\n`));
|
|
461
|
+
}
|
|
462
|
+
return { pruned: [], applied: false };
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const apply = Boolean(options.apply);
|
|
466
|
+
|
|
467
|
+
if (!apply) {
|
|
468
|
+
// Dry run — report only, no disk changes, no events.
|
|
469
|
+
if (options.json) {
|
|
470
|
+
console.log(
|
|
471
|
+
JSON.stringify({
|
|
472
|
+
status: 'dry_run',
|
|
473
|
+
applied: false,
|
|
474
|
+
pruned: candidates.map((c) => ({ id: c.id, expires_at: c.expires_at })),
|
|
475
|
+
})
|
|
476
|
+
);
|
|
477
|
+
} else {
|
|
478
|
+
console.log(chalk.yellow(`\nDry run — ${candidates.length} waiver(s) would be pruned:\n`));
|
|
479
|
+
candidates.forEach((c) => {
|
|
480
|
+
console.log(` ${chalk.bold(c.id)} expired at ${c.expires_at}`);
|
|
481
|
+
});
|
|
482
|
+
console.log(chalk.dim('\nRe-run with --apply to transition status to expired.\n'));
|
|
483
|
+
}
|
|
484
|
+
return { pruned: candidates, applied: false };
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Apply path — transition each file and emit events.
|
|
488
|
+
const { appendEvent } = require('../utils/event-log');
|
|
489
|
+
const pruned = [];
|
|
490
|
+
const failures = [];
|
|
491
|
+
|
|
492
|
+
for (const c of candidates) {
|
|
493
|
+
try {
|
|
494
|
+
const updated = waiversManager.markWaiverExpired(c.path);
|
|
495
|
+
|
|
496
|
+
// Emit waiver_pruned event. spec_id is optional for this event
|
|
497
|
+
// (waivers may or may not be tied to a spec). Using the waiver's
|
|
498
|
+
// applies_to field when available, otherwise omitting.
|
|
499
|
+
const spec_id =
|
|
500
|
+
updated && typeof updated.applies_to === 'string' && updated.applies_to.trim() !== ''
|
|
501
|
+
? updated.applies_to
|
|
502
|
+
: undefined;
|
|
503
|
+
|
|
504
|
+
await appendEvent({
|
|
505
|
+
actor: 'cli',
|
|
506
|
+
event: 'waiver_pruned',
|
|
507
|
+
spec_id,
|
|
508
|
+
data: {
|
|
509
|
+
waiver_id: c.id,
|
|
510
|
+
expires_at: c.expires_at,
|
|
511
|
+
previous_status: 'active',
|
|
512
|
+
new_status: 'expired',
|
|
513
|
+
},
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
pruned.push({ id: c.id, expires_at: c.expires_at });
|
|
517
|
+
} catch (err) {
|
|
518
|
+
failures.push({ id: c.id, error: err.message });
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
if (options.json) {
|
|
523
|
+
console.log(
|
|
524
|
+
JSON.stringify({
|
|
525
|
+
status: failures.length === 0 ? 'ok' : 'partial',
|
|
526
|
+
applied: true,
|
|
527
|
+
pruned,
|
|
528
|
+
failures,
|
|
529
|
+
})
|
|
530
|
+
);
|
|
531
|
+
} else {
|
|
532
|
+
console.log(
|
|
533
|
+
chalk.green(`\nPruned ${pruned.length} expired waiver(s):\n`)
|
|
534
|
+
);
|
|
535
|
+
pruned.forEach((p) => {
|
|
536
|
+
console.log(` ${chalk.bold(p.id)} (was expired at ${p.expires_at})`);
|
|
537
|
+
});
|
|
538
|
+
if (failures.length > 0) {
|
|
539
|
+
console.log(chalk.red(`\n${failures.length} failure(s):\n`));
|
|
540
|
+
failures.forEach((f) => {
|
|
541
|
+
console.log(` ${chalk.bold(f.id)}: ${f.error}`);
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
console.log();
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
return { pruned, applied: true, failures };
|
|
548
|
+
}
|
|
549
|
+
|
|
351
550
|
/**
|
|
352
551
|
* Add waiver to active waivers file for quality gates integration
|
|
353
552
|
*/
|
|
@@ -12,7 +12,12 @@ const {
|
|
|
12
12
|
mergeWorktree,
|
|
13
13
|
pruneWorktrees,
|
|
14
14
|
repairWorktrees,
|
|
15
|
+
loadRegistry,
|
|
16
|
+
saveRegistry,
|
|
17
|
+
getRepoRoot,
|
|
18
|
+
findFeatureSpecPath,
|
|
15
19
|
} = require('../worktree/worktree-manager');
|
|
20
|
+
const { getAgentSessionId } = require('../utils/agent-session');
|
|
16
21
|
|
|
17
22
|
/**
|
|
18
23
|
* Handle worktree subcommands
|
|
@@ -34,9 +39,11 @@ async function worktreeCommand(subcommand, options = {}) {
|
|
|
34
39
|
return handlePrune(options);
|
|
35
40
|
case 'repair':
|
|
36
41
|
return handleRepair(options);
|
|
42
|
+
case 'bind':
|
|
43
|
+
return handleBind(options);
|
|
37
44
|
default:
|
|
38
45
|
console.error(chalk.red(`Unknown worktree subcommand: ${subcommand}`));
|
|
39
|
-
console.log(chalk.blue('Available: create, list, destroy, merge, prune, repair'));
|
|
46
|
+
console.log(chalk.blue('Available: create, list, destroy, merge, prune, repair, bind'));
|
|
40
47
|
process.exit(1);
|
|
41
48
|
}
|
|
42
49
|
} catch (error) {
|
|
@@ -76,27 +83,47 @@ function handleList() {
|
|
|
76
83
|
}
|
|
77
84
|
|
|
78
85
|
const maxNameLen = Math.max(18, ...entries.map((e) => e.name.length + 2));
|
|
79
|
-
const
|
|
86
|
+
const maxBranchLen = Math.max(20, ...entries.map((e) => (e.branch || '').length + 2));
|
|
87
|
+
const totalWidth = maxNameLen + 14 + maxBranchLen + 16 + 16;
|
|
80
88
|
console.log(chalk.bold.cyan('CAWS Worktrees'));
|
|
81
89
|
console.log(chalk.cyan('='.repeat(totalWidth)));
|
|
82
90
|
console.log(
|
|
83
91
|
chalk.bold(
|
|
84
92
|
'Name'.padEnd(maxNameLen) +
|
|
85
|
-
'Status'.padEnd(
|
|
86
|
-
'Branch'.padEnd(
|
|
93
|
+
'Status'.padEnd(14) +
|
|
94
|
+
'Branch'.padEnd(maxBranchLen) +
|
|
87
95
|
'Last Commit'.padEnd(16) +
|
|
88
|
-
'
|
|
96
|
+
'Session'
|
|
89
97
|
)
|
|
90
98
|
);
|
|
91
99
|
console.log(chalk.gray('-'.repeat(totalWidth)));
|
|
92
100
|
|
|
101
|
+
// Show current session for comparison
|
|
102
|
+
const currentSession = getAgentSessionId(process.cwd());
|
|
103
|
+
if (currentSession) {
|
|
104
|
+
const shortCurrent = currentSession.length > 8 ? '...' + currentSession.slice(-8) : currentSession;
|
|
105
|
+
console.log(chalk.gray(`You: ${shortCurrent}`));
|
|
106
|
+
console.log(chalk.gray('-'.repeat(totalWidth)));
|
|
107
|
+
}
|
|
108
|
+
|
|
93
109
|
for (const entry of entries) {
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
110
|
+
const statusColors = {
|
|
111
|
+
active: chalk.green,
|
|
112
|
+
fresh: chalk.cyan,
|
|
113
|
+
merged: chalk.blue,
|
|
114
|
+
destroyed: chalk.gray,
|
|
115
|
+
missing: chalk.red,
|
|
116
|
+
'stale-merged': chalk.yellow,
|
|
117
|
+
orphaned: chalk.yellow,
|
|
118
|
+
unregistered: chalk.yellow,
|
|
119
|
+
};
|
|
120
|
+
const statusColor = statusColors[entry.status] || chalk.white;
|
|
121
|
+
|
|
122
|
+
// Build status string with dirty indicator
|
|
123
|
+
let statusStr = entry.status;
|
|
124
|
+
if (entry.dirty && (entry.status === 'active' || entry.status === 'fresh')) {
|
|
125
|
+
statusStr += '*';
|
|
126
|
+
}
|
|
100
127
|
|
|
101
128
|
// Format last commit age
|
|
102
129
|
let commitAge = chalk.gray('-');
|
|
@@ -104,29 +131,29 @@ function handleList() {
|
|
|
104
131
|
commitAge = chalk.white(entry.lastCommit.age);
|
|
105
132
|
}
|
|
106
133
|
|
|
107
|
-
// Format owner — show truncated session ID
|
|
134
|
+
// Format owner — show truncated session ID, highlight if it's the current session
|
|
108
135
|
let ownerStr = chalk.gray('-');
|
|
109
136
|
if (entry.owner) {
|
|
110
|
-
// Show last 8 chars of session ID for readability
|
|
111
137
|
const short = entry.owner.length > 8 ? '...' + entry.owner.slice(-8) : entry.owner;
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
if (entry.merged && entry.status === 'active') {
|
|
118
|
-
statusStr = 'merged';
|
|
138
|
+
if (currentSession && entry.owner === currentSession) {
|
|
139
|
+
ownerStr = chalk.green(short + ' (you)');
|
|
140
|
+
} else {
|
|
141
|
+
ownerStr = chalk.yellow(short);
|
|
142
|
+
}
|
|
119
143
|
}
|
|
120
144
|
|
|
121
145
|
console.log(
|
|
122
146
|
entry.name.padEnd(maxNameLen) +
|
|
123
|
-
statusColor(statusStr.padEnd(
|
|
124
|
-
(entry.branch || '').padEnd(
|
|
147
|
+
statusColor(statusStr.padEnd(14)) +
|
|
148
|
+
(entry.branch || '').padEnd(maxBranchLen) +
|
|
125
149
|
commitAge.padEnd(16 + 10) + // +10 for chalk color codes
|
|
126
150
|
ownerStr
|
|
127
151
|
);
|
|
128
152
|
}
|
|
129
153
|
|
|
154
|
+
// Legend
|
|
155
|
+
console.log('');
|
|
156
|
+
console.log(chalk.gray('Status: fresh = no commits yet, active = has commits/changes, active* = dirty files'));
|
|
130
157
|
console.log('');
|
|
131
158
|
}
|
|
132
159
|
|
|
@@ -204,9 +231,10 @@ function handleMerge(options) {
|
|
|
204
231
|
|
|
205
232
|
function handlePrune(options) {
|
|
206
233
|
const maxAge = options.maxAge !== undefined ? parseInt(options.maxAge, 10) : 30;
|
|
234
|
+
const force = options.force || false;
|
|
207
235
|
|
|
208
236
|
console.log(chalk.cyan(`Pruning worktrees (max age: ${maxAge} days)`));
|
|
209
|
-
const result = pruneWorktrees({ maxAgeDays: maxAge });
|
|
237
|
+
const result = pruneWorktrees({ maxAgeDays: maxAge, force });
|
|
210
238
|
|
|
211
239
|
// Handle both old return format (array) and new format (object with pruned/skipped)
|
|
212
240
|
const pruned = Array.isArray(result) ? result : result.pruned;
|
|
@@ -234,6 +262,7 @@ function handlePrune(options) {
|
|
|
234
262
|
function handleRepair(options) {
|
|
235
263
|
const dryRun = options.dryRun || false;
|
|
236
264
|
const shouldPrune = options.prune || false;
|
|
265
|
+
const force = options.force || false;
|
|
237
266
|
|
|
238
267
|
if (dryRun) {
|
|
239
268
|
console.log(chalk.cyan('Repair dry-run (no changes will be persisted)'));
|
|
@@ -241,7 +270,7 @@ function handleRepair(options) {
|
|
|
241
270
|
console.log(chalk.cyan('Repairing worktree registry'));
|
|
242
271
|
}
|
|
243
272
|
|
|
244
|
-
const result = repairWorktrees({ prune: shouldPrune, dryRun });
|
|
273
|
+
const result = repairWorktrees({ prune: shouldPrune, dryRun, force });
|
|
245
274
|
|
|
246
275
|
if (result.repaired.length === 0 && result.pruned.length === 0 && result.skipped.length === 0) {
|
|
247
276
|
console.log(chalk.green('Registry is consistent. Nothing to repair.'));
|
|
@@ -251,10 +280,11 @@ function handleRepair(options) {
|
|
|
251
280
|
if (result.repaired.length > 0) {
|
|
252
281
|
console.log(chalk.green('\nRepaired ' + result.repaired.length + ' entry/entries:'));
|
|
253
282
|
for (const r of result.repaired) {
|
|
283
|
+
const ownerTag = r.owner ? chalk.yellow(` [owner: ${r.owner}]`) : '';
|
|
254
284
|
if (r.action === 'registered') {
|
|
255
285
|
console.log(chalk.gray(' + ' + r.name + ' (auto-registered from git)'));
|
|
256
286
|
} else {
|
|
257
|
-
console.log(chalk.gray(' ~ ' + r.name + ' (' + r.from + ' -> ' + r.to + ')'));
|
|
287
|
+
console.log(chalk.gray(' ~ ' + r.name + ' (' + r.from + ' -> ' + r.to + ')') + ownerTag);
|
|
258
288
|
}
|
|
259
289
|
}
|
|
260
290
|
}
|
|
@@ -262,7 +292,8 @@ function handleRepair(options) {
|
|
|
262
292
|
if (result.pruned.length > 0) {
|
|
263
293
|
console.log(chalk.green('\nPruned ' + result.pruned.length + ' stale entry/entries:'));
|
|
264
294
|
for (const p of result.pruned) {
|
|
265
|
-
|
|
295
|
+
const ownerTag = p.owner ? chalk.yellow(` [owner: ${p.owner}]`) : '';
|
|
296
|
+
console.log(chalk.gray(' - ' + p.name + ' (' + p.status + ')') + ownerTag);
|
|
266
297
|
}
|
|
267
298
|
}
|
|
268
299
|
|
|
@@ -278,4 +309,78 @@ function handleRepair(options) {
|
|
|
278
309
|
}
|
|
279
310
|
}
|
|
280
311
|
|
|
312
|
+
function handleBind(options) {
|
|
313
|
+
const path = require('path');
|
|
314
|
+
const fs = require('fs-extra');
|
|
315
|
+
const yaml = require('js-yaml');
|
|
316
|
+
const { specId, name } = options;
|
|
317
|
+
|
|
318
|
+
if (!specId) {
|
|
319
|
+
console.error(chalk.red('Spec ID is required'));
|
|
320
|
+
console.log(chalk.blue('Usage: caws worktree bind <spec-id> [--name <worktree-name>]'));
|
|
321
|
+
process.exit(1);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Determine worktree name: from option, or detect from cwd
|
|
325
|
+
let worktreeName = name;
|
|
326
|
+
if (!worktreeName) {
|
|
327
|
+
const root = getRepoRoot();
|
|
328
|
+
const cwd = process.cwd();
|
|
329
|
+
const worktreesBase = path.join(root, '.caws', 'worktrees');
|
|
330
|
+
|
|
331
|
+
if (cwd.startsWith(worktreesBase + path.sep)) {
|
|
332
|
+
const relative = path.relative(worktreesBase, cwd);
|
|
333
|
+
worktreeName = relative.split(path.sep)[0];
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (!worktreeName) {
|
|
338
|
+
console.error(chalk.red('Could not determine worktree name.'));
|
|
339
|
+
console.log(chalk.blue('Either run this from inside a worktree, or pass --name <worktree-name>'));
|
|
340
|
+
process.exit(1);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const root = getRepoRoot();
|
|
344
|
+
const registry = loadRegistry(root);
|
|
345
|
+
|
|
346
|
+
// Find the worktree entry in the registry
|
|
347
|
+
if (!registry.worktrees || !registry.worktrees[worktreeName]) {
|
|
348
|
+
console.error(chalk.red(`Worktree '${worktreeName}' not found in registry.`));
|
|
349
|
+
console.log(chalk.blue('Run: caws worktree list to see available worktrees'));
|
|
350
|
+
process.exit(1);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Load the spec file
|
|
354
|
+
const specPath = findFeatureSpecPath(root, specId);
|
|
355
|
+
if (!specPath) {
|
|
356
|
+
console.error(chalk.red(`Spec '${specId}' not found in .caws/specs/`));
|
|
357
|
+
console.log(chalk.blue('Run: caws specs list to see available specs'));
|
|
358
|
+
process.exit(1);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const specContent = fs.readFileSync(specPath, 'utf8');
|
|
362
|
+
const specData = yaml.load(specContent);
|
|
363
|
+
|
|
364
|
+
// Warn if spec already bound to a different worktree
|
|
365
|
+
if (specData.worktree && specData.worktree !== worktreeName) {
|
|
366
|
+
console.log(chalk.yellow(`Warning: Spec '${specId}' is currently bound to worktree '${specData.worktree}'.`));
|
|
367
|
+
console.log(chalk.yellow(`Rebinding to '${worktreeName}'.`));
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Update registry side: set specId on the worktree entry
|
|
371
|
+
registry.worktrees[worktreeName].specId = specId;
|
|
372
|
+
saveRegistry(root, registry);
|
|
373
|
+
|
|
374
|
+
// Update spec side: set worktree field
|
|
375
|
+
specData.worktree = worktreeName;
|
|
376
|
+
const updatedYaml = yaml.dump(specData, { lineWidth: 120, noRefs: true });
|
|
377
|
+
fs.writeFileSync(specPath, updatedYaml, 'utf8');
|
|
378
|
+
|
|
379
|
+
console.log(chalk.green(`Binding established`));
|
|
380
|
+
console.log(chalk.gray(` Worktree: ${worktreeName} -> spec: ${specId}`));
|
|
381
|
+
console.log(chalk.gray(` Spec: ${specId} -> worktree: ${worktreeName}`));
|
|
382
|
+
console.log(chalk.gray(` Registry: ${path.join(root, '.caws', 'worktrees.json')}`));
|
|
383
|
+
console.log(chalk.gray(` Spec file: ${specPath}`));
|
|
384
|
+
}
|
|
385
|
+
|
|
281
386
|
module.exports = { worktreeCommand };
|
package/dist/error-handler.js
CHANGED
|
@@ -7,9 +7,7 @@
|
|
|
7
7
|
const chalk = require('chalk');
|
|
8
8
|
const {
|
|
9
9
|
ERROR_CATEGORIES,
|
|
10
|
-
ERROR_CODES,
|
|
11
10
|
getErrorCategory,
|
|
12
|
-
getCategorySuggestions,
|
|
13
11
|
} = require('./utils/error-categories');
|
|
14
12
|
|
|
15
13
|
/**
|
|
@@ -73,7 +71,7 @@ async function safeAsync(operation, context = '', includeTiming = false) {
|
|
|
73
71
|
try {
|
|
74
72
|
const result = await operation();
|
|
75
73
|
|
|
76
|
-
if (includeTiming && timer) {
|
|
74
|
+
if (includeTiming && timer && !isJsonOutput() && process.env.CAWS_QUIET !== '1') {
|
|
77
75
|
const duration = timer.formatDuration();
|
|
78
76
|
console.log(chalk.gray(` (completed in ${duration})`));
|
|
79
77
|
}
|
|
@@ -110,7 +108,7 @@ function safeSync(operation, context = '', includeTiming = false) {
|
|
|
110
108
|
try {
|
|
111
109
|
const result = operation();
|
|
112
110
|
|
|
113
|
-
if (includeTiming && timer) {
|
|
111
|
+
if (includeTiming && timer && !isJsonOutput() && process.env.CAWS_QUIET !== '1') {
|
|
114
112
|
const duration = timer.formatDuration();
|
|
115
113
|
console.log(chalk.gray(` (completed in ${duration})`));
|
|
116
114
|
}
|
|
@@ -182,15 +180,6 @@ const COMMAND_SUGGESTIONS = {
|
|
|
182
180
|
suggestions.push(`Did you mean: caws ${similar}?`);
|
|
183
181
|
}
|
|
184
182
|
|
|
185
|
-
// Context-aware suggestions based on command type
|
|
186
|
-
const commandCategories = {
|
|
187
|
-
setup: ['init', 'scaffold', 'templates'],
|
|
188
|
-
validation: ['validate', 'status', 'diagnose'],
|
|
189
|
-
analysis: ['evaluate', 'iterate', 'test-analysis'],
|
|
190
|
-
compliance: ['waivers', 'workflow', 'quality-monitor'],
|
|
191
|
-
history: ['provenance', 'hooks'],
|
|
192
|
-
};
|
|
193
|
-
|
|
194
183
|
// Suggest category based on what user might be trying to do
|
|
195
184
|
if (command.includes('setup') || command.includes('start') || command.includes('create')) {
|
|
196
185
|
suggestions.push('For project setup: caws init');
|