@paths.design/caws-cli 8.0.1 ā 8.2.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 +5 -6
- package/dist/commands/archive.d.ts +1 -0
- package/dist/commands/archive.d.ts.map +1 -1
- package/dist/commands/archive.js +114 -6
- package/dist/commands/burnup.d.ts.map +1 -1
- package/dist/commands/burnup.js +109 -10
- package/dist/commands/diagnose.js +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +185 -39
- package/dist/commands/mode.d.ts +2 -1
- package/dist/commands/mode.d.ts.map +1 -1
- package/dist/commands/mode.js +24 -14
- package/dist/commands/provenance.d.ts.map +1 -1
- package/dist/commands/provenance.js +216 -93
- package/dist/commands/quality-gates.d.ts.map +1 -1
- package/dist/commands/quality-gates.js +3 -1
- package/dist/commands/specs.d.ts.map +1 -1
- package/dist/commands/specs.js +184 -6
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +134 -10
- package/dist/commands/templates.js +2 -2
- package/dist/commands/worktree.d.ts +7 -0
- package/dist/commands/worktree.d.ts.map +1 -0
- package/dist/commands/worktree.js +136 -0
- package/dist/config/lite-scope.d.ts +33 -0
- package/dist/config/lite-scope.d.ts.map +1 -0
- package/dist/config/lite-scope.js +158 -0
- package/dist/config/modes.d.ts +90 -51
- package/dist/config/modes.d.ts.map +1 -1
- package/dist/config/modes.js +26 -0
- package/dist/error-handler.d.ts +3 -16
- package/dist/error-handler.d.ts.map +1 -1
- package/dist/error-handler.js +6 -98
- package/dist/generators/jest-config-generator.d.ts +32 -0
- package/dist/generators/jest-config-generator.d.ts.map +1 -0
- package/dist/generators/jest-config-generator.js +242 -0
- package/dist/index.js +40 -7
- package/dist/minimal-cli.js +3 -1
- package/dist/scaffold/claude-hooks.d.ts +28 -0
- package/dist/scaffold/claude-hooks.d.ts.map +1 -0
- package/dist/scaffold/claude-hooks.js +344 -0
- package/dist/scaffold/index.d.ts +2 -0
- package/dist/scaffold/index.d.ts.map +1 -1
- package/dist/scaffold/index.js +96 -76
- package/dist/templates/.caws/schemas/scope.schema.json +52 -0
- package/dist/templates/.caws/schemas/working-spec.schema.json +1 -1
- package/dist/templates/.caws/schemas/worktrees.schema.json +36 -0
- package/dist/templates/.claude/README.md +190 -0
- package/dist/templates/.claude/hooks/audit.sh +96 -0
- package/dist/templates/.claude/hooks/block-dangerous.sh +123 -0
- package/dist/templates/.claude/hooks/lite-sprawl-check.sh +117 -0
- package/dist/templates/.claude/hooks/naming-check.sh +97 -0
- package/dist/templates/.claude/hooks/quality-check.sh +68 -0
- package/dist/templates/.claude/hooks/scan-secrets.sh +85 -0
- package/dist/templates/.claude/hooks/scope-guard.sh +192 -0
- package/dist/templates/.claude/hooks/simplification-guard.sh +92 -0
- package/dist/templates/.claude/hooks/validate-spec.sh +76 -0
- package/dist/templates/.claude/settings.json +95 -0
- package/dist/templates/.cursor/README.md +0 -3
- package/dist/templates/.github/copilot-instructions.md +82 -0
- package/dist/templates/.junie/guidelines.md +73 -0
- package/dist/templates/.vscode/launch.json +0 -27
- package/dist/templates/.windsurf/rules/caws-quality-standards.md +54 -0
- package/dist/templates/CLAUDE.md +101 -0
- package/dist/templates/agents.md +73 -1016
- package/dist/templates/docs/README.md +5 -5
- package/dist/test-analysis.d.ts +50 -1
- package/dist/test-analysis.d.ts.map +1 -1
- package/dist/test-analysis.js +203 -10
- package/dist/utils/error-categories.d.ts +52 -0
- package/dist/utils/error-categories.d.ts.map +1 -0
- package/dist/utils/error-categories.js +210 -0
- package/dist/utils/gitignore-updater.d.ts +1 -1
- package/dist/utils/gitignore-updater.d.ts.map +1 -1
- package/dist/utils/gitignore-updater.js +4 -0
- package/dist/utils/ide-detection.js +133 -0
- package/dist/utils/quality-gates-utils.d.ts +49 -0
- package/dist/utils/quality-gates-utils.d.ts.map +1 -0
- package/dist/utils/quality-gates-utils.js +402 -0
- package/dist/utils/typescript-detector.d.ts +8 -5
- package/dist/utils/typescript-detector.d.ts.map +1 -1
- package/dist/utils/typescript-detector.js +36 -90
- package/dist/validation/spec-validation.d.ts.map +1 -1
- package/dist/validation/spec-validation.js +59 -6
- package/dist/worktree/worktree-manager.d.ts +54 -0
- package/dist/worktree/worktree-manager.d.ts.map +1 -0
- package/dist/worktree/worktree-manager.js +378 -0
- package/package.json +9 -3
- package/templates/.caws/schemas/scope.schema.json +52 -0
- package/templates/.caws/schemas/working-spec.schema.json +1 -1
- package/templates/.caws/schemas/worktrees.schema.json +36 -0
- package/templates/.claude/README.md +190 -0
- package/templates/.claude/hooks/audit.sh +96 -0
- package/templates/.claude/hooks/block-dangerous.sh +123 -0
- package/templates/.claude/hooks/lite-sprawl-check.sh +117 -0
- package/templates/.claude/hooks/naming-check.sh +97 -0
- package/templates/.claude/hooks/quality-check.sh +68 -0
- package/templates/.claude/hooks/scan-secrets.sh +85 -0
- package/templates/.claude/hooks/scope-guard.sh +192 -0
- package/templates/.claude/hooks/simplification-guard.sh +92 -0
- package/templates/.claude/hooks/validate-spec.sh +76 -0
- package/templates/.claude/settings.json +95 -0
- package/templates/.cursor/README.md +0 -3
- package/templates/.github/copilot-instructions.md +82 -0
- package/templates/.junie/guidelines.md +73 -0
- package/templates/.vscode/launch.json +0 -27
- package/templates/.windsurf/rules/caws-quality-standards.md +54 -0
- package/templates/AGENTS.md +104 -0
- package/templates/CLAUDE.md +101 -0
- package/templates/docs/README.md +5 -5
- package/templates/.github/copilot/instructions.md +0 -311
- package/templates/agents.md +0 -1047
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ The CAWS CLI serves as the central control point for:
|
|
|
13
13
|
- **Agent Integration**: Programmatic APIs for AI agents to evaluate and guide development
|
|
14
14
|
- **Waiver Management**: Fast-lane escape hatches with full audit trails
|
|
15
15
|
- **CI/CD Optimization**: Pipeline generation and optimization tools
|
|
16
|
-
- **Experimental Features**: Dry-run capabilities for
|
|
16
|
+
- **Experimental Features**: Dry-run capabilities for experimental functionality
|
|
17
17
|
|
|
18
18
|
## Installation
|
|
19
19
|
|
|
@@ -157,15 +157,14 @@ caws-cli/
|
|
|
157
157
|
ā ā
|
|
158
158
|
āāāāāāāāāāāāāāāāāāāāāāāāā
|
|
159
159
|
ā
|
|
160
|
-
āāāāāāāāāāāāāāāāāāā
|
|
161
|
-
ā caws-mcp-server
|
|
162
|
-
ā (Agent Bridge) ā
|
|
163
|
-
āāāāāāāāāāāāāāāāāāā
|
|
160
|
+
āāāāāāāāāāāāāāāāāāā
|
|
161
|
+
ā caws-mcp-server ā
|
|
162
|
+
ā (Agent Bridge) ā
|
|
163
|
+
āāāāāāāāāāāāāāāāāāā
|
|
164
164
|
```
|
|
165
165
|
|
|
166
166
|
- **caws-template**: Provides the tools and configurations that CLI manages
|
|
167
167
|
- **caws-mcp-server**: Exposes CLI functionality to AI agents via MCP protocol
|
|
168
|
-
- **caws-vscode-extension**: Provides IDE integration using CLI capabilities
|
|
169
168
|
|
|
170
169
|
### Quality Gates Integration
|
|
171
170
|
|
|
@@ -18,6 +18,7 @@ export function loadChange(changeId: string): Promise<any | null>;
|
|
|
18
18
|
export function validateAcceptanceCriteria(workingSpec: any): Promise<any>;
|
|
19
19
|
/**
|
|
20
20
|
* Validate change meets quality gates
|
|
21
|
+
* Runs the actual quality gates runner and checks for violations
|
|
21
22
|
* @param {string} changeId - Change identifier
|
|
22
23
|
* @returns {Promise<Object>} Quality gate result
|
|
23
24
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archive.d.ts","sourceRoot":"","sources":["../../src/commands/archive.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"archive.d.ts","sourceRoot":"","sources":["../../src/commands/archive.js"],"names":[],"mappings":"AA0VA;;;;GAIG;AACH,yCAHW,MAAM,+BAqGhB;AAjbD;;;;GAIG;AACH,qCAHW,MAAM,GACJ,OAAO,CAAC,MAAO,IAAI,CAAC,CAgChC;AAED;;;;GAIG;AACH,8DAFa,OAAO,KAAQ,CA8B3B;AAED;;;;;GAKG;AACH,sDAFa,OAAO,KAAQ,CAmH3B;AAED;;;;GAIG;AACH,oDAFa,OAAO,CAAC,MAAM,CAAC,CAgC3B;AAED;;;;GAIG;AACH,4CAFa,OAAO,CAAC,IAAI,CAAC,CAazB;AAED;;;;GAIG;AACH,+CAFa,OAAO,CAAC,IAAI,CAAC,CAoCzB;AAED;;;;;GAKG;AACH,6FAiCC"}
|
package/dist/commands/archive.js
CHANGED
|
@@ -8,6 +8,7 @@ const fs = require('fs-extra');
|
|
|
8
8
|
const path = require('path');
|
|
9
9
|
const yaml = require('js-yaml');
|
|
10
10
|
const chalk = require('chalk');
|
|
11
|
+
const { execSync } = require('child_process');
|
|
11
12
|
const { safeAsync, outputResult } = require('../error-handler');
|
|
12
13
|
|
|
13
14
|
// Import spec resolution system
|
|
@@ -87,16 +88,123 @@ async function validateAcceptanceCriteria(workingSpec) {
|
|
|
87
88
|
|
|
88
89
|
/**
|
|
89
90
|
* Validate change meets quality gates
|
|
91
|
+
* Runs the actual quality gates runner and checks for violations
|
|
90
92
|
* @param {string} changeId - Change identifier
|
|
91
93
|
* @returns {Promise<Object>} Quality gate result
|
|
92
94
|
*/
|
|
93
95
|
async function validateQualityGates(_changeId) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
96
|
+
const gates = [];
|
|
97
|
+
const violations = [];
|
|
98
|
+
const warnings = [];
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
// Try to run the quality gates runner
|
|
102
|
+
const qualityGatesPath = path.join(
|
|
103
|
+
__dirname,
|
|
104
|
+
'..',
|
|
105
|
+
'..',
|
|
106
|
+
'..',
|
|
107
|
+
'quality-gates',
|
|
108
|
+
'run-quality-gates.mjs'
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
// Check if quality gates runner exists
|
|
112
|
+
if (await fs.pathExists(qualityGatesPath)) {
|
|
113
|
+
try {
|
|
114
|
+
// Run quality gates in CI mode (checks all files, not just staged)
|
|
115
|
+
const result = execSync(`node "${qualityGatesPath}" --context=ci --json 2>&1`, {
|
|
116
|
+
encoding: 'utf8',
|
|
117
|
+
timeout: 60000, // 60 second timeout
|
|
118
|
+
cwd: process.cwd(),
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Try to parse JSON output
|
|
122
|
+
try {
|
|
123
|
+
const jsonMatch = result.match(/\{[\s\S]*\}$/);
|
|
124
|
+
if (jsonMatch) {
|
|
125
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
126
|
+
if (parsed.violations) {
|
|
127
|
+
violations.push(...parsed.violations);
|
|
128
|
+
}
|
|
129
|
+
if (parsed.warnings) {
|
|
130
|
+
warnings.push(...parsed.warnings);
|
|
131
|
+
}
|
|
132
|
+
gates.push(...(parsed.gates || []));
|
|
133
|
+
}
|
|
134
|
+
} catch {
|
|
135
|
+
// JSON parsing failed, check for error indicators in output
|
|
136
|
+
if (result.includes('ā') || result.includes('FAIL')) {
|
|
137
|
+
violations.push({ message: 'Quality gates reported failures', output: result });
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
} catch (execError) {
|
|
141
|
+
// Command failed - check exit code and output
|
|
142
|
+
if (execError.status !== 0) {
|
|
143
|
+
const output = execError.stdout || execError.message;
|
|
144
|
+
if (output.includes('violations') || output.includes('ā')) {
|
|
145
|
+
violations.push({ message: 'Quality gates failed', output: output.substring(0, 500) });
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Also check for active waivers that might cover violations
|
|
152
|
+
const waiversPath = path.join(process.cwd(), '.caws', 'waivers', 'active-waivers.yaml');
|
|
153
|
+
let hasActiveWaivers = false;
|
|
154
|
+
if (await fs.pathExists(waiversPath)) {
|
|
155
|
+
const waiversContent = await fs.readFile(waiversPath, 'utf8');
|
|
156
|
+
const waivers = yaml.load(waiversContent);
|
|
157
|
+
if (waivers && waivers.waivers) {
|
|
158
|
+
const activeWaiverCount = Object.keys(waivers.waivers).length;
|
|
159
|
+
if (activeWaiverCount > 0) {
|
|
160
|
+
hasActiveWaivers = true;
|
|
161
|
+
gates.push(`${activeWaiverCount} active waiver(s)`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Determine overall validity
|
|
167
|
+
const hasBlockingViolations = violations.some(
|
|
168
|
+
(v) => v.severity === 'block' || v.severity === 'fail'
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
if (violations.length === 0) {
|
|
172
|
+
return {
|
|
173
|
+
valid: true,
|
|
174
|
+
message: 'All quality gates passed',
|
|
175
|
+
gates: gates.length > 0 ? gates : ['naming', 'duplication', 'god-objects', 'hidden-todo'],
|
|
176
|
+
violations: [],
|
|
177
|
+
warnings,
|
|
178
|
+
};
|
|
179
|
+
} else if (hasActiveWaivers && !hasBlockingViolations) {
|
|
180
|
+
return {
|
|
181
|
+
valid: true,
|
|
182
|
+
message: `Quality gates passed with ${violations.length} waived violation(s)`,
|
|
183
|
+
gates,
|
|
184
|
+
violations,
|
|
185
|
+
warnings,
|
|
186
|
+
waived: true,
|
|
187
|
+
};
|
|
188
|
+
} else {
|
|
189
|
+
return {
|
|
190
|
+
valid: false,
|
|
191
|
+
message: `${violations.length} quality gate violation(s) found`,
|
|
192
|
+
gates,
|
|
193
|
+
violations,
|
|
194
|
+
warnings,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
} catch (error) {
|
|
198
|
+
// If quality gates can't be run, warn but don't block
|
|
199
|
+
return {
|
|
200
|
+
valid: true,
|
|
201
|
+
message: `Quality gates check skipped: ${error.message}`,
|
|
202
|
+
gates: [],
|
|
203
|
+
violations: [],
|
|
204
|
+
warnings: [{ message: `Could not run quality gates: ${error.message}` }],
|
|
205
|
+
skipped: true,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
100
208
|
}
|
|
101
209
|
|
|
102
210
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"burnup.d.ts","sourceRoot":"","sources":["../../src/commands/burnup.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"burnup.d.ts","sourceRoot":"","sources":["../../src/commands/burnup.js"],"names":[],"mappings":"AAsFA;;;GAGG;AACH,wCAFW,MAAM,iBAgGhB"}
|
package/dist/commands/burnup.js
CHANGED
|
@@ -8,9 +8,82 @@ const fs = require('fs-extra');
|
|
|
8
8
|
const path = require('path');
|
|
9
9
|
const yaml = require('js-yaml');
|
|
10
10
|
const chalk = require('chalk');
|
|
11
|
+
const { execSync } = require('child_process');
|
|
11
12
|
|
|
12
13
|
const { deriveBudget, generateBurnupReport } = require('../budget-derivation');
|
|
13
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Get actual git change statistics from the repository
|
|
17
|
+
* Analyzes changes since the last tag or initial commit
|
|
18
|
+
* @param {string} specDir - Directory containing the spec file
|
|
19
|
+
* @returns {Object} Stats with files_changed, lines_added, lines_removed, lines_changed
|
|
20
|
+
*/
|
|
21
|
+
function getGitChangeStats(specDir) {
|
|
22
|
+
try {
|
|
23
|
+
const cwd = specDir || process.cwd();
|
|
24
|
+
|
|
25
|
+
// Find the base reference - prefer last tag, fall back to first commit
|
|
26
|
+
let baseRef;
|
|
27
|
+
try {
|
|
28
|
+
baseRef = execSync('git describe --tags --abbrev=0 2>/dev/null', {
|
|
29
|
+
cwd,
|
|
30
|
+
encoding: 'utf8',
|
|
31
|
+
}).trim();
|
|
32
|
+
} catch {
|
|
33
|
+
// No tags, use first commit
|
|
34
|
+
try {
|
|
35
|
+
baseRef = execSync('git rev-list --max-parents=0 HEAD', {
|
|
36
|
+
cwd,
|
|
37
|
+
encoding: 'utf8',
|
|
38
|
+
}).trim();
|
|
39
|
+
} catch {
|
|
40
|
+
// Not a git repo or no commits
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Get file change count
|
|
46
|
+
const filesOutput = execSync(`git diff --name-only ${baseRef}..HEAD`, {
|
|
47
|
+
cwd,
|
|
48
|
+
encoding: 'utf8',
|
|
49
|
+
});
|
|
50
|
+
const filesChanged = filesOutput.trim().split('\n').filter(Boolean).length;
|
|
51
|
+
|
|
52
|
+
// Get line statistics using --numstat
|
|
53
|
+
const numstatOutput = execSync(`git diff --numstat ${baseRef}..HEAD`, {
|
|
54
|
+
cwd,
|
|
55
|
+
encoding: 'utf8',
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
let linesAdded = 0;
|
|
59
|
+
let linesRemoved = 0;
|
|
60
|
+
|
|
61
|
+
numstatOutput
|
|
62
|
+
.trim()
|
|
63
|
+
.split('\n')
|
|
64
|
+
.filter(Boolean)
|
|
65
|
+
.forEach((line) => {
|
|
66
|
+
const [added, removed] = line.split('\t');
|
|
67
|
+
// Skip binary files (shown as '-')
|
|
68
|
+
if (added !== '-' && removed !== '-') {
|
|
69
|
+
linesAdded += parseInt(added, 10) || 0;
|
|
70
|
+
linesRemoved += parseInt(removed, 10) || 0;
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
files_changed: filesChanged,
|
|
76
|
+
lines_added: linesAdded,
|
|
77
|
+
lines_removed: linesRemoved,
|
|
78
|
+
lines_changed: linesAdded + linesRemoved,
|
|
79
|
+
base_ref: baseRef,
|
|
80
|
+
};
|
|
81
|
+
} catch (error) {
|
|
82
|
+
// Return null if git analysis fails
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
14
87
|
/**
|
|
15
88
|
* Burn-up command handler
|
|
16
89
|
* @param {string} specFile - Path to spec file
|
|
@@ -32,15 +105,34 @@ async function burnupCommand(specFile) {
|
|
|
32
105
|
// Derive budget
|
|
33
106
|
const derivedBudget = deriveBudget(spec, path.dirname(specPath));
|
|
34
107
|
|
|
35
|
-
//
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
108
|
+
// Get actual git change statistics
|
|
109
|
+
const gitStats = getGitChangeStats(path.dirname(specPath));
|
|
110
|
+
|
|
111
|
+
let currentStats;
|
|
112
|
+
if (gitStats) {
|
|
113
|
+
currentStats = {
|
|
114
|
+
files_changed: gitStats.files_changed,
|
|
115
|
+
lines_changed: gitStats.lines_changed,
|
|
116
|
+
lines_added: gitStats.lines_added,
|
|
117
|
+
lines_removed: gitStats.lines_removed,
|
|
118
|
+
risk_tier: spec.risk_tier,
|
|
119
|
+
base_ref: gitStats.base_ref,
|
|
120
|
+
};
|
|
121
|
+
console.log(chalk.gray(` Analyzing changes since: ${gitStats.base_ref}`));
|
|
122
|
+
} else {
|
|
123
|
+
// Fallback if git analysis fails (not in a repo or no commits)
|
|
124
|
+
console.log(chalk.yellow(' ā ļø Could not analyze git history, using zero values'));
|
|
125
|
+
currentStats = {
|
|
126
|
+
files_changed: 0,
|
|
127
|
+
lines_changed: 0,
|
|
128
|
+
lines_added: 0,
|
|
129
|
+
lines_removed: 0,
|
|
130
|
+
risk_tier: spec.risk_tier,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
41
133
|
|
|
42
134
|
// Generate report
|
|
43
|
-
const report = generateBurnupReport(derivedBudget,
|
|
135
|
+
const report = generateBurnupReport(derivedBudget, currentStats);
|
|
44
136
|
|
|
45
137
|
console.log(report);
|
|
46
138
|
|
|
@@ -63,15 +155,22 @@ async function burnupCommand(specFile) {
|
|
|
63
155
|
|
|
64
156
|
console.log(
|
|
65
157
|
chalk.gray(
|
|
66
|
-
` Current Usage: ${
|
|
158
|
+
` Current Usage: ${currentStats.files_changed} files, ${currentStats.lines_changed} LOC`
|
|
67
159
|
)
|
|
68
160
|
);
|
|
161
|
+
if (currentStats.lines_added !== undefined) {
|
|
162
|
+
console.log(
|
|
163
|
+
chalk.gray(
|
|
164
|
+
` Breakdown: +${currentStats.lines_added} added, -${currentStats.lines_removed} removed`
|
|
165
|
+
)
|
|
166
|
+
);
|
|
167
|
+
}
|
|
69
168
|
|
|
70
169
|
const filePercent = Math.round(
|
|
71
|
-
(
|
|
170
|
+
(currentStats.files_changed / derivedBudget.effective.max_files) * 100
|
|
72
171
|
);
|
|
73
172
|
const locPercent = Math.round(
|
|
74
|
-
(
|
|
173
|
+
(currentStats.lines_changed / derivedBudget.effective.max_loc) * 100
|
|
75
174
|
);
|
|
76
175
|
|
|
77
176
|
if (filePercent > 90 || locPercent > 90) {
|
|
@@ -11,7 +11,7 @@ const chalk = require('chalk');
|
|
|
11
11
|
|
|
12
12
|
// Import utilities
|
|
13
13
|
const { checkTypeScriptTestConfig } = require('../utils/typescript-detector');
|
|
14
|
-
const { configureJestForTypeScript } = require('../generators/jest-config');
|
|
14
|
+
const { configureJestForTypeScript } = require('../generators/jest-config-generator');
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* Health check: Working spec validity
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.js"],"names":[],"mappings":"AAwBA;;GAEG;AACH,2EA6mBC"}
|
package/dist/commands/init.js
CHANGED
|
@@ -18,6 +18,15 @@ const { finalizeProject } = require('../utils/finalization');
|
|
|
18
18
|
const { scaffoldCursorHooks } = require('../scaffold/cursor-hooks');
|
|
19
19
|
const { scaffoldIDEIntegrations } = require('../scaffold/index');
|
|
20
20
|
const { updateGitignore } = require('../utils/gitignore-updater');
|
|
21
|
+
const { getLiteScopeDefaults } = require('../config/lite-scope');
|
|
22
|
+
const { scaffoldClaudeHooks } = require('../scaffold/claude-hooks');
|
|
23
|
+
const { setCurrentMode } = require('../config/modes');
|
|
24
|
+
const {
|
|
25
|
+
IDE_REGISTRY,
|
|
26
|
+
detectActiveIDEs,
|
|
27
|
+
getRecommendedIDEs,
|
|
28
|
+
parseIDESelection,
|
|
29
|
+
} = require('../utils/ide-detection');
|
|
21
30
|
|
|
22
31
|
/**
|
|
23
32
|
* Initialize a new project with CAWS
|
|
@@ -115,8 +124,9 @@ async function initProject(projectName, options) {
|
|
|
115
124
|
const cawsSetup = detectCAWSSetup(targetDir);
|
|
116
125
|
const originalTemplateDir = cawsSetup?.hasTemplateDir ? cawsSetup.templateDir : null;
|
|
117
126
|
|
|
118
|
-
// Check for existing agents.md/caws.md in target directory
|
|
119
|
-
const existingAgentsMd = fs.existsSync(path.join(targetDir, '
|
|
127
|
+
// Check for existing AGENTS.md/agents.md/caws.md in target directory
|
|
128
|
+
const existingAgentsMd = fs.existsSync(path.join(targetDir, 'AGENTS.md')) ||
|
|
129
|
+
fs.existsSync(path.join(targetDir, 'agents.md'));
|
|
120
130
|
const existingCawsMd = fs.existsSync(path.join(targetDir, 'caws.md'));
|
|
121
131
|
|
|
122
132
|
// Create project directory and change to it (unless already in current directory)
|
|
@@ -137,55 +147,45 @@ async function initProject(projectName, options) {
|
|
|
137
147
|
await fs.ensureDir('.agent');
|
|
138
148
|
console.log(chalk.blue('ā¹ļø Created basic CAWS structure'));
|
|
139
149
|
|
|
140
|
-
// Copy
|
|
150
|
+
// Copy AGENTS.md guide if templates are available
|
|
141
151
|
if (originalTemplateDir) {
|
|
142
152
|
try {
|
|
143
|
-
const agentsMdSource = path.join(originalTemplateDir, '
|
|
144
|
-
let targetFile = '
|
|
153
|
+
const agentsMdSource = path.join(originalTemplateDir, 'AGENTS.md');
|
|
154
|
+
let targetFile = 'AGENTS.md';
|
|
145
155
|
|
|
146
156
|
if (fs.existsSync(agentsMdSource)) {
|
|
147
|
-
// Use the pre-checked values for conflicts
|
|
148
157
|
if (existingAgentsMd) {
|
|
149
|
-
// Conflict: user already has agents.md
|
|
150
158
|
if (options.interactive && !options.nonInteractive) {
|
|
151
|
-
// Interactive mode: ask user
|
|
152
159
|
const overwriteAnswer = await inquirer.prompt([
|
|
153
160
|
{
|
|
154
161
|
type: 'confirm',
|
|
155
162
|
name: 'overwrite',
|
|
156
|
-
message: '
|
|
163
|
+
message: 'AGENTS.md already exists. Overwrite with CAWS guide?',
|
|
157
164
|
default: false,
|
|
158
165
|
},
|
|
159
166
|
]);
|
|
160
167
|
|
|
161
|
-
if (overwriteAnswer.overwrite) {
|
|
162
|
-
targetFile = 'agents.md';
|
|
163
|
-
} else {
|
|
168
|
+
if (!overwriteAnswer.overwrite) {
|
|
164
169
|
targetFile = 'caws.md';
|
|
165
170
|
}
|
|
166
171
|
} else {
|
|
167
|
-
// Non-interactive mode: use caws.md instead
|
|
168
172
|
targetFile = 'caws.md';
|
|
169
|
-
console.log(chalk.blue('
|
|
173
|
+
console.log(chalk.blue('AGENTS.md exists, using caws.md for CAWS guide'));
|
|
170
174
|
}
|
|
171
175
|
}
|
|
172
176
|
|
|
173
|
-
// If caws.md also exists and that's our target, skip
|
|
174
177
|
if (targetFile === 'caws.md' && existingCawsMd) {
|
|
175
178
|
console.log(
|
|
176
|
-
chalk.yellow('
|
|
179
|
+
chalk.yellow('Both AGENTS.md and caws.md exist, skipping guide copy')
|
|
177
180
|
);
|
|
178
181
|
} else {
|
|
179
182
|
const agentsMdDest = path.join(process.cwd(), targetFile);
|
|
180
183
|
await fs.copyFile(agentsMdSource, agentsMdDest);
|
|
181
|
-
console.log(chalk.green(
|
|
184
|
+
console.log(chalk.green(`Added ${targetFile} guide`));
|
|
182
185
|
}
|
|
183
186
|
}
|
|
184
187
|
} catch (templateError) {
|
|
185
|
-
console.warn(chalk.yellow('
|
|
186
|
-
console.warn(
|
|
187
|
-
chalk.blue('š” You can manually copy the guide from the caws-template package')
|
|
188
|
-
);
|
|
188
|
+
console.warn(chalk.yellow('Could not copy agents guide:'), templateError.message);
|
|
189
189
|
}
|
|
190
190
|
}
|
|
191
191
|
} else {
|
|
@@ -193,12 +193,92 @@ async function initProject(projectName, options) {
|
|
|
193
193
|
console.log(chalk.green('ā
CAWS project detected - skipping template copy'));
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
+
// Handle lite mode init path
|
|
197
|
+
if (options.mode === 'lite') {
|
|
198
|
+
console.log(chalk.magenta('š”ļø CAWS Lite Mode ā guardrails without YAML specs'));
|
|
199
|
+
|
|
200
|
+
// Detect allowed directories
|
|
201
|
+
const detectedDirs = [];
|
|
202
|
+
const commonDirs = ['src/', 'lib/', 'app/', 'tests/', 'test/', 'docs/'];
|
|
203
|
+
for (const dir of commonDirs) {
|
|
204
|
+
if (fs.existsSync(path.join(process.cwd(), dir.replace(/\/$/, '')))) {
|
|
205
|
+
detectedDirs.push(dir);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
let allowedDirs = detectedDirs.length > 0 ? detectedDirs : ['src/', 'tests/', 'docs/'];
|
|
210
|
+
|
|
211
|
+
if (options.interactive && !options.nonInteractive) {
|
|
212
|
+
const liteAnswers = await inquirer.prompt([
|
|
213
|
+
{
|
|
214
|
+
type: 'input',
|
|
215
|
+
name: 'projectName',
|
|
216
|
+
message: 'š Project name:',
|
|
217
|
+
default: path.basename(process.cwd()),
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
type: 'input',
|
|
221
|
+
name: 'allowedDirs',
|
|
222
|
+
message: 'š Allowed directories (comma-separated):',
|
|
223
|
+
default: allowedDirs.join(', '),
|
|
224
|
+
},
|
|
225
|
+
]);
|
|
226
|
+
allowedDirs = liteAnswers.allowedDirs.split(',').map((d) => d.trim()).filter(Boolean);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Generate .caws/scope.json
|
|
230
|
+
await fs.ensureDir('.caws');
|
|
231
|
+
const scopeConfig = getLiteScopeDefaults();
|
|
232
|
+
scopeConfig.allowedDirectories = allowedDirs;
|
|
233
|
+
await fs.writeFile(
|
|
234
|
+
path.join('.caws', 'scope.json'),
|
|
235
|
+
JSON.stringify(scopeConfig, null, 2)
|
|
236
|
+
);
|
|
237
|
+
console.log(chalk.green('ā
Created .caws/scope.json'));
|
|
238
|
+
|
|
239
|
+
// Set mode to lite
|
|
240
|
+
await setCurrentMode('lite');
|
|
241
|
+
console.log(chalk.green('ā
Set mode to lite in .caws/mode.json'));
|
|
242
|
+
|
|
243
|
+
// Scaffold hooks: block-dangerous + scope-guard + lite-sprawl-check + simplification-guard
|
|
244
|
+
const liteIDEs = options.ide ? parseIDESelection(options.ide) : ['claude'];
|
|
245
|
+
if (liteIDEs.includes('claude')) {
|
|
246
|
+
console.log(chalk.blue('š§ Setting up lite-mode hooks...'));
|
|
247
|
+
await scaffoldClaudeHooks(process.cwd(), ['safety', 'scope', 'lite']);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Update .gitignore
|
|
251
|
+
console.log(chalk.blue('š Updating .gitignore...'));
|
|
252
|
+
await updateGitignore(process.cwd());
|
|
253
|
+
|
|
254
|
+
// Success
|
|
255
|
+
console.log(chalk.green('\nš CAWS Lite mode initialized!'));
|
|
256
|
+
console.log(chalk.blue('\nGuardrails active:'));
|
|
257
|
+
console.log(' - Destructive command blocking (git push --force, rm -rf, etc.)');
|
|
258
|
+
console.log(' - Scope fencing (edits outside allowed directories require confirmation)');
|
|
259
|
+
console.log(' - File sprawl detection (banned patterns like *-enhanced.*, *-final.*)');
|
|
260
|
+
console.log(' - Simplification guard (prevents stubbing out implementations)');
|
|
261
|
+
console.log(chalk.blue('\nNext steps:'));
|
|
262
|
+
console.log(' 1. Review .caws/scope.json and customize for your project');
|
|
263
|
+
console.log(' 2. Start coding ā hooks will protect against common AI mistakes');
|
|
264
|
+
console.log(' 3. Use `caws worktree create <name>` for isolated agent workspaces');
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
|
|
196
268
|
// Handle interactive wizard or template-based setup
|
|
197
269
|
if (options.interactive && !options.nonInteractive) {
|
|
198
270
|
console.log(chalk.cyan('šÆ CAWS Interactive Setup Wizard'));
|
|
199
271
|
console.log(chalk.blue('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā'));
|
|
200
272
|
console.log(chalk.gray('This wizard will guide you through creating a CAWS working spec\n'));
|
|
201
273
|
|
|
274
|
+
// Detect active IDEs for pre-selecting the IDE prompt
|
|
275
|
+
const detectedIDEs = detectActiveIDEs();
|
|
276
|
+
const recommendedIDEs = getRecommendedIDEs();
|
|
277
|
+
if (detectedIDEs.length > 0) {
|
|
278
|
+
console.log(chalk.blue(`Detected IDE: ${detectedIDEs.map((id) => IDE_REGISTRY[id].name).join(', ')}`));
|
|
279
|
+
console.log(chalk.gray(' (Pre-selected based on your environment)\n'));
|
|
280
|
+
}
|
|
281
|
+
|
|
202
282
|
// Detect project type
|
|
203
283
|
const detectedType = detectProjectType(process.cwd());
|
|
204
284
|
console.log(chalk.blue(`š¦ Detected project type: ${chalk.cyan(detectedType)}`));
|
|
@@ -337,10 +417,65 @@ async function initProject(projectName, options) {
|
|
|
337
417
|
},
|
|
338
418
|
},
|
|
339
419
|
{
|
|
340
|
-
type: '
|
|
341
|
-
name: '
|
|
342
|
-
message: '
|
|
343
|
-
|
|
420
|
+
type: 'checkbox',
|
|
421
|
+
name: 'selectedIDEs',
|
|
422
|
+
message: 'Which IDE integrations do you want to install?',
|
|
423
|
+
choices: [
|
|
424
|
+
{
|
|
425
|
+
name: `Cursor (hooks, rules, audit) - AI-first IDE`,
|
|
426
|
+
value: 'cursor',
|
|
427
|
+
checked: options.ide
|
|
428
|
+
? parseIDESelection(options.ide).includes('cursor')
|
|
429
|
+
: recommendedIDEs.includes('cursor'),
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
name: `Claude Code (safety hooks, settings)`,
|
|
433
|
+
value: 'claude',
|
|
434
|
+
checked: options.ide
|
|
435
|
+
? parseIDESelection(options.ide).includes('claude')
|
|
436
|
+
: recommendedIDEs.includes('claude'),
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
name: `VS Code (settings, debug configs)`,
|
|
440
|
+
value: 'vscode',
|
|
441
|
+
checked: options.ide
|
|
442
|
+
? parseIDESelection(options.ide).includes('vscode')
|
|
443
|
+
: recommendedIDEs.includes('vscode'),
|
|
444
|
+
},
|
|
445
|
+
{
|
|
446
|
+
name: `IntelliJ IDEA (run configurations)`,
|
|
447
|
+
value: 'intellij',
|
|
448
|
+
checked: options.ide
|
|
449
|
+
? parseIDESelection(options.ide).includes('intellij')
|
|
450
|
+
: recommendedIDEs.includes('intellij'),
|
|
451
|
+
},
|
|
452
|
+
{
|
|
453
|
+
name: `Windsurf (CAWS workflow)`,
|
|
454
|
+
value: 'windsurf',
|
|
455
|
+
checked: options.ide
|
|
456
|
+
? parseIDESelection(options.ide).includes('windsurf')
|
|
457
|
+
: recommendedIDEs.includes('windsurf'),
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
name: `GitHub Copilot (instructions)`,
|
|
461
|
+
value: 'copilot',
|
|
462
|
+
checked: options.ide
|
|
463
|
+
? parseIDESelection(options.ide).includes('copilot')
|
|
464
|
+
: recommendedIDEs.includes('copilot'),
|
|
465
|
+
},
|
|
466
|
+
{
|
|
467
|
+
name: `JetBrains Junie (AI agent guidelines)`,
|
|
468
|
+
value: 'junie',
|
|
469
|
+
checked: options.ide
|
|
470
|
+
? parseIDESelection(options.ide).includes('junie')
|
|
471
|
+
: recommendedIDEs.includes('junie'),
|
|
472
|
+
},
|
|
473
|
+
new inquirer.Separator(),
|
|
474
|
+
{
|
|
475
|
+
name: 'All IDEs (install everything)',
|
|
476
|
+
value: 'all',
|
|
477
|
+
},
|
|
478
|
+
],
|
|
344
479
|
},
|
|
345
480
|
{
|
|
346
481
|
type: 'confirm',
|
|
@@ -412,15 +547,20 @@ Happy coding! šÆ
|
|
|
412
547
|
console.log(chalk.green('ā
Created test and docs directories'));
|
|
413
548
|
}
|
|
414
549
|
|
|
415
|
-
// Setup
|
|
416
|
-
|
|
417
|
-
|
|
550
|
+
// Setup selected IDE integrations
|
|
551
|
+
const selectedIDEs = parseIDESelection(answers.selectedIDEs || []);
|
|
552
|
+
|
|
553
|
+
if (selectedIDEs.includes('cursor')) {
|
|
554
|
+
console.log(chalk.blue('Setting up Cursor hooks...'));
|
|
418
555
|
await scaffoldCursorHooks(process.cwd());
|
|
419
556
|
}
|
|
420
557
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
558
|
+
if (selectedIDEs.length > 0) {
|
|
559
|
+
console.log(chalk.blue('Setting up IDE integrations...'));
|
|
560
|
+
await scaffoldIDEIntegrations(process.cwd(), { force: false, ides: selectedIDEs });
|
|
561
|
+
} else {
|
|
562
|
+
console.log(chalk.gray('Skipping IDE setup (none selected, run `caws scaffold --ide <ides>` later)'));
|
|
563
|
+
}
|
|
424
564
|
|
|
425
565
|
// Update .gitignore to exclude CAWS local runtime files
|
|
426
566
|
console.log(chalk.blue('š Updating .gitignore...'));
|
|
@@ -500,13 +640,18 @@ Happy coding! šÆ
|
|
|
500
640
|
console.log(chalk.green('ā
Created .caws/policy.yaml (optional - defaults work fine)'));
|
|
501
641
|
}
|
|
502
642
|
|
|
503
|
-
// Setup
|
|
504
|
-
|
|
505
|
-
await scaffoldCursorHooks(process.cwd());
|
|
643
|
+
// Setup IDE integrations based on --ide flag or auto-detection
|
|
644
|
+
const selectedIDEs = options.ide ? parseIDESelection(options.ide) : getRecommendedIDEs();
|
|
506
645
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
646
|
+
if (selectedIDEs.includes('cursor')) {
|
|
647
|
+
console.log(chalk.blue('Setting up Cursor hooks...'));
|
|
648
|
+
await scaffoldCursorHooks(process.cwd());
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
if (selectedIDEs.length > 0) {
|
|
652
|
+
console.log(chalk.blue(`Setting up IDE integrations: ${selectedIDEs.map((id) => IDE_REGISTRY[id].name).join(', ')}...`));
|
|
653
|
+
await scaffoldIDEIntegrations(process.cwd(), { force: false, ides: selectedIDEs });
|
|
654
|
+
}
|
|
510
655
|
|
|
511
656
|
// Update .gitignore to exclude CAWS local runtime files
|
|
512
657
|
console.log(chalk.blue('š Updating .gitignore...'));
|
|
@@ -549,8 +694,9 @@ Happy coding! šÆ
|
|
|
549
694
|
console.log(' 3. Run: caws validate (verify setup)');
|
|
550
695
|
console.log(' 4. Run: caws diagnose (check health)');
|
|
551
696
|
console.log(' 5. Optional: Create .caws/policy.yaml for custom budgets');
|
|
552
|
-
|
|
553
|
-
|
|
697
|
+
const finalIDEs = answers?.selectedIDEs || [];
|
|
698
|
+
if (finalIDEs.includes('cursor') || finalIDEs.includes('claude') || options.interactive === false) {
|
|
699
|
+
console.log(' 6. Restart your IDE to activate quality gates');
|
|
554
700
|
}
|
|
555
701
|
console.log('\nš” Quick start: caws scaffold && caws validate && caws diagnose');
|
|
556
702
|
} catch (error) {
|