@paths.design/caws-cli 3.0.0 → 3.1.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 +295 -150
- package/dist/budget-derivation.d.ts +35 -0
- package/dist/budget-derivation.d.ts.map +1 -0
- package/dist/budget-derivation.js +204 -0
- package/dist/cicd-optimizer.d.ts +142 -0
- package/dist/cicd-optimizer.d.ts.map +1 -0
- package/dist/cicd-optimizer.js +504 -0
- package/dist/commands/burnup.d.ts +6 -0
- package/dist/commands/burnup.d.ts.map +1 -0
- package/dist/commands/burnup.js +90 -0
- package/dist/commands/init.d.ts +5 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +514 -0
- package/dist/commands/provenance.d.ts +22 -0
- package/dist/commands/provenance.d.ts.map +1 -0
- package/dist/commands/provenance.js +594 -0
- package/dist/commands/tool.d.ts +13 -0
- package/dist/commands/tool.d.ts.map +1 -0
- package/dist/commands/tool.js +138 -0
- package/dist/commands/validate.d.ts +7 -0
- package/dist/commands/validate.d.ts.map +1 -0
- package/dist/commands/validate.js +80 -0
- package/dist/config/index.d.ts +29 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +132 -0
- package/dist/error-handler.d.ts +50 -0
- package/dist/error-handler.d.ts.map +1 -0
- package/dist/error-handler.js +253 -0
- package/dist/generators/working-spec.d.ts +13 -0
- package/dist/generators/working-spec.d.ts.map +1 -0
- package/dist/generators/working-spec.js +204 -0
- package/dist/index-new.d.ts +5 -0
- package/dist/index-new.d.ts.map +1 -0
- package/dist/index-new.js +317 -0
- package/dist/index.d.ts +3 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +100 -1659
- package/dist/index.js.backup +4711 -0
- package/dist/scaffold/cursor-hooks.d.ts +7 -0
- package/dist/scaffold/cursor-hooks.d.ts.map +1 -0
- package/dist/scaffold/cursor-hooks.js +152 -0
- package/dist/scaffold/index.d.ts +20 -0
- package/dist/scaffold/index.d.ts.map +1 -0
- package/dist/scaffold/index.js +486 -0
- package/dist/test-analysis.d.ts +182 -0
- package/dist/test-analysis.d.ts.map +1 -0
- package/dist/test-analysis.js +580 -0
- package/dist/tool-interface.d.ts +236 -0
- package/dist/tool-interface.d.ts.map +1 -0
- package/dist/tool-interface.js +314 -0
- package/dist/tool-loader.d.ts +77 -0
- package/dist/tool-loader.d.ts.map +1 -0
- package/dist/tool-loader.js +298 -0
- package/dist/tool-validator.d.ts +72 -0
- package/dist/tool-validator.d.ts.map +1 -0
- package/dist/tool-validator.js +387 -0
- package/dist/utils/detection.d.ts +7 -0
- package/dist/utils/detection.d.ts.map +1 -0
- package/dist/utils/detection.js +174 -0
- package/dist/utils/finalization.d.ts +17 -0
- package/dist/utils/finalization.d.ts.map +1 -0
- package/dist/utils/finalization.js +229 -0
- package/dist/utils/project-analysis.d.ts +14 -0
- package/dist/utils/project-analysis.d.ts.map +1 -0
- package/dist/utils/project-analysis.js +105 -0
- package/dist/validation/spec-validation.d.ts +29 -0
- package/dist/validation/spec-validation.d.ts.map +1 -0
- package/dist/validation/spec-validation.js +376 -0
- package/dist/waivers-manager.d.ts +167 -0
- package/dist/waivers-manager.d.ts.map +1 -0
- package/dist/waivers-manager.js +549 -0
- package/package.json +10 -12
- package/templates/.cursor/README.md +311 -0
- package/templates/.cursor/hooks/audit.sh +55 -0
- package/templates/.cursor/hooks/block-dangerous.sh +77 -0
- package/templates/.cursor/hooks/caws-quality-check.sh +52 -0
- package/templates/.cursor/hooks/caws-scope-guard.sh +74 -0
- package/templates/.cursor/hooks/caws-tool-validation.sh +121 -0
- package/templates/.cursor/hooks/format.sh +38 -0
- package/templates/.cursor/hooks/naming-check.sh +64 -0
- package/templates/.cursor/hooks/scan-secrets.sh +46 -0
- package/templates/.cursor/hooks/scope-guard.sh +52 -0
- package/templates/.cursor/hooks/validate-spec.sh +38 -0
- package/templates/.cursor/hooks.json +59 -0
- package/templates/.github/copilot/instructions.md +311 -0
- package/templates/.idea/runConfigurations/CAWS_Evaluate.xml +5 -0
- package/templates/.idea/runConfigurations/CAWS_Validate.xml +5 -0
- package/templates/.vscode/launch.json +56 -0
- package/templates/.vscode/settings.json +93 -0
- package/templates/.windsurf/workflows/caws-guided-development.md +92 -0
- package/templates/apps/tools/caws/README.md +1 -1
- package/templates/apps/tools/caws/prompt-lint.js.backup +274 -0
- package/templates/apps/tools/caws/provenance.js.backup +73 -0
- package/templates/apps/tools/caws/schemas/working-spec.schema.json +21 -3
- package/templates/codemod/test.js +93 -1
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview CAWS Tool Validator - Security validation for dynamically loaded tools
|
|
5
|
+
* Validates tools against allowlists, scans for security violations, and ensures safe execution
|
|
6
|
+
* @author @darianrosebrook
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const crypto = require('crypto');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Tool Validator - Security validation and allowlist enforcement
|
|
15
|
+
*/
|
|
16
|
+
class ToolValidator {
|
|
17
|
+
constructor(options = {}) {
|
|
18
|
+
this.options = {
|
|
19
|
+
allowlistPath:
|
|
20
|
+
options.allowlistPath || path.join(process.cwd(), 'apps/tools/caws/tools-allow.json'),
|
|
21
|
+
strictMode: options.strictMode !== false,
|
|
22
|
+
maxFileSize: options.maxFileSize || 1024 * 1024, // 1MB
|
|
23
|
+
...options,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
this.allowlist = null;
|
|
27
|
+
this.validationCache = new Map();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Load and parse the tools allowlist
|
|
32
|
+
* @returns {Promise<Array<string>>} Array of allowed commands/patterns
|
|
33
|
+
*/
|
|
34
|
+
async loadAllowlist() {
|
|
35
|
+
if (this.allowlist) return this.allowlist;
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
if (!fs.existsSync(this.options.allowlistPath)) {
|
|
39
|
+
throw new Error(`Allowlist file not found: ${this.options.allowlistPath}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const content = await fs.promises.readFile(this.options.allowlistPath, 'utf8');
|
|
43
|
+
this.allowlist = JSON.parse(content);
|
|
44
|
+
|
|
45
|
+
if (!Array.isArray(this.allowlist)) {
|
|
46
|
+
throw new Error('Allowlist must be an array of strings');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return this.allowlist;
|
|
50
|
+
} catch (error) {
|
|
51
|
+
throw new Error(`Failed to load allowlist: ${error.message}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Validate a tool against security requirements
|
|
57
|
+
* @param {Object} tool - Tool object with module and metadata
|
|
58
|
+
* @returns {Promise<Object>} Validation result
|
|
59
|
+
*/
|
|
60
|
+
async validateTool(tool) {
|
|
61
|
+
const toolId = tool.metadata?.id || path.basename(tool.path, '.js');
|
|
62
|
+
const cacheKey = crypto
|
|
63
|
+
.createHash('md5')
|
|
64
|
+
.update(toolId + tool.loadedAt)
|
|
65
|
+
.digest('hex');
|
|
66
|
+
|
|
67
|
+
// Check cache first
|
|
68
|
+
if (this.validationCache.has(cacheKey)) {
|
|
69
|
+
return this.validationCache.get(cacheKey);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const result = {
|
|
73
|
+
valid: true,
|
|
74
|
+
checks: [],
|
|
75
|
+
warnings: [],
|
|
76
|
+
errors: [],
|
|
77
|
+
score: 100,
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
// Load allowlist if not loaded
|
|
82
|
+
await this.loadAllowlist();
|
|
83
|
+
|
|
84
|
+
// Run all validation checks
|
|
85
|
+
const checks = await Promise.allSettled([
|
|
86
|
+
this.checkFileSecurity(tool),
|
|
87
|
+
this.checkCodeSecurity(tool),
|
|
88
|
+
this.checkInterfaceCompliance(tool),
|
|
89
|
+
this.checkMetadataValidity(tool),
|
|
90
|
+
this.checkDependencySafety(tool),
|
|
91
|
+
]);
|
|
92
|
+
|
|
93
|
+
// Process check results
|
|
94
|
+
checks.forEach((check, index) => {
|
|
95
|
+
const checkName = [
|
|
96
|
+
'fileSecurity',
|
|
97
|
+
'codeSecurity',
|
|
98
|
+
'interfaceCompliance',
|
|
99
|
+
'metadataValidity',
|
|
100
|
+
'dependencySafety',
|
|
101
|
+
][index];
|
|
102
|
+
|
|
103
|
+
if (check.status === 'fulfilled') {
|
|
104
|
+
const checkResult = check.value;
|
|
105
|
+
result.checks.push({
|
|
106
|
+
name: checkName,
|
|
107
|
+
passed: checkResult.passed,
|
|
108
|
+
message: checkResult.message,
|
|
109
|
+
severity: checkResult.severity || 'info',
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
if (!checkResult.passed) {
|
|
113
|
+
result.valid = false;
|
|
114
|
+
if (checkResult.severity === 'error') {
|
|
115
|
+
result.errors.push(checkResult.message);
|
|
116
|
+
result.score -= 20;
|
|
117
|
+
} else {
|
|
118
|
+
result.warnings.push(checkResult.message);
|
|
119
|
+
result.score -= 5;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
} else {
|
|
123
|
+
result.checks.push({
|
|
124
|
+
name: checkName,
|
|
125
|
+
passed: false,
|
|
126
|
+
message: `Check failed: ${check.reason.message}`,
|
|
127
|
+
severity: 'error',
|
|
128
|
+
});
|
|
129
|
+
result.valid = false;
|
|
130
|
+
result.errors.push(check.reason.message);
|
|
131
|
+
result.score -= 20;
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// Cache result
|
|
136
|
+
this.validationCache.set(cacheKey, result);
|
|
137
|
+
} catch (error) {
|
|
138
|
+
result.valid = false;
|
|
139
|
+
result.errors.push(`Validation failed: ${error.message}`);
|
|
140
|
+
result.score = 0;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return result;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Check file-level security
|
|
148
|
+
* @private
|
|
149
|
+
* @param {Object} tool - Tool object
|
|
150
|
+
*/
|
|
151
|
+
async checkFileSecurity(tool) {
|
|
152
|
+
const issues = [];
|
|
153
|
+
|
|
154
|
+
try {
|
|
155
|
+
const stats = await fs.promises.stat(tool.path);
|
|
156
|
+
|
|
157
|
+
// Check file size
|
|
158
|
+
if (stats.size > this.options.maxFileSize) {
|
|
159
|
+
issues.push(`File too large: ${stats.size} bytes > ${this.options.maxFileSize} bytes`);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Check file permissions (should be readable)
|
|
163
|
+
const mode = stats.mode;
|
|
164
|
+
if (!(mode & parseInt('0444', 8))) {
|
|
165
|
+
// Owner, group, others can read
|
|
166
|
+
issues.push('File permissions too restrictive');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Check if file is executable (should not be)
|
|
170
|
+
if (mode & parseInt('0111', 8)) {
|
|
171
|
+
// Execute permissions
|
|
172
|
+
issues.push('Tool file should not have execute permissions');
|
|
173
|
+
}
|
|
174
|
+
} catch (error) {
|
|
175
|
+
issues.push(`File access error: ${error.message}`);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
passed: issues.length === 0,
|
|
180
|
+
message: issues.length > 0 ? issues.join('; ') : 'File security check passed',
|
|
181
|
+
severity: issues.length > 0 ? 'error' : 'info',
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Check code-level security
|
|
187
|
+
* @private
|
|
188
|
+
* @param {Object} tool - Tool object
|
|
189
|
+
*/
|
|
190
|
+
async checkCodeSecurity(tool) {
|
|
191
|
+
const issues = [];
|
|
192
|
+
|
|
193
|
+
try {
|
|
194
|
+
const content = await fs.promises.readFile(tool.path, 'utf8');
|
|
195
|
+
|
|
196
|
+
// Check for dangerous patterns
|
|
197
|
+
const dangerousPatterns = [
|
|
198
|
+
{ pattern: /require\(['"`]child_process['"`]\)/g, message: 'Direct child_process usage' },
|
|
199
|
+
{ pattern: /require\(['"`]fs['"`]\)\.writeFileSync/g, message: 'Synchronous file writing' },
|
|
200
|
+
{ pattern: /process\.exit\(/g, message: 'Process termination' },
|
|
201
|
+
{ pattern: /eval\(/g, message: 'Code evaluation' },
|
|
202
|
+
{ pattern: /Function\(['"`]/g, message: 'Dynamic function creation' },
|
|
203
|
+
{ pattern: /require\(['"`]\.\./g, message: 'Directory traversal in require' },
|
|
204
|
+
];
|
|
205
|
+
|
|
206
|
+
dangerousPatterns.forEach(({ pattern, message }) => {
|
|
207
|
+
const matches = content.match(pattern);
|
|
208
|
+
if (matches) {
|
|
209
|
+
issues.push(`${message} (${matches.length} occurrences)`);
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// Check for secrets (basic pattern matching)
|
|
214
|
+
const secretPatterns = [
|
|
215
|
+
/password\s*[=:]\s*['"`][^'"]{8,}['"`]/gi,
|
|
216
|
+
/token\s*[=:]\s*['"`][^'"]{20,}['"`]/gi,
|
|
217
|
+
/key\s*[=:]\s*['"`][^'"]{16,}['"`]/gi,
|
|
218
|
+
/secret\s*[=:]\s*['"`][^'"]{16,}['"`]/gi,
|
|
219
|
+
];
|
|
220
|
+
|
|
221
|
+
secretPatterns.forEach((pattern) => {
|
|
222
|
+
if (content.match(pattern)) {
|
|
223
|
+
issues.push('Potential hardcoded secrets detected');
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
} catch (error) {
|
|
227
|
+
issues.push(`Code analysis error: ${error.message}`);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
passed: issues.length === 0,
|
|
232
|
+
message:
|
|
233
|
+
issues.length > 0 ? `Security issues: ${issues.join('; ')}` : 'Code security check passed',
|
|
234
|
+
severity: issues.length > 0 ? 'error' : 'info',
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Check interface compliance
|
|
240
|
+
* @private
|
|
241
|
+
* @param {Object} tool - Tool object
|
|
242
|
+
*/
|
|
243
|
+
async checkInterfaceCompliance(tool) {
|
|
244
|
+
const requiredMethods = ['execute', 'getMetadata'];
|
|
245
|
+
const missingMethods = [];
|
|
246
|
+
|
|
247
|
+
requiredMethods.forEach((method) => {
|
|
248
|
+
if (typeof tool.module[method] !== 'function') {
|
|
249
|
+
missingMethods.push(method);
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
return {
|
|
254
|
+
passed: missingMethods.length === 0,
|
|
255
|
+
message:
|
|
256
|
+
missingMethods.length > 0
|
|
257
|
+
? `Missing required methods: ${missingMethods.join(', ')}`
|
|
258
|
+
: 'Interface compliance check passed',
|
|
259
|
+
severity: missingMethods.length > 0 ? 'error' : 'info',
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Check metadata validity
|
|
265
|
+
* @private
|
|
266
|
+
* @param {Object} tool - Tool object
|
|
267
|
+
*/
|
|
268
|
+
async checkMetadataValidity(tool) {
|
|
269
|
+
const metadata = tool.metadata || {};
|
|
270
|
+
const requiredFields = ['id', 'name', 'version'];
|
|
271
|
+
const missingFields = [];
|
|
272
|
+
const invalidFields = [];
|
|
273
|
+
|
|
274
|
+
// Check required fields
|
|
275
|
+
requiredFields.forEach((field) => {
|
|
276
|
+
if (!metadata[field]) {
|
|
277
|
+
missingFields.push(field);
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// Validate field types and formats
|
|
282
|
+
if (metadata.id && typeof metadata.id !== 'string') {
|
|
283
|
+
invalidFields.push('id must be string');
|
|
284
|
+
}
|
|
285
|
+
if (metadata.name && typeof metadata.name !== 'string') {
|
|
286
|
+
invalidFields.push('name must be string');
|
|
287
|
+
}
|
|
288
|
+
if (metadata.version && typeof metadata.version !== 'string') {
|
|
289
|
+
invalidFields.push('version must be string');
|
|
290
|
+
}
|
|
291
|
+
if (metadata.capabilities && !Array.isArray(metadata.capabilities)) {
|
|
292
|
+
invalidFields.push('capabilities must be array');
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const issues = [...missingFields.map((f) => `missing ${f}`), ...invalidFields];
|
|
296
|
+
|
|
297
|
+
return {
|
|
298
|
+
passed: issues.length === 0,
|
|
299
|
+
message:
|
|
300
|
+
issues.length > 0
|
|
301
|
+
? `Metadata issues: ${issues.join(', ')}`
|
|
302
|
+
: 'Metadata validity check passed',
|
|
303
|
+
severity: issues.length > 0 ? 'error' : 'info',
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Check dependency safety
|
|
309
|
+
* @private
|
|
310
|
+
* @param {Object} tool - Tool object
|
|
311
|
+
*/
|
|
312
|
+
async checkDependencySafety(tool) {
|
|
313
|
+
const metadata = tool.metadata || {};
|
|
314
|
+
const issues = [];
|
|
315
|
+
|
|
316
|
+
if (metadata.dependencies) {
|
|
317
|
+
if (!Array.isArray(metadata.dependencies)) {
|
|
318
|
+
issues.push('dependencies must be array');
|
|
319
|
+
} else {
|
|
320
|
+
// Check for potentially unsafe dependencies
|
|
321
|
+
const unsafeDeps = ['child_process', 'fs-extra', 'execa', 'shelljs'];
|
|
322
|
+
const foundUnsafe = metadata.dependencies.filter((dep) =>
|
|
323
|
+
unsafeDeps.some((unsafe) => dep.includes(unsafe))
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
if (foundUnsafe.length > 0) {
|
|
327
|
+
issues.push(`Potentially unsafe dependencies: ${foundUnsafe.join(', ')}`);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return {
|
|
333
|
+
passed: issues.length === 0,
|
|
334
|
+
message:
|
|
335
|
+
issues.length > 0
|
|
336
|
+
? `Dependency issues: ${issues.join('; ')}`
|
|
337
|
+
: 'Dependency safety check passed',
|
|
338
|
+
severity: issues.length > 0 ? 'warning' : 'info',
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Validate a command against the allowlist
|
|
344
|
+
* @param {string} command - Command to validate
|
|
345
|
+
* @returns {boolean} True if command is allowed
|
|
346
|
+
*/
|
|
347
|
+
async validateCommand(command) {
|
|
348
|
+
const allowlist = await this.loadAllowlist();
|
|
349
|
+
|
|
350
|
+
// Check exact matches first
|
|
351
|
+
if (allowlist.includes(command)) {
|
|
352
|
+
return true;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Check pattern matches
|
|
356
|
+
return allowlist.some((allowed) => {
|
|
357
|
+
if (allowed.includes('*')) {
|
|
358
|
+
// Simple wildcard matching
|
|
359
|
+
const regex = new RegExp(allowed.replace(/\*/g, '.*'));
|
|
360
|
+
return regex.test(command);
|
|
361
|
+
}
|
|
362
|
+
return false;
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Clear validation cache
|
|
368
|
+
*/
|
|
369
|
+
clearCache() {
|
|
370
|
+
this.validationCache.clear();
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Get validator statistics
|
|
375
|
+
* @returns {Object} Statistics object
|
|
376
|
+
*/
|
|
377
|
+
getStats() {
|
|
378
|
+
return {
|
|
379
|
+
allowlistLoaded: this.allowlist !== null,
|
|
380
|
+
allowlistSize: this.allowlist?.length || 0,
|
|
381
|
+
cacheSize: this.validationCache.size,
|
|
382
|
+
strictMode: this.options.strictMode,
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
module.exports = ToolValidator;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detection.d.ts","sourceRoot":"","sources":["../../src/utils/detection.js"],"names":[],"mappings":"AAUA;;;;GAIG;AACH,sCAHW,MAAM,OA6JhB"}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview CAWS Setup Detection Utilities
|
|
3
|
+
* Functions for detecting and analyzing CAWS project setups
|
|
4
|
+
* @author @darianrosebrook
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs-extra');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const chalk = require('chalk');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Detect CAWS setup in a directory
|
|
13
|
+
* @param {string} cwd - Current working directory
|
|
14
|
+
* @returns {Object} Setup information
|
|
15
|
+
*/
|
|
16
|
+
function detectCAWSSetup(cwd = process.cwd()) {
|
|
17
|
+
// Skip logging for version/help commands
|
|
18
|
+
const isQuietCommand =
|
|
19
|
+
process.argv.includes('--version') ||
|
|
20
|
+
process.argv.includes('-V') ||
|
|
21
|
+
process.argv.includes('--help');
|
|
22
|
+
|
|
23
|
+
if (!isQuietCommand) {
|
|
24
|
+
console.log(chalk.blue('🔍 Detecting CAWS setup...'));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Check for existing CAWS setup
|
|
28
|
+
const cawsDir = path.join(cwd, '.caws');
|
|
29
|
+
const hasCAWSDir = fs.existsSync(cawsDir);
|
|
30
|
+
|
|
31
|
+
if (!hasCAWSDir) {
|
|
32
|
+
if (!isQuietCommand) {
|
|
33
|
+
console.log(chalk.gray('ℹ️ No .caws directory found - new project setup'));
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
type: 'new',
|
|
37
|
+
hasCAWSDir: false,
|
|
38
|
+
cawsDir: null,
|
|
39
|
+
capabilities: [],
|
|
40
|
+
hasTemplateDir: false,
|
|
41
|
+
templateDir: null,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Analyze existing setup
|
|
46
|
+
const files = fs.readdirSync(cawsDir);
|
|
47
|
+
const hasWorkingSpec = fs.existsSync(path.join(cawsDir, 'working-spec.yaml'));
|
|
48
|
+
const hasValidateScript = fs.existsSync(path.join(cawsDir, 'validate.js'));
|
|
49
|
+
const hasPolicy = fs.existsSync(path.join(cawsDir, 'policy'));
|
|
50
|
+
const hasSchemas = fs.existsSync(path.join(cawsDir, 'schemas'));
|
|
51
|
+
const hasTemplates = fs.existsSync(path.join(cawsDir, 'templates'));
|
|
52
|
+
|
|
53
|
+
// Check for multiple spec files (enhanced project pattern)
|
|
54
|
+
const specFiles = files.filter((f) => f.endsWith('-spec.yaml'));
|
|
55
|
+
const hasMultipleSpecs = specFiles.length > 1;
|
|
56
|
+
|
|
57
|
+
// Check for tools directory (enhanced setup)
|
|
58
|
+
const toolsDir = path.join(cwd, 'apps/tools/caws');
|
|
59
|
+
const hasTools = fs.existsSync(toolsDir);
|
|
60
|
+
|
|
61
|
+
// Determine setup type
|
|
62
|
+
let setupType = 'basic';
|
|
63
|
+
let capabilities = [];
|
|
64
|
+
|
|
65
|
+
if (hasMultipleSpecs && hasWorkingSpec) {
|
|
66
|
+
setupType = 'enhanced';
|
|
67
|
+
capabilities.push('multiple-specs', 'working-spec', 'domain-specific');
|
|
68
|
+
} else if (hasWorkingSpec) {
|
|
69
|
+
setupType = 'standard';
|
|
70
|
+
capabilities.push('working-spec');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (hasValidateScript) {
|
|
74
|
+
capabilities.push('validation');
|
|
75
|
+
}
|
|
76
|
+
if (hasPolicy) {
|
|
77
|
+
capabilities.push('policies');
|
|
78
|
+
}
|
|
79
|
+
if (hasSchemas) {
|
|
80
|
+
capabilities.push('schemas');
|
|
81
|
+
}
|
|
82
|
+
if (hasTemplates) {
|
|
83
|
+
capabilities.push('templates');
|
|
84
|
+
}
|
|
85
|
+
if (hasTools) {
|
|
86
|
+
capabilities.push('tools');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (!isQuietCommand) {
|
|
90
|
+
console.log(chalk.green(`✅ Detected ${setupType} CAWS setup`));
|
|
91
|
+
console.log(chalk.gray(` Capabilities: ${capabilities.join(', ')}`));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Check for template directory - try multiple possible locations
|
|
95
|
+
let templateDir = null;
|
|
96
|
+
const possibleTemplatePaths = [
|
|
97
|
+
// FIRST: Try bundled templates (for npm-installed CLI)
|
|
98
|
+
{ path: path.resolve(__dirname, '../templates'), source: 'bundled with CLI' },
|
|
99
|
+
{ path: path.resolve(__dirname, 'templates'), source: 'bundled with CLI (fallback)' },
|
|
100
|
+
// Try relative to current working directory (for monorepo setups)
|
|
101
|
+
{ path: path.resolve(cwd, '../caws-template'), source: 'monorepo parent directory' },
|
|
102
|
+
{ path: path.resolve(cwd, '../../caws-template'), source: 'monorepo grandparent' },
|
|
103
|
+
{ path: path.resolve(cwd, '../../../caws-template'), source: 'workspace root' },
|
|
104
|
+
{ path: path.resolve(cwd, 'packages/caws-template'), source: 'packages/ subdirectory' },
|
|
105
|
+
{ path: path.resolve(cwd, 'caws-template'), source: 'caws-template/ subdirectory' },
|
|
106
|
+
// Try relative to CLI location (for installed CLI)
|
|
107
|
+
{ path: path.resolve(__dirname, '../caws-template'), source: 'CLI installation' },
|
|
108
|
+
{ path: path.resolve(__dirname, '../../caws-template'), source: 'CLI parent directory' },
|
|
109
|
+
{ path: path.resolve(__dirname, '../../../caws-template'), source: 'CLI workspace root' },
|
|
110
|
+
// Try absolute paths for CI environments
|
|
111
|
+
{ path: path.resolve(process.cwd(), 'packages/caws-template'), source: 'current packages/' },
|
|
112
|
+
{ path: path.resolve(process.cwd(), '../packages/caws-template'), source: 'parent packages/' },
|
|
113
|
+
{
|
|
114
|
+
path: path.resolve(process.cwd(), '../../packages/caws-template'),
|
|
115
|
+
source: 'grandparent packages/',
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
path: path.resolve(process.cwd(), '../../../packages/caws-template'),
|
|
119
|
+
source: 'workspace packages/',
|
|
120
|
+
},
|
|
121
|
+
// Try from workspace root
|
|
122
|
+
{ path: path.resolve(process.cwd(), 'caws-template'), source: 'workspace caws-template/' },
|
|
123
|
+
// Try various other common locations
|
|
124
|
+
{
|
|
125
|
+
path: '/home/runner/work/coding-agent-working-standard/coding-agent-working-standard/packages/caws-template',
|
|
126
|
+
source: 'GitHub Actions CI',
|
|
127
|
+
},
|
|
128
|
+
{ path: '/workspace/packages/caws-template', source: 'Docker workspace' },
|
|
129
|
+
{ path: '/caws/packages/caws-template', source: 'Container workspace' },
|
|
130
|
+
];
|
|
131
|
+
|
|
132
|
+
for (const { path: testPath, source } of possibleTemplatePaths) {
|
|
133
|
+
if (fs.existsSync(testPath)) {
|
|
134
|
+
templateDir = testPath;
|
|
135
|
+
if (!isQuietCommand) {
|
|
136
|
+
console.log(`✅ Found CAWS templates in ${source}:`);
|
|
137
|
+
console.log(` ${chalk.gray(testPath)}`);
|
|
138
|
+
}
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (!templateDir && !isQuietCommand) {
|
|
144
|
+
console.warn(chalk.yellow('⚠️ CAWS templates not found in standard locations'));
|
|
145
|
+
console.warn(chalk.blue('💡 This may limit available scaffolding features'));
|
|
146
|
+
console.warn(
|
|
147
|
+
chalk.blue('💡 For full functionality, ensure caws-template package is available')
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const hasTemplateDir = templateDir !== null;
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
type: setupType,
|
|
155
|
+
hasCAWSDir: true,
|
|
156
|
+
cawsDir,
|
|
157
|
+
hasWorkingSpec,
|
|
158
|
+
hasMultipleSpecs,
|
|
159
|
+
hasValidateScript,
|
|
160
|
+
hasPolicy,
|
|
161
|
+
hasSchemas,
|
|
162
|
+
hasTemplates,
|
|
163
|
+
hasTools,
|
|
164
|
+
hasTemplateDir,
|
|
165
|
+
templateDir,
|
|
166
|
+
capabilities,
|
|
167
|
+
isEnhanced: setupType === 'enhanced',
|
|
168
|
+
isAdvanced: hasTools || hasValidateScript,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
module.exports = {
|
|
173
|
+
detectCAWSSetup,
|
|
174
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate provenance manifest and git initialization (for both modes)
|
|
3
|
+
* @param {string} projectName - Project name
|
|
4
|
+
* @param {Object} options - Command options
|
|
5
|
+
* @param {Object} answers - User answers
|
|
6
|
+
*/
|
|
7
|
+
export function finalizeProject(projectName: string, options: any, answers: any): Promise<void>;
|
|
8
|
+
/**
|
|
9
|
+
* Display success message after project initialization
|
|
10
|
+
*/
|
|
11
|
+
export function continueToSuccess(): void;
|
|
12
|
+
/**
|
|
13
|
+
* Set dependencies for finalization utilities
|
|
14
|
+
* @param {Object} deps - Dependencies object
|
|
15
|
+
*/
|
|
16
|
+
export function setFinalizationDependencies(deps: any): void;
|
|
17
|
+
//# sourceMappingURL=finalization.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"finalization.d.ts","sourceRoot":"","sources":["../../src/utils/finalization.js"],"names":[],"mappings":"AA6BA;;;;;GAKG;AACH,6CAJW,MAAM,6CAgKhB;AAED;;GAEG;AACH,0CA0BC;AA1MD;;;GAGG;AACH,6DAGC"}
|