@paths.design/caws-cli 7.0.1 → 7.0.3
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/dist/budget-derivation.js +5 -4
- package/dist/commands/diagnose.js +26 -20
- package/dist/commands/init.js +72 -5
- package/dist/commands/specs.js +40 -1
- package/dist/commands/status.js +2 -2
- package/dist/commands/templates.js +10 -0
- package/dist/commands/tool.js +2 -3
- package/dist/commands/validate.js +12 -0
- package/dist/config/index.js +17 -8
- package/dist/generators/working-spec.js +42 -9
- package/dist/index.js +3 -1
- package/dist/scaffold/cursor-hooks.js +10 -2
- package/dist/scaffold/git-hooks.js +189 -32
- package/dist/scaffold/index.js +105 -17
- package/dist/templates/.caws/tools/README.md +20 -0
- package/dist/templates/.cursor/README.md +311 -0
- package/dist/templates/.cursor/hooks/audit.sh +55 -0
- package/dist/templates/.cursor/hooks/block-dangerous.sh +83 -0
- package/dist/templates/.cursor/hooks/caws-quality-check.sh +52 -0
- package/dist/templates/.cursor/hooks/caws-scope-guard.sh +130 -0
- package/dist/templates/.cursor/hooks/caws-tool-validation.sh +121 -0
- package/dist/templates/.cursor/hooks/format.sh +38 -0
- package/dist/templates/.cursor/hooks/naming-check.sh +64 -0
- package/dist/templates/.cursor/hooks/scan-secrets.sh +46 -0
- package/dist/templates/.cursor/hooks/scope-guard.sh +52 -0
- package/dist/templates/.cursor/hooks/validate-spec.sh +83 -0
- package/dist/templates/.cursor/hooks.json +59 -0
- package/dist/templates/.cursor/rules/00-claims-verification.mdc +144 -0
- package/dist/templates/.cursor/rules/01-working-style.mdc +50 -0
- package/dist/templates/.cursor/rules/02-quality-gates.mdc +370 -0
- package/dist/templates/.cursor/rules/03-naming-and-refactor.mdc +33 -0
- package/dist/templates/.cursor/rules/04-logging-language-style.mdc +23 -0
- package/dist/templates/.cursor/rules/05-safe-defaults-guards.mdc +23 -0
- package/dist/templates/.cursor/rules/06-typescript-conventions.mdc +36 -0
- package/dist/templates/.cursor/rules/07-process-ops.mdc +20 -0
- package/dist/templates/.cursor/rules/08-solid-and-architecture.mdc +16 -0
- package/dist/templates/.cursor/rules/09-docstrings.mdc +89 -0
- package/dist/templates/.cursor/rules/10-documentation-quality-standards.mdc +390 -0
- package/dist/templates/.cursor/rules/11-scope-management-waivers.mdc +385 -0
- package/dist/templates/.cursor/rules/12-implementation-completeness.mdc +516 -0
- package/dist/templates/.cursor/rules/13-language-agnostic-standards.mdc +588 -0
- package/dist/templates/.cursor/rules/README.md +148 -0
- package/dist/templates/.github/copilot/instructions.md +311 -0
- package/dist/templates/.idea/runConfigurations/CAWS_Evaluate.xml +5 -0
- package/dist/templates/.idea/runConfigurations/CAWS_Validate.xml +5 -0
- package/dist/templates/.vscode/launch.json +56 -0
- package/dist/templates/.vscode/settings.json +93 -0
- package/dist/templates/.windsurf/workflows/caws-guided-development.md +92 -0
- package/dist/templates/COMMIT_CONVENTIONS.md +86 -0
- package/dist/templates/OIDC_SETUP.md +300 -0
- package/dist/templates/agents.md +1047 -0
- package/dist/templates/codemod/README.md +1 -0
- package/dist/templates/codemod/test.js +93 -0
- package/dist/templates/docs/README.md +150 -0
- package/dist/templates/scripts/quality-gates/check-god-objects.js +146 -0
- package/dist/templates/scripts/quality-gates/run-quality-gates.js +50 -0
- package/dist/templates/scripts/v3/analysis/todo_analyzer.py +1997 -0
- package/dist/tool-loader.js +6 -1
- package/dist/tool-validator.js +8 -2
- package/dist/utils/detection.js +34 -6
- package/dist/utils/git-lock.js +118 -0
- package/dist/utils/gitignore-updater.js +148 -0
- package/dist/utils/quality-gates.js +47 -7
- package/dist/utils/spec-resolver.js +23 -3
- package/dist/utils/yaml-validation.js +155 -0
- package/dist/validation/spec-validation.js +105 -2
- package/package.json +2 -2
- package/templates/.caws/schemas/waivers.schema.json +30 -0
- package/templates/.caws/schemas/working-spec.schema.json +133 -0
- package/templates/.caws/templates/working-spec.template.yml +74 -0
- package/templates/.caws/tools/README.md +20 -0
- package/templates/.caws/tools/scope-guard.js +208 -0
- package/templates/.caws/tools-allow.json +331 -0
- package/templates/.caws/waivers.yml +19 -0
- package/templates/.cursor/hooks/scope-guard.sh +2 -2
- package/templates/.cursor/hooks/validate-spec.sh +42 -7
- package/templates/apps/tools/caws/COMPLETION_REPORT.md +0 -331
- package/templates/apps/tools/caws/MIGRATION_SUMMARY.md +0 -360
- package/templates/apps/tools/caws/README.md +0 -463
- package/templates/apps/tools/caws/TEST_STATUS.md +0 -365
- package/templates/apps/tools/caws/attest.js +0 -357
- package/templates/apps/tools/caws/ci-optimizer.js +0 -642
- package/templates/apps/tools/caws/config.ts +0 -245
- package/templates/apps/tools/caws/cross-functional.js +0 -876
- package/templates/apps/tools/caws/dashboard.js +0 -1112
- package/templates/apps/tools/caws/flake-detector.ts +0 -362
- package/templates/apps/tools/caws/gates.js +0 -198
- package/templates/apps/tools/caws/gates.ts +0 -271
- package/templates/apps/tools/caws/language-adapters.ts +0 -381
- package/templates/apps/tools/caws/language-support.d.ts +0 -367
- package/templates/apps/tools/caws/language-support.d.ts.map +0 -1
- package/templates/apps/tools/caws/language-support.js +0 -585
- package/templates/apps/tools/caws/legacy-assessment.ts +0 -408
- package/templates/apps/tools/caws/legacy-assessor.js +0 -764
- package/templates/apps/tools/caws/mutant-analyzer.js +0 -734
- package/templates/apps/tools/caws/perf-budgets.ts +0 -349
- package/templates/apps/tools/caws/prompt-lint.js.backup +0 -274
- package/templates/apps/tools/caws/property-testing.js +0 -707
- package/templates/apps/tools/caws/provenance.d.ts +0 -14
- package/templates/apps/tools/caws/provenance.d.ts.map +0 -1
- package/templates/apps/tools/caws/provenance.js +0 -132
- package/templates/apps/tools/caws/provenance.js.backup +0 -73
- package/templates/apps/tools/caws/provenance.ts +0 -211
- package/templates/apps/tools/caws/security-provenance.ts +0 -483
- package/templates/apps/tools/caws/shared/base-tool.ts +0 -281
- package/templates/apps/tools/caws/shared/config-manager.ts +0 -366
- package/templates/apps/tools/caws/shared/gate-checker.ts +0 -849
- package/templates/apps/tools/caws/shared/types.ts +0 -444
- package/templates/apps/tools/caws/shared/validator.ts +0 -305
- package/templates/apps/tools/caws/shared/waivers-manager.ts +0 -174
- package/templates/apps/tools/caws/spec-test-mapper.ts +0 -391
- package/templates/apps/tools/caws/test-quality.js +0 -578
- package/templates/apps/tools/caws/validate.js +0 -76
- package/templates/apps/tools/caws/validate.ts +0 -228
- package/templates/apps/tools/caws/waivers.js +0 -344
- /package/{templates/apps/tools/caws → dist/templates/.caws}/schemas/waivers.schema.json +0 -0
- /package/{templates/apps/tools/caws → dist/templates/.caws}/schemas/working-spec.schema.json +0 -0
- /package/{templates/apps/tools/caws → dist/templates/.caws}/templates/working-spec.template.yml +0 -0
- /package/{templates/apps/tools/caws → dist/templates/.caws/tools}/scope-guard.js +0 -0
- /package/{templates/apps/tools/caws → dist/templates/.caws}/tools-allow.json +0 -0
- /package/{templates/apps/tools/caws → dist/templates/.caws}/waivers.yml +0 -0
package/dist/tool-loader.js
CHANGED
|
@@ -19,8 +19,13 @@ const { safeAsync } = require('./error-handler');
|
|
|
19
19
|
class ToolLoader extends EventEmitter {
|
|
20
20
|
constructor(options = {}) {
|
|
21
21
|
super();
|
|
22
|
+
// Check new location first, fall back to legacy location
|
|
23
|
+
const newToolsDir = path.join(process.cwd(), '.caws/tools');
|
|
24
|
+
const legacyToolsDir = path.join(process.cwd(), 'apps/tools/caws');
|
|
25
|
+
const defaultToolsDir = fs.existsSync(newToolsDir) ? newToolsDir : legacyToolsDir;
|
|
26
|
+
|
|
22
27
|
this.options = {
|
|
23
|
-
toolsDir: options.toolsDir ||
|
|
28
|
+
toolsDir: options.toolsDir || defaultToolsDir,
|
|
24
29
|
cacheEnabled: options.cacheEnabled !== false,
|
|
25
30
|
timeout: options.timeout || 10000,
|
|
26
31
|
maxTools: options.maxTools || 50,
|
package/dist/tool-validator.js
CHANGED
|
@@ -15,9 +15,15 @@ const crypto = require('crypto');
|
|
|
15
15
|
*/
|
|
16
16
|
class ToolValidator {
|
|
17
17
|
constructor(options = {}) {
|
|
18
|
+
// Check new location first, fall back to legacy location
|
|
19
|
+
const newAllowlistPath = path.join(process.cwd(), '.caws/tools-allow.json');
|
|
20
|
+
const legacyAllowlistPath = path.join(process.cwd(), 'apps/tools/caws/tools-allow.json');
|
|
21
|
+
const defaultAllowlistPath = fs.existsSync(newAllowlistPath)
|
|
22
|
+
? newAllowlistPath
|
|
23
|
+
: legacyAllowlistPath;
|
|
24
|
+
|
|
18
25
|
this.options = {
|
|
19
|
-
allowlistPath:
|
|
20
|
-
options.allowlistPath || path.join(process.cwd(), 'apps/tools/caws/tools-allow.json'),
|
|
26
|
+
allowlistPath: options.allowlistPath || defaultAllowlistPath,
|
|
21
27
|
strictMode: options.strictMode !== false,
|
|
22
28
|
maxFileSize: options.maxFileSize || 1024 * 1024, // 1MB
|
|
23
29
|
...options,
|
package/dist/utils/detection.js
CHANGED
|
@@ -8,6 +8,25 @@ const fs = require('fs-extra');
|
|
|
8
8
|
const path = require('path');
|
|
9
9
|
const chalk = require('chalk');
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Find the package root directory by looking for package.json
|
|
13
|
+
* Works in both development (src/) and production (dist/) scenarios
|
|
14
|
+
* @param {string} startDir - Directory to start searching from (defaults to __dirname)
|
|
15
|
+
* @returns {string} Package root directory path
|
|
16
|
+
*/
|
|
17
|
+
function findPackageRoot(startDir = __dirname) {
|
|
18
|
+
let packageRoot = startDir;
|
|
19
|
+
for (let i = 0; i < 5; i++) {
|
|
20
|
+
const packageJsonPath = path.join(packageRoot, 'package.json');
|
|
21
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
22
|
+
return packageRoot;
|
|
23
|
+
}
|
|
24
|
+
packageRoot = path.dirname(packageRoot);
|
|
25
|
+
}
|
|
26
|
+
// Fallback to startDir if package.json not found
|
|
27
|
+
return startDir;
|
|
28
|
+
}
|
|
29
|
+
|
|
11
30
|
/**
|
|
12
31
|
* Detect CAWS setup in a directory
|
|
13
32
|
* @param {string} cwd - Current working directory
|
|
@@ -54,9 +73,10 @@ function detectCAWSSetup(cwd = process.cwd()) {
|
|
|
54
73
|
const specFiles = files.filter((f) => f.endsWith('-spec.yaml'));
|
|
55
74
|
const hasMultipleSpecs = specFiles.length > 1;
|
|
56
75
|
|
|
57
|
-
// Check for tools directory (enhanced setup)
|
|
58
|
-
const toolsDir = path.join(cwd, '
|
|
59
|
-
const
|
|
76
|
+
// Check for tools directory (enhanced setup) - check new location first
|
|
77
|
+
const toolsDir = path.join(cwd, '.caws/tools');
|
|
78
|
+
const legacyToolsDir = path.join(cwd, 'apps/tools/caws');
|
|
79
|
+
const hasTools = fs.existsSync(toolsDir) || fs.existsSync(legacyToolsDir);
|
|
60
80
|
|
|
61
81
|
// Determine setup type
|
|
62
82
|
let setupType = 'basic';
|
|
@@ -93,10 +113,17 @@ function detectCAWSSetup(cwd = process.cwd()) {
|
|
|
93
113
|
|
|
94
114
|
// Check for template directory - try multiple possible locations
|
|
95
115
|
let templateDir = null;
|
|
116
|
+
|
|
117
|
+
// Find package root using shared utility
|
|
118
|
+
const packageRoot = findPackageRoot(__dirname);
|
|
119
|
+
|
|
96
120
|
const possibleTemplatePaths = [
|
|
97
|
-
// FIRST: Try bundled templates (
|
|
98
|
-
{ path: path.
|
|
99
|
-
|
|
121
|
+
// FIRST: Try bundled templates relative to package root (works in dev and global install)
|
|
122
|
+
{ path: path.join(packageRoot, 'templates'), source: 'bundled with CLI (package root)' },
|
|
123
|
+
// Fallback: Try relative to current file location (for development)
|
|
124
|
+
{ path: path.resolve(__dirname, '../../templates'), source: 'bundled with CLI (dev fallback)' },
|
|
125
|
+
{ path: path.resolve(__dirname, '../templates'), source: 'bundled with CLI (legacy fallback)' },
|
|
126
|
+
{ path: path.resolve(__dirname, 'templates'), source: 'bundled with CLI (legacy fallback 2)' },
|
|
100
127
|
// Try relative to current working directory (for monorepo setups)
|
|
101
128
|
{ path: path.resolve(cwd, '../caws-template'), source: 'monorepo parent directory' },
|
|
102
129
|
{ path: path.resolve(cwd, '../../caws-template'), source: 'monorepo grandparent' },
|
|
@@ -171,4 +198,5 @@ function detectCAWSSetup(cwd = process.cwd()) {
|
|
|
171
198
|
|
|
172
199
|
module.exports = {
|
|
173
200
|
detectCAWSSetup,
|
|
201
|
+
findPackageRoot,
|
|
174
202
|
};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Git Lock Detection Utilities
|
|
3
|
+
* Functions for detecting and handling git locks
|
|
4
|
+
* @author @darianrosebrook
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs-extra');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Check for git lock files
|
|
12
|
+
* @param {string} projectRoot - Project root directory
|
|
13
|
+
* @returns {Object} Lock status information
|
|
14
|
+
*/
|
|
15
|
+
function checkGitLock(projectRoot) {
|
|
16
|
+
const lockFile = path.join(projectRoot, '.git', 'index.lock');
|
|
17
|
+
const headLockFile = path.join(projectRoot, '.git', 'HEAD.lock');
|
|
18
|
+
|
|
19
|
+
const result = {
|
|
20
|
+
locked: false,
|
|
21
|
+
stale: false,
|
|
22
|
+
lockFiles: [],
|
|
23
|
+
message: null,
|
|
24
|
+
suggestion: null,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// Check index.lock
|
|
28
|
+
if (fs.existsSync(lockFile)) {
|
|
29
|
+
const lockAge = Date.now() - fs.statSync(lockFile).mtimeMs;
|
|
30
|
+
const lockAgeMinutes = Math.floor(lockAge / 60000);
|
|
31
|
+
|
|
32
|
+
result.locked = true;
|
|
33
|
+
result.lockFiles.push({
|
|
34
|
+
path: '.git/index.lock',
|
|
35
|
+
age: lockAgeMinutes,
|
|
36
|
+
stale: lockAgeMinutes > 5,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
if (lockAgeMinutes > 5) {
|
|
40
|
+
// Stale lock (older than 5 minutes)
|
|
41
|
+
result.stale = true;
|
|
42
|
+
result.message = `Stale git lock detected (${lockAgeMinutes} minutes old). This may indicate a crashed git process.`;
|
|
43
|
+
result.suggestion = 'Remove stale lock: rm .git/index.lock';
|
|
44
|
+
} else {
|
|
45
|
+
// Active lock
|
|
46
|
+
result.message =
|
|
47
|
+
'Git lock detected. Another git process may be running.';
|
|
48
|
+
result.suggestion =
|
|
49
|
+
'Wait for the other process to complete, or check for running git/editor processes';
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Check HEAD.lock
|
|
54
|
+
if (fs.existsSync(headLockFile)) {
|
|
55
|
+
const lockAge = Date.now() - fs.statSync(headLockFile).mtimeMs;
|
|
56
|
+
const lockAgeMinutes = Math.floor(lockAge / 60000);
|
|
57
|
+
|
|
58
|
+
result.locked = true;
|
|
59
|
+
result.lockFiles.push({
|
|
60
|
+
path: '.git/HEAD.lock',
|
|
61
|
+
age: lockAgeMinutes,
|
|
62
|
+
stale: lockAgeMinutes > 5,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
if (lockAgeMinutes > 5) {
|
|
66
|
+
result.stale = true;
|
|
67
|
+
if (!result.message) {
|
|
68
|
+
result.message = `Stale git lock detected (${lockAgeMinutes} minutes old).`;
|
|
69
|
+
result.suggestion = 'Remove stale lock: rm .git/HEAD.lock';
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Format git lock error message
|
|
79
|
+
* @param {Object} lockStatus - Lock status from checkGitLock
|
|
80
|
+
* @returns {string} Formatted error message
|
|
81
|
+
*/
|
|
82
|
+
function formatGitLockError(lockStatus) {
|
|
83
|
+
if (!lockStatus.locked) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
let message = '⚠️ Git lock detected\n';
|
|
88
|
+
message += ` ${lockStatus.message}\n`;
|
|
89
|
+
|
|
90
|
+
if (lockStatus.lockFiles.length > 0) {
|
|
91
|
+
message += '\n Lock files:\n';
|
|
92
|
+
for (const lockFile of lockStatus.lockFiles) {
|
|
93
|
+
message += ` - ${lockFile.path} (${lockFile.age} minutes old)`;
|
|
94
|
+
if (lockFile.stale) {
|
|
95
|
+
message += ' [STALE]';
|
|
96
|
+
}
|
|
97
|
+
message += '\n';
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (lockStatus.suggestion) {
|
|
102
|
+
message += `\n 💡 ${lockStatus.suggestion}\n`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (lockStatus.stale) {
|
|
106
|
+
message +=
|
|
107
|
+
'\n ⚠️ Warning: Removing stale locks may cause data loss if another process is actually running.\n';
|
|
108
|
+
message += ' Check for running git/editor processes before removing locks.\n';
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return message;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
module.exports = {
|
|
115
|
+
checkGitLock,
|
|
116
|
+
formatGitLockError,
|
|
117
|
+
};
|
|
118
|
+
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Gitignore Updater Utility
|
|
3
|
+
* Updates .gitignore to properly handle CAWS runtime files vs source files
|
|
4
|
+
* @author @darianrosebrook
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs-extra');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const chalk = require('chalk');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* CAWS .gitignore entries
|
|
13
|
+
*
|
|
14
|
+
* Strategy: Track shared/collaborative files, ignore local-only runtime data
|
|
15
|
+
*
|
|
16
|
+
* TRACKED (shared with team):
|
|
17
|
+
* - .caws/working-spec.yaml (main spec)
|
|
18
|
+
* - .caws/specs/*.yaml (feature specs)
|
|
19
|
+
* - .caws/policy.yaml (team policy)
|
|
20
|
+
* - .caws/waivers/*.yaml (project-wide waivers)
|
|
21
|
+
* - .caws/provenance/ (audit trails for compliance)
|
|
22
|
+
* - .caws/changes/ (change tracking for team visibility)
|
|
23
|
+
* - .caws/archive/ (archived changes for history)
|
|
24
|
+
* - .caws/plans/*.md (implementation plans)
|
|
25
|
+
*
|
|
26
|
+
* IGNORED (local-only):
|
|
27
|
+
* - .agent/ (agent runtime tracking, local to each developer)
|
|
28
|
+
* - Temporary files (*.tmp, *.bak)
|
|
29
|
+
* - Logs (caws.log, debug logs)
|
|
30
|
+
* - Local overrides (caws.local.*)
|
|
31
|
+
*/
|
|
32
|
+
const CAWS_GITIGNORE_ENTRIES = `
|
|
33
|
+
# CAWS Local Runtime Data (developer-specific, should not be tracked)
|
|
34
|
+
# ====================================================================
|
|
35
|
+
# Note: Specs, policy, waivers, provenance, and plans ARE tracked for team collaboration
|
|
36
|
+
# Only local agent tracking, generated tools, and temporary files are ignored
|
|
37
|
+
|
|
38
|
+
# Agent runtime tracking (local to each developer)
|
|
39
|
+
.agent/
|
|
40
|
+
|
|
41
|
+
# CAWS tools (now in .caws/tools/)
|
|
42
|
+
.caws/tools/
|
|
43
|
+
# Legacy location (for backward compatibility)
|
|
44
|
+
apps/tools/caws/
|
|
45
|
+
|
|
46
|
+
# Temporary CAWS files
|
|
47
|
+
**/*.caws.tmp
|
|
48
|
+
**/*.working-spec.bak
|
|
49
|
+
.caws/*.tmp
|
|
50
|
+
.caws/*.bak
|
|
51
|
+
|
|
52
|
+
# CAWS logs (local debugging)
|
|
53
|
+
caws-debug.log*
|
|
54
|
+
**/caws.log
|
|
55
|
+
.caws/*.log
|
|
56
|
+
|
|
57
|
+
# Local development overrides (developer-specific)
|
|
58
|
+
caws.local.*
|
|
59
|
+
.caws/local.*
|
|
60
|
+
`;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Update .gitignore to include CAWS runtime file exclusions
|
|
64
|
+
* @param {string} projectRoot - Project root directory
|
|
65
|
+
* @param {Object} options - Options
|
|
66
|
+
* @param {boolean} options.force - Force update even if entries exist
|
|
67
|
+
* @returns {Promise<boolean>} Whether .gitignore was updated
|
|
68
|
+
*/
|
|
69
|
+
async function updateGitignore(projectRoot, options = {}) {
|
|
70
|
+
const { force = false } = options;
|
|
71
|
+
const gitignorePath = path.join(projectRoot, '.gitignore');
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
// Read existing .gitignore or create empty
|
|
75
|
+
let existingContent = '';
|
|
76
|
+
if (await fs.pathExists(gitignorePath)) {
|
|
77
|
+
existingContent = await fs.readFile(gitignorePath, 'utf8');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Check if CAWS entries already exist (check for either old or new header)
|
|
81
|
+
const hasCawsEntries =
|
|
82
|
+
existingContent.includes('# CAWS Local Runtime Data') ||
|
|
83
|
+
existingContent.includes('# CAWS Runtime Data');
|
|
84
|
+
|
|
85
|
+
if (hasCawsEntries && !force) {
|
|
86
|
+
// Already has CAWS entries, skip
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// If old entries exist, replace them with new ones
|
|
91
|
+
if (existingContent.includes('# CAWS Runtime Data') && force) {
|
|
92
|
+
// Remove old CAWS entries (between "# CAWS Runtime Data" and next major section)
|
|
93
|
+
const lines = existingContent.split('\n');
|
|
94
|
+
const startIndex = lines.findIndex((line) => line.includes('# CAWS Runtime Data'));
|
|
95
|
+
if (startIndex !== -1) {
|
|
96
|
+
// Find the end of CAWS section (next major section starting with #)
|
|
97
|
+
let endIndex = startIndex + 1;
|
|
98
|
+
while (
|
|
99
|
+
endIndex < lines.length &&
|
|
100
|
+
(lines[endIndex].trim() === '' ||
|
|
101
|
+
lines[endIndex].startsWith('#') ||
|
|
102
|
+
lines[endIndex].startsWith('.caws/') ||
|
|
103
|
+
lines[endIndex].startsWith('.agent/') ||
|
|
104
|
+
lines[endIndex].includes('caws') ||
|
|
105
|
+
lines[endIndex].includes('CAWS'))
|
|
106
|
+
) {
|
|
107
|
+
endIndex++;
|
|
108
|
+
}
|
|
109
|
+
// Remove old section and insert new one
|
|
110
|
+
const before = lines.slice(0, startIndex).join('\n');
|
|
111
|
+
const after = lines.slice(endIndex).join('\n');
|
|
112
|
+
existingContent = [before, after].filter(Boolean).join('\n');
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Append CAWS entries
|
|
117
|
+
const updatedContent = existingContent.trim() + '\n' + CAWS_GITIGNORE_ENTRIES.trim() + '\n';
|
|
118
|
+
|
|
119
|
+
await fs.writeFile(gitignorePath, updatedContent, 'utf8');
|
|
120
|
+
|
|
121
|
+
return true;
|
|
122
|
+
} catch (error) {
|
|
123
|
+
console.warn(chalk.yellow(`⚠️ Could not update .gitignore: ${error.message}`));
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Verify .gitignore has proper CAWS entries
|
|
130
|
+
* @param {string} projectRoot - Project root directory
|
|
131
|
+
* @returns {Promise<boolean>} Whether .gitignore has CAWS entries
|
|
132
|
+
*/
|
|
133
|
+
async function verifyGitignore(projectRoot) {
|
|
134
|
+
const gitignorePath = path.join(projectRoot, '.gitignore');
|
|
135
|
+
|
|
136
|
+
if (!(await fs.pathExists(gitignorePath))) {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const content = await fs.readFile(gitignorePath, 'utf8');
|
|
141
|
+
return content.includes('# CAWS Local Runtime Data') || content.includes('# CAWS Runtime Data');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
module.exports = {
|
|
145
|
+
updateGitignore,
|
|
146
|
+
verifyGitignore,
|
|
147
|
+
CAWS_GITIGNORE_ENTRIES,
|
|
148
|
+
};
|
|
@@ -196,18 +196,58 @@ function checkHiddenTodos(stagedFiles) {
|
|
|
196
196
|
console.log(`📁 Found ${supportedFiles.length} staged files to analyze for TODOs`);
|
|
197
197
|
|
|
198
198
|
try {
|
|
199
|
-
//
|
|
200
|
-
const
|
|
201
|
-
|
|
199
|
+
// Find TODO analyzer .mjs file (preferred - no Python dependency)
|
|
200
|
+
const possiblePaths = [
|
|
201
|
+
// Published npm package (priority)
|
|
202
|
+
path.join(
|
|
203
|
+
process.cwd(),
|
|
204
|
+
'node_modules',
|
|
205
|
+
'@paths.design',
|
|
206
|
+
'quality-gates',
|
|
207
|
+
'todo-analyzer.mjs'
|
|
208
|
+
),
|
|
209
|
+
// Legacy monorepo local copy
|
|
210
|
+
path.join(process.cwd(), 'node_modules', '@caws', 'quality-gates', 'todo-analyzer.mjs'),
|
|
211
|
+
// Monorepo structure (development)
|
|
212
|
+
path.join(process.cwd(), 'packages', 'quality-gates', 'todo-analyzer.mjs'),
|
|
213
|
+
// Local copy in scripts directory (if scaffolded)
|
|
214
|
+
path.join(process.cwd(), 'scripts', 'todo-analyzer.mjs'),
|
|
215
|
+
// Legacy Python analyzer (deprecated)
|
|
216
|
+
path.join(process.cwd(), 'scripts', 'v3', 'analysis', 'todo_analyzer.py'),
|
|
217
|
+
];
|
|
218
|
+
|
|
219
|
+
let analyzerPath = null;
|
|
220
|
+
let usePython = false;
|
|
221
|
+
|
|
222
|
+
for (const testPath of possiblePaths) {
|
|
223
|
+
if (fs.existsSync(testPath)) {
|
|
224
|
+
analyzerPath = testPath;
|
|
225
|
+
usePython = testPath.endsWith('.py');
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (!analyzerPath) {
|
|
202
231
|
console.warn('⚠️ TODO analyzer not found - skipping TODO analysis');
|
|
232
|
+
console.warn(
|
|
233
|
+
'💡 Install @paths.design/quality-gates: npm install --save-dev @paths.design/quality-gates'
|
|
234
|
+
);
|
|
203
235
|
return { todos: [], blocking: 0, total: 0 };
|
|
204
236
|
}
|
|
205
237
|
|
|
238
|
+
if (usePython) {
|
|
239
|
+
console.warn('⚠️ Using legacy Python TODO analyzer (deprecated)');
|
|
240
|
+
console.warn(
|
|
241
|
+
'💡 Install @paths.design/quality-gates for Node.js version: npm install --save-dev @paths.design/quality-gates'
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
|
|
206
245
|
// Run the TODO analyzer with staged files
|
|
207
|
-
const
|
|
208
|
-
`python3 ${analyzerPath} --staged-only --min-confidence ${CONFIG.todoConfidenceThreshold}
|
|
209
|
-
{
|
|
210
|
-
|
|
246
|
+
const command = usePython
|
|
247
|
+
? `python3 ${analyzerPath} --staged-only --min-confidence ${CONFIG.todoConfidenceThreshold}`
|
|
248
|
+
: `node ${analyzerPath} --staged-only --ci-mode --min-confidence ${CONFIG.todoConfidenceThreshold}`;
|
|
249
|
+
|
|
250
|
+
const result = execSync(command, { encoding: 'utf8', cwd: process.cwd() });
|
|
211
251
|
|
|
212
252
|
// Parse the output to extract TODO count
|
|
213
253
|
const lines = result.split('\n');
|
|
@@ -108,7 +108,14 @@ async function resolveSpec(options = {}) {
|
|
|
108
108
|
const specPath = path.join(SPECS_DIR, registry.specs[id].path);
|
|
109
109
|
try {
|
|
110
110
|
const content = await fs.readFile(specPath, 'utf8');
|
|
111
|
-
|
|
111
|
+
let spec;
|
|
112
|
+
try {
|
|
113
|
+
spec = yaml.load(content);
|
|
114
|
+
} catch (yamlError) {
|
|
115
|
+
console.log(chalk.yellow(` - ${id} (YAML syntax error: ${yamlError.message})`));
|
|
116
|
+
specsInfo.push({ id, type: 'unknown', status: 'unknown', title: 'YAML error' });
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
112
119
|
const status = spec.status || 'draft';
|
|
113
120
|
const type = spec.type || 'feature';
|
|
114
121
|
const statusColor =
|
|
@@ -122,7 +129,7 @@ async function resolveSpec(options = {}) {
|
|
|
122
129
|
);
|
|
123
130
|
specsInfo.push({ id, type, status, title: spec.title || 'Untitled' });
|
|
124
131
|
} catch (error) {
|
|
125
|
-
console.log(chalk.yellow(` - ${id} (error loading details)`));
|
|
132
|
+
console.log(chalk.yellow(` - ${id} (error loading details: ${error.message})`));
|
|
126
133
|
specsInfo.push({ id, type: 'unknown', status: 'unknown', title: 'Error loading' });
|
|
127
134
|
}
|
|
128
135
|
}
|
|
@@ -363,7 +370,20 @@ async function checkScopeConflicts(specIds) {
|
|
|
363
370
|
|
|
364
371
|
try {
|
|
365
372
|
const content = await fs.readFile(specPath, 'utf8');
|
|
366
|
-
|
|
373
|
+
let spec;
|
|
374
|
+
try {
|
|
375
|
+
spec = yaml.load(content);
|
|
376
|
+
} catch (yamlError) {
|
|
377
|
+
const relativePath = path.relative(process.cwd(), specPath);
|
|
378
|
+
throw new Error(
|
|
379
|
+
`Invalid YAML syntax in ${relativePath}: ${yamlError.message}\n` +
|
|
380
|
+
(yamlError.mark
|
|
381
|
+
? ` Line ${yamlError.mark.line + 1}, Column ${yamlError.mark.column + 1}\n`
|
|
382
|
+
: '') +
|
|
383
|
+
(yamlError.mark?.snippet ? ` ${yamlError.mark.snippet}\n` : '') +
|
|
384
|
+
`💡 Fix YAML syntax errors or use 'caws specs create <id>' for proper structure`
|
|
385
|
+
);
|
|
386
|
+
}
|
|
367
387
|
|
|
368
388
|
specScopes.push({
|
|
369
389
|
id,
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview YAML Validation Utilities
|
|
3
|
+
* Functions for validating YAML syntax and structure
|
|
4
|
+
* @author @darianrosebrook
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const yaml = require('js-yaml');
|
|
8
|
+
const fs = require('fs-extra');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Validate YAML syntax for a file
|
|
13
|
+
* @param {string} filePath - Path to YAML file
|
|
14
|
+
* @returns {Object} Validation result with valid flag and error details
|
|
15
|
+
*/
|
|
16
|
+
function validateYamlSyntax(filePath) {
|
|
17
|
+
try {
|
|
18
|
+
if (!fs.existsSync(filePath)) {
|
|
19
|
+
return {
|
|
20
|
+
valid: false,
|
|
21
|
+
error: `File not found: ${filePath}`,
|
|
22
|
+
line: null,
|
|
23
|
+
column: null,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
28
|
+
yaml.load(content); // Will throw if invalid
|
|
29
|
+
|
|
30
|
+
return { valid: true };
|
|
31
|
+
} catch (error) {
|
|
32
|
+
return {
|
|
33
|
+
valid: false,
|
|
34
|
+
error: error.message,
|
|
35
|
+
line: error.mark?.line ? error.mark.line + 1 : null, // Convert to 1-based
|
|
36
|
+
column: error.mark?.column ? error.mark.column + 1 : null, // Convert to 1-based
|
|
37
|
+
snippet: error.mark?.snippet || null,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Validate YAML syntax for multiple files
|
|
44
|
+
* @param {string[]} filePaths - Array of file paths to validate
|
|
45
|
+
* @returns {Object} Validation results with summary
|
|
46
|
+
*/
|
|
47
|
+
function validateYamlFiles(filePaths) {
|
|
48
|
+
const results = {
|
|
49
|
+
valid: true,
|
|
50
|
+
files: [],
|
|
51
|
+
errors: [],
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
for (const filePath of filePaths) {
|
|
55
|
+
const validation = validateYamlSyntax(filePath);
|
|
56
|
+
const relativePath = path.relative(process.cwd(), filePath);
|
|
57
|
+
|
|
58
|
+
results.files.push({
|
|
59
|
+
path: relativePath,
|
|
60
|
+
...validation,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
if (!validation.valid) {
|
|
64
|
+
results.valid = false;
|
|
65
|
+
results.errors.push({
|
|
66
|
+
file: relativePath,
|
|
67
|
+
error: validation.error,
|
|
68
|
+
line: validation.line,
|
|
69
|
+
column: validation.column,
|
|
70
|
+
snippet: validation.snippet,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return results;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Find all YAML files in .caws directory
|
|
80
|
+
* @param {string} projectRoot - Project root directory
|
|
81
|
+
* @returns {string[]} Array of YAML file paths
|
|
82
|
+
*/
|
|
83
|
+
function findCawsYamlFiles(projectRoot) {
|
|
84
|
+
const cawsDir = path.join(projectRoot, '.caws');
|
|
85
|
+
const yamlFiles = [];
|
|
86
|
+
|
|
87
|
+
if (!fs.existsSync(cawsDir)) {
|
|
88
|
+
return yamlFiles;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function walkDir(dir) {
|
|
92
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
93
|
+
|
|
94
|
+
for (const entry of entries) {
|
|
95
|
+
const fullPath = path.join(dir, entry.name);
|
|
96
|
+
|
|
97
|
+
if (entry.isDirectory()) {
|
|
98
|
+
walkDir(fullPath);
|
|
99
|
+
} else if (
|
|
100
|
+
entry.isFile() &&
|
|
101
|
+
(entry.name.endsWith('.yaml') || entry.name.endsWith('.yml'))
|
|
102
|
+
) {
|
|
103
|
+
yamlFiles.push(fullPath);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
walkDir(cawsDir);
|
|
109
|
+
return yamlFiles;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Validate all CAWS YAML files in project
|
|
114
|
+
* @param {string} projectRoot - Project root directory
|
|
115
|
+
* @returns {Object} Validation results
|
|
116
|
+
*/
|
|
117
|
+
function validateAllCawsYamlFiles(projectRoot) {
|
|
118
|
+
const yamlFiles = findCawsYamlFiles(projectRoot);
|
|
119
|
+
return validateYamlFiles(yamlFiles);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Format validation error for display
|
|
124
|
+
* @param {Object} error - Error object from validateYamlSyntax
|
|
125
|
+
* @param {string} filePath - File path
|
|
126
|
+
* @returns {string} Formatted error message
|
|
127
|
+
*/
|
|
128
|
+
function formatYamlError(error, filePath) {
|
|
129
|
+
const relativePath = path.relative(process.cwd(), filePath);
|
|
130
|
+
let message = `❌ Invalid YAML in ${relativePath}\n`;
|
|
131
|
+
message += ` Error: ${error.error}\n`;
|
|
132
|
+
|
|
133
|
+
if (error.line !== null) {
|
|
134
|
+
message += ` Line: ${error.line}`;
|
|
135
|
+
if (error.column !== null) {
|
|
136
|
+
message += `, Column: ${error.column}`;
|
|
137
|
+
}
|
|
138
|
+
message += '\n';
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (error.snippet) {
|
|
142
|
+
message += ` ${error.snippet}\n`;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return message;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
module.exports = {
|
|
149
|
+
validateYamlSyntax,
|
|
150
|
+
validateYamlFiles,
|
|
151
|
+
findCawsYamlFiles,
|
|
152
|
+
validateAllCawsYamlFiles,
|
|
153
|
+
formatYamlError,
|
|
154
|
+
};
|
|
155
|
+
|