@paths.design/caws-cli 3.5.0 → 4.0.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/dist/budget-derivation.d.ts +41 -2
- package/dist/budget-derivation.d.ts.map +1 -1
- package/dist/budget-derivation.js +417 -30
- package/dist/commands/validate.d.ts +1 -0
- package/dist/commands/validate.d.ts.map +1 -1
- package/dist/commands/validate.js +105 -28
- package/dist/index.js +2 -0
- package/dist/policy/PolicyManager.d.ts +104 -0
- package/dist/policy/PolicyManager.d.ts.map +1 -0
- package/dist/policy/PolicyManager.js +399 -0
- package/dist/scaffold/cursor-hooks.d.ts.map +1 -1
- package/dist/scaffold/cursor-hooks.js +15 -0
- package/dist/spec/SpecFileManager.d.ts +146 -0
- package/dist/spec/SpecFileManager.d.ts.map +1 -0
- package/dist/spec/SpecFileManager.js +419 -0
- package/dist/validation/spec-validation.d.ts +14 -0
- package/dist/validation/spec-validation.d.ts.map +1 -1
- package/dist/validation/spec-validation.js +225 -13
- package/package.json +1 -1
- package/templates/.cursor/rules/01-claims-verification.mdc +144 -0
- package/templates/.cursor/rules/02-testing-standards.mdc +315 -0
- package/templates/.cursor/rules/03-infrastructure-standards.mdc +251 -0
- package/templates/.cursor/rules/04-documentation-integrity.mdc +291 -0
- package/templates/.cursor/rules/05-production-readiness-checklist.mdc +214 -0
- package/templates/.cursor/rules/README.md +64 -0
|
@@ -138,6 +138,21 @@ async function scaffoldCursorHooks(projectDir, levels = ['safety', 'quality', 's
|
|
|
138
138
|
await fs.copy(readmePath, path.join(cursorDir, 'README.md'));
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
+
// Copy rules directory if it exists
|
|
142
|
+
const rulesTemplateDir = path.join(cursorTemplateDir, 'rules');
|
|
143
|
+
const rulesDestDir = path.join(cursorDir, 'rules');
|
|
144
|
+
if (fs.existsSync(rulesTemplateDir)) {
|
|
145
|
+
try {
|
|
146
|
+
await fs.ensureDir(rulesDestDir);
|
|
147
|
+
await fs.copy(rulesTemplateDir, rulesDestDir);
|
|
148
|
+
const ruleFiles = fs.readdirSync(rulesTemplateDir).filter((file) => file.endsWith('.mdc'));
|
|
149
|
+
console.log(chalk.green('✅ Cursor rules configured'));
|
|
150
|
+
console.log(chalk.gray(` Rules: ${ruleFiles.length} rule files installed`));
|
|
151
|
+
} catch (error) {
|
|
152
|
+
console.warn(chalk.yellow('⚠️ Failed to copy Cursor rules:'), error.message);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
141
156
|
console.log(chalk.green('✅ Cursor hooks configured'));
|
|
142
157
|
console.log(chalk.gray(` Enabled: ${levels.join(', ')}`));
|
|
143
158
|
console.log(
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spec File Manager - Handles WorkingSpec file operations and YAML conversion
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - Bidirectional WorkingSpec ↔ YAML conversion
|
|
6
|
+
* - Temporary file support for validation workflows
|
|
7
|
+
* - Backup/restore capabilities
|
|
8
|
+
* - Automatic cleanup of old temporary files
|
|
9
|
+
*/
|
|
10
|
+
export class SpecFileManager {
|
|
11
|
+
constructor(config?: {});
|
|
12
|
+
projectRoot: any;
|
|
13
|
+
useTemporaryFiles: any;
|
|
14
|
+
tempDir: any;
|
|
15
|
+
/**
|
|
16
|
+
* Convert WorkingSpec object to YAML string
|
|
17
|
+
*
|
|
18
|
+
* @param {Object} spec - WorkingSpec to convert
|
|
19
|
+
* @returns {string} YAML string representation
|
|
20
|
+
*/
|
|
21
|
+
specToYaml(spec: any): string;
|
|
22
|
+
/**
|
|
23
|
+
* Parse YAML string to WorkingSpec object
|
|
24
|
+
*
|
|
25
|
+
* @param {string} yamlContent - YAML string to parse
|
|
26
|
+
* @returns {Object} Parsed WorkingSpec object
|
|
27
|
+
* @throws {Error} If YAML is invalid or doesn't match WorkingSpec schema
|
|
28
|
+
*/
|
|
29
|
+
yamlToSpec(yamlContent: string): any;
|
|
30
|
+
/**
|
|
31
|
+
* Get path to .caws/working-spec.yaml in project
|
|
32
|
+
*
|
|
33
|
+
* @returns {string} Absolute path to working spec file
|
|
34
|
+
*/
|
|
35
|
+
getSpecFilePath(): string;
|
|
36
|
+
/**
|
|
37
|
+
* Check if working spec file exists
|
|
38
|
+
*
|
|
39
|
+
* @returns {Promise<boolean>} True if file exists
|
|
40
|
+
*/
|
|
41
|
+
specFileExists(): Promise<boolean>;
|
|
42
|
+
/**
|
|
43
|
+
* Read working spec from .caws/working-spec.yaml
|
|
44
|
+
*
|
|
45
|
+
* @returns {Promise<Object>} Parsed WorkingSpec object
|
|
46
|
+
* @throws {Error} If file doesn't exist or is invalid
|
|
47
|
+
*/
|
|
48
|
+
readSpecFile(): Promise<any>;
|
|
49
|
+
/**
|
|
50
|
+
* Write WorkingSpec to file
|
|
51
|
+
*
|
|
52
|
+
* Writes to .caws/working-spec.yaml or a temporary file based on configuration.
|
|
53
|
+
*
|
|
54
|
+
* @param {Object} spec - WorkingSpec to write
|
|
55
|
+
* @param {Object} options - Write options
|
|
56
|
+
* @param {boolean} options.useTemp - Override temp file usage
|
|
57
|
+
* @param {boolean} options.backup - Create backup before writing
|
|
58
|
+
* @returns {Promise<Object>} Write result with file path and cleanup function
|
|
59
|
+
*/
|
|
60
|
+
writeSpecFile(spec: any, options?: {
|
|
61
|
+
useTemp: boolean;
|
|
62
|
+
backup: boolean;
|
|
63
|
+
}): Promise<any>;
|
|
64
|
+
/**
|
|
65
|
+
* Update existing working spec file
|
|
66
|
+
*
|
|
67
|
+
* Reads current spec, merges changes, and writes back.
|
|
68
|
+
*
|
|
69
|
+
* @param {Object} updates - Partial WorkingSpec with fields to update
|
|
70
|
+
* @returns {Promise<Object>} Updated WorkingSpec
|
|
71
|
+
*/
|
|
72
|
+
updateSpecFile(updates: any): Promise<any>;
|
|
73
|
+
/**
|
|
74
|
+
* Create backup of working spec
|
|
75
|
+
*
|
|
76
|
+
* @returns {Promise<string>} Path to backup file
|
|
77
|
+
*/
|
|
78
|
+
backupSpecFile(): Promise<string>;
|
|
79
|
+
/**
|
|
80
|
+
* Restore working spec from backup
|
|
81
|
+
*
|
|
82
|
+
* @param {string} backupPath - Path to backup file
|
|
83
|
+
* @returns {Promise<void>}
|
|
84
|
+
*/
|
|
85
|
+
restoreSpecFile(backupPath: string): Promise<void>;
|
|
86
|
+
/**
|
|
87
|
+
* List all backup files
|
|
88
|
+
*
|
|
89
|
+
* @returns {Promise<string[]>} Array of backup file paths
|
|
90
|
+
*/
|
|
91
|
+
listBackups(): Promise<string[]>;
|
|
92
|
+
/**
|
|
93
|
+
* Delete old backup files
|
|
94
|
+
*
|
|
95
|
+
* @param {Object} options - Cleanup options
|
|
96
|
+
* @param {number} options.maxAge - Maximum age in milliseconds (default: 7 days)
|
|
97
|
+
* @param {number} options.keep - Minimum number of backups to keep (default: 5)
|
|
98
|
+
* @returns {Promise<number>} Number of backups deleted
|
|
99
|
+
*/
|
|
100
|
+
cleanupBackups(options?: {
|
|
101
|
+
maxAge: number;
|
|
102
|
+
keep: number;
|
|
103
|
+
}): Promise<number>;
|
|
104
|
+
/**
|
|
105
|
+
* Validate spec file exists and is parseable
|
|
106
|
+
*
|
|
107
|
+
* @returns {Promise<Object>} Validation result
|
|
108
|
+
*/
|
|
109
|
+
validateSpecFile(): Promise<any>;
|
|
110
|
+
/**
|
|
111
|
+
* Clean up old temporary spec files
|
|
112
|
+
*
|
|
113
|
+
* Removes temp files older than specified age.
|
|
114
|
+
*
|
|
115
|
+
* @param {number} maxAge - Maximum age in milliseconds (default: 1 hour)
|
|
116
|
+
* @returns {Promise<number>} Number of files cleaned up
|
|
117
|
+
*/
|
|
118
|
+
cleanupTempFiles(maxAge?: number): Promise<number>;
|
|
119
|
+
/**
|
|
120
|
+
* Get spec file stats (size, modified date, etc.)
|
|
121
|
+
*
|
|
122
|
+
* @returns {Promise<Object>} File stats
|
|
123
|
+
*/
|
|
124
|
+
getSpecFileStats(): Promise<any>;
|
|
125
|
+
/**
|
|
126
|
+
* Create a new SpecFileManager instance with different configuration
|
|
127
|
+
*
|
|
128
|
+
* @param {Object} config - New configuration
|
|
129
|
+
* @returns {SpecFileManager} New instance
|
|
130
|
+
*/
|
|
131
|
+
withConfig(config: any): SpecFileManager;
|
|
132
|
+
}
|
|
133
|
+
export const defaultSpecFileManager: SpecFileManager;
|
|
134
|
+
/**
|
|
135
|
+
* Create a SpecFileManager instance with default configuration
|
|
136
|
+
*
|
|
137
|
+
* @param {string} projectRoot - Project root directory
|
|
138
|
+
* @param {Object} options - Additional options
|
|
139
|
+
* @returns {SpecFileManager} SpecFileManager instance
|
|
140
|
+
*/
|
|
141
|
+
export function createSpecFileManager(projectRoot: string, options?: any): SpecFileManager;
|
|
142
|
+
export declare function specToYaml(spec: any): string;
|
|
143
|
+
export declare function yamlToSpec(yaml: any): any;
|
|
144
|
+
export declare function readSpecFile(projectRoot: any): Promise<any>;
|
|
145
|
+
export declare function writeSpecFile(spec: any, projectRoot: any, options: any): Promise<any>;
|
|
146
|
+
//# sourceMappingURL=SpecFileManager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SpecFileManager.d.ts","sourceRoot":"","sources":["../../src/spec/SpecFileManager.js"],"names":[],"mappings":"AAaA;;;;;;;;GAQG;AACH;IACE,yBAIC;IAHC,iBAAsD;IACtD,uBAA0D;IAC1D,aAA4C;IAG9C;;;;;OAKG;IACH,uBAFa,MAAM,CASlB;IAED;;;;;;OAMG;IACH,wBAJW,MAAM,OAqBhB;IAED;;;;OAIG;IACH,mBAFa,MAAM,CAIlB;IAED;;;;OAIG;IACH,kBAFa,OAAO,CAAC,OAAO,CAAC,CAS5B;IAED;;;;;OAKG;IACH,gBAHa,OAAO,KAAQ,CAe3B;IAED;;;;;;;;;;OAUG;IACH,mCAJG;QAAyB,OAAO,EAAxB,OAAO;QACU,MAAM,EAAvB,OAAO;KACf,GAAU,OAAO,KAAQ,CA2C3B;IAED;;;;;;;OAOG;IACH,8BAFa,OAAO,KAAQ,CAa3B;IAED;;;;OAIG;IACH,kBAFa,OAAO,CAAC,MAAM,CAAC,CAS3B;IAED;;;;;OAKG;IACH,4BAHW,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAKzB;IAED;;;;OAIG;IACH,eAFa,OAAO,CAAC,MAAM,EAAE,CAAC,CAoB7B;IAED;;;;;;;OAOG;IACH,yBAJG;QAAwB,MAAM,EAAtB,MAAM;QACU,IAAI,EAApB,MAAM;KACd,GAAU,OAAO,CAAC,MAAM,CAAC,CA+B3B;IAED;;;;OAIG;IACH,oBAFa,OAAO,KAAQ,CAe3B;IAED;;;;;;;OAOG;IACH,0BAHW,MAAM,GACJ,OAAO,CAAC,MAAM,CAAC,CA6B3B;IAED;;;;OAIG;IACH,oBAFa,OAAO,KAAQ,CA0B3B;IAED;;;;;OAKG;IACH,yBAFa,eAAe,CAS3B;CACF;AAiBD,qDAAqD;AAfrD;;;;;;GAMG;AACH,mDAJW,MAAM,kBAEJ,eAAe,CAO3B;AAWa,sDAAiD;AACjD,mDAAiD;AAC/C,qEAKb;AACc,+FAKd"}
|
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Spec File Manager - WorkingSpec ↔ YAML conversion and file management
|
|
3
|
+
* Handles conversion between JavaScript WorkingSpec objects and YAML files,
|
|
4
|
+
* manages .caws/working-spec.yaml lifecycle, and provides temporary file utilities.
|
|
5
|
+
* Ported from agent-agency v2 CAWS integration patterns.
|
|
6
|
+
* @author @darianrosebrook
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require('fs-extra');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const yaml = require('js-yaml');
|
|
12
|
+
const os = require('os');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Spec File Manager - Handles WorkingSpec file operations and YAML conversion
|
|
16
|
+
*
|
|
17
|
+
* Features:
|
|
18
|
+
* - Bidirectional WorkingSpec ↔ YAML conversion
|
|
19
|
+
* - Temporary file support for validation workflows
|
|
20
|
+
* - Backup/restore capabilities
|
|
21
|
+
* - Automatic cleanup of old temporary files
|
|
22
|
+
*/
|
|
23
|
+
class SpecFileManager {
|
|
24
|
+
constructor(config = {}) {
|
|
25
|
+
this.projectRoot = config.projectRoot || process.cwd();
|
|
26
|
+
this.useTemporaryFiles = config.useTemporaryFiles ?? false;
|
|
27
|
+
this.tempDir = config.tempDir || os.tmpdir();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Convert WorkingSpec object to YAML string
|
|
32
|
+
*
|
|
33
|
+
* @param {Object} spec - WorkingSpec to convert
|
|
34
|
+
* @returns {string} YAML string representation
|
|
35
|
+
*/
|
|
36
|
+
specToYaml(spec) {
|
|
37
|
+
return yaml.dump(spec, {
|
|
38
|
+
indent: 2,
|
|
39
|
+
lineWidth: 100,
|
|
40
|
+
noRefs: true,
|
|
41
|
+
sortKeys: false,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Parse YAML string to WorkingSpec object
|
|
47
|
+
*
|
|
48
|
+
* @param {string} yamlContent - YAML string to parse
|
|
49
|
+
* @returns {Object} Parsed WorkingSpec object
|
|
50
|
+
* @throws {Error} If YAML is invalid or doesn't match WorkingSpec schema
|
|
51
|
+
*/
|
|
52
|
+
yamlToSpec(yamlContent) {
|
|
53
|
+
try {
|
|
54
|
+
const parsed = yaml.load(yamlContent);
|
|
55
|
+
|
|
56
|
+
// Basic validation
|
|
57
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
58
|
+
throw new Error('Invalid YAML: not an object');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!parsed.id || !parsed.title || !parsed.risk_tier) {
|
|
62
|
+
throw new Error('Invalid WorkingSpec: missing required fields (id, title, risk_tier)');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return parsed;
|
|
66
|
+
} catch (error) {
|
|
67
|
+
throw new Error(`Failed to parse YAML: ${error.message}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Get path to .caws/working-spec.yaml in project
|
|
73
|
+
*
|
|
74
|
+
* @returns {string} Absolute path to working spec file
|
|
75
|
+
*/
|
|
76
|
+
getSpecFilePath() {
|
|
77
|
+
return path.join(this.projectRoot, '.caws', 'working-spec.yaml');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Check if working spec file exists
|
|
82
|
+
*
|
|
83
|
+
* @returns {Promise<boolean>} True if file exists
|
|
84
|
+
*/
|
|
85
|
+
async specFileExists() {
|
|
86
|
+
try {
|
|
87
|
+
await fs.access(this.getSpecFilePath());
|
|
88
|
+
return true;
|
|
89
|
+
} catch {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Read working spec from .caws/working-spec.yaml
|
|
96
|
+
*
|
|
97
|
+
* @returns {Promise<Object>} Parsed WorkingSpec object
|
|
98
|
+
* @throws {Error} If file doesn't exist or is invalid
|
|
99
|
+
*/
|
|
100
|
+
async readSpecFile() {
|
|
101
|
+
const specPath = this.getSpecFilePath();
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
const content = await fs.readFile(specPath, 'utf-8');
|
|
105
|
+
return this.yamlToSpec(content);
|
|
106
|
+
} catch (error) {
|
|
107
|
+
if (error.code === 'ENOENT') {
|
|
108
|
+
throw new Error(`Working spec not found: ${specPath}\nRun 'caws init' to create it`);
|
|
109
|
+
}
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Write WorkingSpec to file
|
|
116
|
+
*
|
|
117
|
+
* Writes to .caws/working-spec.yaml or a temporary file based on configuration.
|
|
118
|
+
*
|
|
119
|
+
* @param {Object} spec - WorkingSpec to write
|
|
120
|
+
* @param {Object} options - Write options
|
|
121
|
+
* @param {boolean} options.useTemp - Override temp file usage
|
|
122
|
+
* @param {boolean} options.backup - Create backup before writing
|
|
123
|
+
* @returns {Promise<Object>} Write result with file path and cleanup function
|
|
124
|
+
*/
|
|
125
|
+
async writeSpecFile(spec, options = {}) {
|
|
126
|
+
const yamlContent = this.specToYaml(spec);
|
|
127
|
+
const useTemp = options.useTemp ?? this.useTemporaryFiles;
|
|
128
|
+
|
|
129
|
+
if (useTemp) {
|
|
130
|
+
// Write to temporary file
|
|
131
|
+
const tempPath = path.join(this.tempDir, `caws-spec-${spec.id || 'temp'}-${Date.now()}.yaml`);
|
|
132
|
+
|
|
133
|
+
await fs.writeFile(tempPath, yamlContent, 'utf-8');
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
filePath: tempPath,
|
|
137
|
+
isTemporary: true,
|
|
138
|
+
cleanup: async () => {
|
|
139
|
+
try {
|
|
140
|
+
await fs.unlink(tempPath);
|
|
141
|
+
} catch {
|
|
142
|
+
// Ignore cleanup errors (file may already be deleted)
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
} else {
|
|
147
|
+
// Write to project .caws directory
|
|
148
|
+
const specPath = this.getSpecFilePath();
|
|
149
|
+
const cawsDir = path.dirname(specPath);
|
|
150
|
+
|
|
151
|
+
// Create backup if requested
|
|
152
|
+
if (options.backup && (await this.specFileExists())) {
|
|
153
|
+
await this.backupSpecFile();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Ensure .caws directory exists
|
|
157
|
+
await fs.mkdir(cawsDir, { recursive: true });
|
|
158
|
+
|
|
159
|
+
await fs.writeFile(specPath, yamlContent, 'utf-8');
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
filePath: specPath,
|
|
163
|
+
isTemporary: false,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Update existing working spec file
|
|
170
|
+
*
|
|
171
|
+
* Reads current spec, merges changes, and writes back.
|
|
172
|
+
*
|
|
173
|
+
* @param {Object} updates - Partial WorkingSpec with fields to update
|
|
174
|
+
* @returns {Promise<Object>} Updated WorkingSpec
|
|
175
|
+
*/
|
|
176
|
+
async updateSpecFile(updates) {
|
|
177
|
+
const currentSpec = await this.readSpecFile();
|
|
178
|
+
const updatedSpec = {
|
|
179
|
+
...currentSpec,
|
|
180
|
+
...updates,
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
// Always write to permanent location for updates
|
|
184
|
+
await this.writeSpecFile(updatedSpec, { useTemp: false });
|
|
185
|
+
|
|
186
|
+
return updatedSpec;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Create backup of working spec
|
|
191
|
+
*
|
|
192
|
+
* @returns {Promise<string>} Path to backup file
|
|
193
|
+
*/
|
|
194
|
+
async backupSpecFile() {
|
|
195
|
+
const specPath = this.getSpecFilePath();
|
|
196
|
+
const backupPath = `${specPath}.backup-${Date.now()}`;
|
|
197
|
+
|
|
198
|
+
await fs.copyFile(specPath, backupPath);
|
|
199
|
+
|
|
200
|
+
return backupPath;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Restore working spec from backup
|
|
205
|
+
*
|
|
206
|
+
* @param {string} backupPath - Path to backup file
|
|
207
|
+
* @returns {Promise<void>}
|
|
208
|
+
*/
|
|
209
|
+
async restoreSpecFile(backupPath) {
|
|
210
|
+
const specPath = this.getSpecFilePath();
|
|
211
|
+
await fs.copyFile(backupPath, specPath);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* List all backup files
|
|
216
|
+
*
|
|
217
|
+
* @returns {Promise<string[]>} Array of backup file paths
|
|
218
|
+
*/
|
|
219
|
+
async listBackups() {
|
|
220
|
+
const specPath = this.getSpecFilePath();
|
|
221
|
+
const cawsDir = path.dirname(specPath);
|
|
222
|
+
const specName = path.basename(specPath);
|
|
223
|
+
|
|
224
|
+
try {
|
|
225
|
+
const files = await fs.readdir(cawsDir);
|
|
226
|
+
const backups = files
|
|
227
|
+
.filter((f) => f.startsWith(`${specName}.backup-`))
|
|
228
|
+
.map((f) => path.join(cawsDir, f));
|
|
229
|
+
|
|
230
|
+
// Sort by timestamp (newest first)
|
|
231
|
+
backups.sort().reverse();
|
|
232
|
+
|
|
233
|
+
return backups;
|
|
234
|
+
} catch {
|
|
235
|
+
return [];
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Delete old backup files
|
|
241
|
+
*
|
|
242
|
+
* @param {Object} options - Cleanup options
|
|
243
|
+
* @param {number} options.maxAge - Maximum age in milliseconds (default: 7 days)
|
|
244
|
+
* @param {number} options.keep - Minimum number of backups to keep (default: 5)
|
|
245
|
+
* @returns {Promise<number>} Number of backups deleted
|
|
246
|
+
*/
|
|
247
|
+
async cleanupBackups(options = {}) {
|
|
248
|
+
const maxAge = options.maxAge ?? 7 * 24 * 60 * 60 * 1000; // 7 days
|
|
249
|
+
const keepCount = options.keep ?? 5;
|
|
250
|
+
|
|
251
|
+
const backups = await this.listBackups();
|
|
252
|
+
const now = Date.now();
|
|
253
|
+
let deleted = 0;
|
|
254
|
+
|
|
255
|
+
// Delete old backups beyond the keep count
|
|
256
|
+
for (let i = 0; i < backups.length; i++) {
|
|
257
|
+
const backupPath = backups[i];
|
|
258
|
+
|
|
259
|
+
try {
|
|
260
|
+
const stats = await fs.stat(backupPath);
|
|
261
|
+
const age = now - stats.mtimeMs;
|
|
262
|
+
|
|
263
|
+
// Keep the most recent N backups, or delete if too old
|
|
264
|
+
const shouldDelete = i >= keepCount && age > maxAge;
|
|
265
|
+
|
|
266
|
+
if (shouldDelete) {
|
|
267
|
+
await fs.unlink(backupPath);
|
|
268
|
+
deleted++;
|
|
269
|
+
}
|
|
270
|
+
} catch {
|
|
271
|
+
// Skip files that can't be accessed
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return deleted;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Validate spec file exists and is parseable
|
|
280
|
+
*
|
|
281
|
+
* @returns {Promise<Object>} Validation result
|
|
282
|
+
*/
|
|
283
|
+
async validateSpecFile() {
|
|
284
|
+
try {
|
|
285
|
+
const spec = await this.readSpecFile();
|
|
286
|
+
return {
|
|
287
|
+
valid: true,
|
|
288
|
+
spec,
|
|
289
|
+
};
|
|
290
|
+
} catch (error) {
|
|
291
|
+
return {
|
|
292
|
+
valid: false,
|
|
293
|
+
error: error.message,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Clean up old temporary spec files
|
|
300
|
+
*
|
|
301
|
+
* Removes temp files older than specified age.
|
|
302
|
+
*
|
|
303
|
+
* @param {number} maxAge - Maximum age in milliseconds (default: 1 hour)
|
|
304
|
+
* @returns {Promise<number>} Number of files cleaned up
|
|
305
|
+
*/
|
|
306
|
+
async cleanupTempFiles(maxAge = 3600000) {
|
|
307
|
+
try {
|
|
308
|
+
const files = await fs.readdir(this.tempDir);
|
|
309
|
+
const specFiles = files.filter((f) => f.startsWith('caws-spec-'));
|
|
310
|
+
|
|
311
|
+
let cleaned = 0;
|
|
312
|
+
const now = Date.now();
|
|
313
|
+
|
|
314
|
+
for (const file of specFiles) {
|
|
315
|
+
const filePath = path.join(this.tempDir, file);
|
|
316
|
+
try {
|
|
317
|
+
const stats = await fs.stat(filePath);
|
|
318
|
+
const age = now - stats.mtimeMs;
|
|
319
|
+
|
|
320
|
+
if (age > maxAge) {
|
|
321
|
+
await fs.unlink(filePath);
|
|
322
|
+
cleaned++;
|
|
323
|
+
}
|
|
324
|
+
} catch {
|
|
325
|
+
// Skip files that can't be accessed
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return cleaned;
|
|
330
|
+
} catch {
|
|
331
|
+
return 0;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Get spec file stats (size, modified date, etc.)
|
|
337
|
+
*
|
|
338
|
+
* @returns {Promise<Object>} File stats
|
|
339
|
+
*/
|
|
340
|
+
async getSpecFileStats() {
|
|
341
|
+
const specPath = this.getSpecFilePath();
|
|
342
|
+
|
|
343
|
+
try {
|
|
344
|
+
const stats = await fs.stat(specPath);
|
|
345
|
+
const content = await fs.readFile(specPath, 'utf-8');
|
|
346
|
+
const lines = content.split('\n').length;
|
|
347
|
+
|
|
348
|
+
return {
|
|
349
|
+
exists: true,
|
|
350
|
+
size: stats.size,
|
|
351
|
+
sizeKB: Math.round((stats.size / 1024) * 10) / 10,
|
|
352
|
+
lines,
|
|
353
|
+
modified: stats.mtime,
|
|
354
|
+
created: stats.birthtime,
|
|
355
|
+
};
|
|
356
|
+
} catch (error) {
|
|
357
|
+
if (error.code === 'ENOENT') {
|
|
358
|
+
return {
|
|
359
|
+
exists: false,
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
throw error;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Create a new SpecFileManager instance with different configuration
|
|
368
|
+
*
|
|
369
|
+
* @param {Object} config - New configuration
|
|
370
|
+
* @returns {SpecFileManager} New instance
|
|
371
|
+
*/
|
|
372
|
+
withConfig(config) {
|
|
373
|
+
return new SpecFileManager({
|
|
374
|
+
projectRoot: this.projectRoot,
|
|
375
|
+
useTemporaryFiles: this.useTemporaryFiles,
|
|
376
|
+
tempDir: this.tempDir,
|
|
377
|
+
...config,
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Create a SpecFileManager instance with default configuration
|
|
384
|
+
*
|
|
385
|
+
* @param {string} projectRoot - Project root directory
|
|
386
|
+
* @param {Object} options - Additional options
|
|
387
|
+
* @returns {SpecFileManager} SpecFileManager instance
|
|
388
|
+
*/
|
|
389
|
+
function createSpecFileManager(projectRoot, options = {}) {
|
|
390
|
+
return new SpecFileManager({
|
|
391
|
+
projectRoot,
|
|
392
|
+
...options,
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Export singleton instance for convenience
|
|
397
|
+
const defaultSpecFileManager = new SpecFileManager();
|
|
398
|
+
|
|
399
|
+
module.exports = {
|
|
400
|
+
SpecFileManager,
|
|
401
|
+
defaultSpecFileManager,
|
|
402
|
+
createSpecFileManager,
|
|
403
|
+
|
|
404
|
+
// Convenience exports for backward compatibility
|
|
405
|
+
specToYaml: (spec) => defaultSpecFileManager.specToYaml(spec),
|
|
406
|
+
yamlToSpec: (yaml) => defaultSpecFileManager.yamlToSpec(yaml),
|
|
407
|
+
readSpecFile: (projectRoot) => {
|
|
408
|
+
if (projectRoot) {
|
|
409
|
+
return createSpecFileManager(projectRoot).readSpecFile();
|
|
410
|
+
}
|
|
411
|
+
return defaultSpecFileManager.readSpecFile();
|
|
412
|
+
},
|
|
413
|
+
writeSpecFile: (spec, projectRoot, options) => {
|
|
414
|
+
if (projectRoot) {
|
|
415
|
+
return createSpecFileManager(projectRoot).writeSpecFile(spec, options);
|
|
416
|
+
}
|
|
417
|
+
return defaultSpecFileManager.writeSpecFile(spec, options);
|
|
418
|
+
},
|
|
419
|
+
};
|
|
@@ -26,4 +26,18 @@ export function getFieldSuggestion(field: string, _spec: any): string;
|
|
|
26
26
|
* @returns {boolean} Whether field can be auto-fixed
|
|
27
27
|
*/
|
|
28
28
|
export function canAutoFixField(field: string, _spec: any): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Calculate compliance score based on errors and warnings
|
|
31
|
+
* Score ranges from 0 (many issues) to 1 (perfect)
|
|
32
|
+
* @param {Array} errors - Validation errors
|
|
33
|
+
* @param {Array} warnings - Validation warnings
|
|
34
|
+
* @returns {number} Compliance score (0-1)
|
|
35
|
+
*/
|
|
36
|
+
export function calculateComplianceScore(errors: any[], warnings: any[]): number;
|
|
37
|
+
/**
|
|
38
|
+
* Get compliance grade from score
|
|
39
|
+
* @param {number} score - Compliance score (0-1)
|
|
40
|
+
* @returns {string} Grade (A, B, C, D, F)
|
|
41
|
+
*/
|
|
42
|
+
export function getComplianceGrade(score: number): string;
|
|
29
43
|
//# sourceMappingURL=spec-validation.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spec-validation.d.ts","sourceRoot":"","sources":["../../src/validation/spec-validation.js"],"names":[],"mappings":"AAQA;;;;;GAKG;AACH,mEA8HC;AAED;;;;;GAKG;AACH,
|
|
1
|
+
{"version":3,"file":"spec-validation.d.ts","sourceRoot":"","sources":["../../src/validation/spec-validation.js"],"names":[],"mappings":"AAQA;;;;;GAKG;AACH,mEA8HC;AAED;;;;;GAKG;AACH,kFAyWC;AAoCD;;;;;GAKG;AACH,0CAJW,MAAM,eAEJ,MAAM,CAkBlB;AAED;;;;;GAKG;AACH,uCAJW,MAAM,eAEJ,OAAO,CAKnB;AAnED;;;;;;GAMG;AACH,0EAFa,MAAM,CAclB;AAED;;;;GAIG;AACH,0CAHW,MAAM,GACJ,MAAM,CAQlB"}
|