@polymorphism-tech/morph-spec 3.1.0 โ 3.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/CLAUDE.md +534 -0
- package/README.md +78 -4
- package/bin/morph-spec.js +50 -1
- package/bin/render-template.js +56 -10
- package/bin/task-manager.cjs +101 -7
- package/docs/cli-auto-detection.md +219 -0
- package/docs/llm-interaction-config.md +735 -0
- package/docs/troubleshooting.md +269 -0
- package/package.json +5 -1
- package/src/commands/advance-phase.js +93 -2
- package/src/commands/approve.js +221 -0
- package/src/commands/capture-pattern.js +121 -0
- package/src/commands/generate.js +128 -1
- package/src/commands/init.js +37 -0
- package/src/commands/migrate-state.js +158 -0
- package/src/commands/search-patterns.js +126 -0
- package/src/commands/spawn-team.js +172 -0
- package/src/commands/task.js +2 -2
- package/src/commands/update.js +36 -0
- package/src/commands/upgrade.js +346 -0
- package/src/generator/.gitkeep +0 -0
- package/src/generator/config-generator.js +206 -0
- package/src/generator/templates/config.json.template +40 -0
- package/src/generator/templates/project.md.template +67 -0
- package/src/lib/checkpoint-hooks.js +258 -0
- package/src/lib/metadata-extractor.js +380 -0
- package/src/lib/phase-state-machine.js +214 -0
- package/src/lib/state-manager.js +120 -0
- package/src/lib/template-data-sources.js +325 -0
- package/src/lib/validators/content-validator.js +351 -0
- package/src/llm/.gitkeep +0 -0
- package/src/llm/analyzer.js +215 -0
- package/src/llm/environment-detector.js +43 -0
- package/src/llm/few-shot-examples.js +216 -0
- package/src/llm/project-config-schema.json +188 -0
- package/src/llm/prompt-builder.js +96 -0
- package/src/llm/schema-validator.js +121 -0
- package/src/orchestrator.js +206 -0
- package/src/sanitizer/.gitkeep +0 -0
- package/src/sanitizer/context-sanitizer.js +221 -0
- package/src/sanitizer/patterns.js +163 -0
- package/src/scanner/.gitkeep +0 -0
- package/src/scanner/project-scanner.js +242 -0
- package/src/types/index.js +477 -0
- package/src/ui/.gitkeep +0 -0
- package/src/ui/diff-display.js +91 -0
- package/src/ui/interactive-wizard.js +96 -0
- package/src/ui/user-review.js +211 -0
- package/src/ui/wizard-questions.js +190 -0
- package/src/writer/.gitkeep +0 -0
- package/src/writer/file-writer.js +86 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview ConfigGenerator - Renders templates and generates config files
|
|
3
|
+
* @module morph-spec/generator/config-generator
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { readFile, access, copyFile } from 'fs/promises';
|
|
7
|
+
import { join, dirname } from 'path';
|
|
8
|
+
import Handlebars from 'handlebars';
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
10
|
+
import Ajv from 'ajv';
|
|
11
|
+
|
|
12
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
13
|
+
const __dirname = dirname(__filename);
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @typedef {import('../types/index.js').ProjectConfig} ProjectConfig
|
|
17
|
+
* @typedef {import('../types/index.js').GeneratedConfigs} GeneratedConfigs
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Validation Error
|
|
22
|
+
*/
|
|
23
|
+
export class ValidationError extends Error {
|
|
24
|
+
constructor(message, field, value) {
|
|
25
|
+
super(message);
|
|
26
|
+
this.name = 'ValidationError';
|
|
27
|
+
this.field = field;
|
|
28
|
+
this.value = value;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* ConfigGenerator - Generates configuration files from ProjectConfig
|
|
34
|
+
* @class
|
|
35
|
+
*/
|
|
36
|
+
export class ConfigGenerator {
|
|
37
|
+
constructor() {
|
|
38
|
+
this.projectMdTemplate = null;
|
|
39
|
+
this.configJsonTemplate = null;
|
|
40
|
+
this.ajv = new Ajv({ allErrors: true });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Load templates from filesystem
|
|
45
|
+
* @private
|
|
46
|
+
*/
|
|
47
|
+
async loadTemplates() {
|
|
48
|
+
if (this.projectMdTemplate && this.configJsonTemplate) {
|
|
49
|
+
return; // Already loaded
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const templatesDir = join(__dirname, 'templates');
|
|
53
|
+
|
|
54
|
+
const [projectMdSource, configJsonSource] = await Promise.all([
|
|
55
|
+
readFile(join(templatesDir, 'project.md.template'), 'utf-8'),
|
|
56
|
+
readFile(join(templatesDir, 'config.json.template'), 'utf-8')
|
|
57
|
+
]);
|
|
58
|
+
|
|
59
|
+
this.projectMdTemplate = Handlebars.compile(projectMdSource);
|
|
60
|
+
this.configJsonTemplate = Handlebars.compile(configJsonSource);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Generate configuration files from project config
|
|
65
|
+
* @param {ProjectConfig} projectConfig - Detected project config
|
|
66
|
+
* @returns {Promise<GeneratedConfigs>}
|
|
67
|
+
*/
|
|
68
|
+
async generate(projectConfig) {
|
|
69
|
+
// Load templates if not already loaded
|
|
70
|
+
await this.loadTemplates();
|
|
71
|
+
|
|
72
|
+
// Add generation timestamp
|
|
73
|
+
const context = {
|
|
74
|
+
...projectConfig,
|
|
75
|
+
generatedAt: new Date().toISOString()
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// Render templates
|
|
79
|
+
const projectMd = this.renderProjectMd(context);
|
|
80
|
+
const configJson = this.renderConfigJson(context);
|
|
81
|
+
|
|
82
|
+
// Parse config.json to object
|
|
83
|
+
const configObject = JSON.parse(configJson);
|
|
84
|
+
|
|
85
|
+
// Validate config.json (optional, but good practice)
|
|
86
|
+
// Note: agent-schema.json validation would happen here if we had the schema
|
|
87
|
+
// For now, we just ensure it's valid JSON
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
projectMd,
|
|
91
|
+
configJson,
|
|
92
|
+
configObject
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Render project.md template
|
|
98
|
+
* @param {ProjectConfig} config - Project config
|
|
99
|
+
* @returns {string} Rendered markdown
|
|
100
|
+
*/
|
|
101
|
+
renderProjectMd(config) {
|
|
102
|
+
if (!this.projectMdTemplate) {
|
|
103
|
+
throw new Error('Templates not loaded. Call loadTemplates() first.');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return this.projectMdTemplate(config);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Render config.json template
|
|
111
|
+
* @param {ProjectConfig} config - Project config
|
|
112
|
+
* @returns {string} Rendered JSON string
|
|
113
|
+
*/
|
|
114
|
+
renderConfigJson(config) {
|
|
115
|
+
if (!this.configJsonTemplate) {
|
|
116
|
+
throw new Error('Templates not loaded. Call loadTemplates() first.');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const rendered = this.configJsonTemplate(config);
|
|
120
|
+
|
|
121
|
+
// Validate that rendered output is valid JSON
|
|
122
|
+
try {
|
|
123
|
+
JSON.parse(rendered);
|
|
124
|
+
return rendered;
|
|
125
|
+
} catch (error) {
|
|
126
|
+
throw new ValidationError(
|
|
127
|
+
`Rendered config.json is not valid JSON: ${error.message}`,
|
|
128
|
+
'configJson',
|
|
129
|
+
rendered
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Validate config.json against agent schema
|
|
136
|
+
* @param {string} configJson - JSON string
|
|
137
|
+
* @returns {boolean} True if valid
|
|
138
|
+
* @throws {ValidationError} If validation fails
|
|
139
|
+
*/
|
|
140
|
+
validateConfigJson(configJson) {
|
|
141
|
+
// Parse JSON
|
|
142
|
+
let parsed;
|
|
143
|
+
try {
|
|
144
|
+
parsed = JSON.parse(configJson);
|
|
145
|
+
} catch (error) {
|
|
146
|
+
throw new ValidationError(
|
|
147
|
+
`Invalid JSON: ${error.message}`,
|
|
148
|
+
'configJson',
|
|
149
|
+
configJson
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Basic validation - ensure required fields exist
|
|
154
|
+
const requiredFields = ['name', 'type', 'description', 'stack', 'architecture'];
|
|
155
|
+
for (const field of requiredFields) {
|
|
156
|
+
if (!parsed[field]) {
|
|
157
|
+
throw new ValidationError(
|
|
158
|
+
`Missing required field: ${field}`,
|
|
159
|
+
field,
|
|
160
|
+
parsed
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Validate stack.backend is required
|
|
166
|
+
if (!parsed.stack || !parsed.stack.backend) {
|
|
167
|
+
throw new ValidationError(
|
|
168
|
+
'stack.backend is required',
|
|
169
|
+
'stack.backend',
|
|
170
|
+
parsed
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Backup existing configuration files
|
|
179
|
+
* @param {string} cwd - Current working directory
|
|
180
|
+
* @returns {Promise<void>}
|
|
181
|
+
*/
|
|
182
|
+
async backupExisting(cwd) {
|
|
183
|
+
const projectMdPath = join(cwd, '.morph', 'project.md');
|
|
184
|
+
const configJsonPath = join(cwd, '.morph', 'config', 'config.json');
|
|
185
|
+
|
|
186
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
187
|
+
|
|
188
|
+
// Backup project.md if exists
|
|
189
|
+
try {
|
|
190
|
+
await access(projectMdPath);
|
|
191
|
+
const backupPath = join(cwd, '.morph', `project.md.${timestamp}.backup`);
|
|
192
|
+
await copyFile(projectMdPath, backupPath);
|
|
193
|
+
} catch (error) {
|
|
194
|
+
// File doesn't exist, no need to backup
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Backup config.json if exists
|
|
198
|
+
try {
|
|
199
|
+
await access(configJsonPath);
|
|
200
|
+
const backupPath = join(cwd, '.morph', 'config', `config.json.${timestamp}.backup`);
|
|
201
|
+
await copyFile(configJsonPath, backupPath);
|
|
202
|
+
} catch (error) {
|
|
203
|
+
// File doesn't exist, no need to backup
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../schema/agent-schema.json",
|
|
3
|
+
"name": "{{name}}",
|
|
4
|
+
"type": "{{type}}",
|
|
5
|
+
"description": "{{description}}",
|
|
6
|
+
"stack": {
|
|
7
|
+
{{#if stack.frontend}}
|
|
8
|
+
"frontend": {
|
|
9
|
+
"tech": "{{stack.frontend.tech}}",
|
|
10
|
+
"version": "{{stack.frontend.version}}"{{#if stack.frontend.details}},
|
|
11
|
+
"details": "{{stack.frontend.details}}"{{/if}}
|
|
12
|
+
},
|
|
13
|
+
{{/if}}
|
|
14
|
+
"backend": {
|
|
15
|
+
"tech": "{{stack.backend.tech}}",
|
|
16
|
+
"version": "{{stack.backend.version}}"{{#if stack.backend.details}},
|
|
17
|
+
"details": "{{stack.backend.details}}"{{/if}}
|
|
18
|
+
}{{#if stack.database}},
|
|
19
|
+
"database": {
|
|
20
|
+
"tech": "{{stack.database.tech}}",
|
|
21
|
+
"version": "{{stack.database.version}}"{{#if stack.database.details}},
|
|
22
|
+
"details": "{{stack.database.details}}"{{/if}}
|
|
23
|
+
}{{/if}}{{#if stack.hosting}},
|
|
24
|
+
"hosting": "{{stack.hosting}}"{{/if}}
|
|
25
|
+
},
|
|
26
|
+
"architecture": "{{architecture}}",
|
|
27
|
+
"conventions": "{{conventions}}",
|
|
28
|
+
"infrastructure": {
|
|
29
|
+
"azure": {{hasAzure}},
|
|
30
|
+
"docker": {{hasDocker}},
|
|
31
|
+
"devops": {{hasDevOps}}
|
|
32
|
+
}{{#if repository}},
|
|
33
|
+
"repository": "{{repository}}"{{/if}},
|
|
34
|
+
"meta": {
|
|
35
|
+
"generatedBy": "morph-spec-cli",
|
|
36
|
+
"generatedAt": "{{generatedAt}}",
|
|
37
|
+
"llmConfidence": {{confidence}},
|
|
38
|
+
"autoDetected": true
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# {{name}}
|
|
2
|
+
|
|
3
|
+
> {{description}}
|
|
4
|
+
|
|
5
|
+
## Stack
|
|
6
|
+
|
|
7
|
+
| Component | Technology |
|
|
8
|
+
|-----------|------------|
|
|
9
|
+
{{#if stack.frontend}}
|
|
10
|
+
| **Frontend** | {{stack.frontend.tech}} {{stack.frontend.version}}{{#if stack.frontend.details}} - {{stack.frontend.details}}{{/if}} |
|
|
11
|
+
{{/if}}
|
|
12
|
+
| **Backend** | {{stack.backend.tech}} {{stack.backend.version}}{{#if stack.backend.details}} - {{stack.backend.details}}{{/if}} |
|
|
13
|
+
{{#if stack.database}}
|
|
14
|
+
| **Database** | {{stack.database.tech}} {{stack.database.version}}{{#if stack.database.details}} - {{stack.database.details}}{{/if}} |
|
|
15
|
+
{{/if}}
|
|
16
|
+
{{#if stack.hosting}}
|
|
17
|
+
| **Hosting** | {{stack.hosting}} |
|
|
18
|
+
{{/if}}
|
|
19
|
+
|
|
20
|
+
## Architecture
|
|
21
|
+
|
|
22
|
+
**Pattern:** {{architecture}}
|
|
23
|
+
|
|
24
|
+
{{projectStructure}}
|
|
25
|
+
|
|
26
|
+
## Code Conventions
|
|
27
|
+
|
|
28
|
+
{{conventions}}
|
|
29
|
+
|
|
30
|
+
## Infrastructure
|
|
31
|
+
|
|
32
|
+
{{#if hasAzure}}
|
|
33
|
+
- โ
**Azure** - Uses Azure infrastructure (Bicep files detected)
|
|
34
|
+
{{else}}
|
|
35
|
+
- โ **Azure** - No Azure resources detected
|
|
36
|
+
{{/if}}
|
|
37
|
+
|
|
38
|
+
{{#if hasDocker}}
|
|
39
|
+
- โ
**Docker** - Containerized (Dockerfile/docker-compose.yml detected)
|
|
40
|
+
{{else}}
|
|
41
|
+
- โ **Docker** - Not containerized
|
|
42
|
+
{{/if}}
|
|
43
|
+
|
|
44
|
+
{{#if hasDevOps}}
|
|
45
|
+
- โ
**CI/CD** - Pipelines detected
|
|
46
|
+
{{else}}
|
|
47
|
+
- โ **CI/CD** - No pipelines detected
|
|
48
|
+
{{/if}}
|
|
49
|
+
|
|
50
|
+
{{#if repository}}
|
|
51
|
+
## Repository
|
|
52
|
+
|
|
53
|
+
{{repository}}
|
|
54
|
+
{{/if}}
|
|
55
|
+
|
|
56
|
+
{{#if warnings.length}}
|
|
57
|
+
## โ ๏ธ Warnings
|
|
58
|
+
|
|
59
|
+
{{#each warnings}}
|
|
60
|
+
- {{this}}
|
|
61
|
+
{{/each}}
|
|
62
|
+
{{/if}}
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
*Auto-generated by MORPH-SPEC CLI on {{generatedAt}}*
|
|
67
|
+
*LLM Confidence: {{confidence}}%*
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { execSync } from 'child_process';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Checkpoint Hooks - Automated validation orchestration
|
|
7
|
+
*
|
|
8
|
+
* Runs validators, tests, and linters at checkpoint milestones (every N tasks).
|
|
9
|
+
* Blocks progress if critical validations fail.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Load LLM interaction configuration
|
|
14
|
+
* @returns {Object} Configuration object
|
|
15
|
+
*/
|
|
16
|
+
function loadLLMInteractionConfig() {
|
|
17
|
+
const configPath = join(process.cwd(), '.morph/config/llm-interaction.json');
|
|
18
|
+
|
|
19
|
+
if (!existsSync(configPath)) {
|
|
20
|
+
// Return defaults if config doesn't exist
|
|
21
|
+
return {
|
|
22
|
+
checkpoints: {
|
|
23
|
+
frequency: 3,
|
|
24
|
+
autoValidate: true,
|
|
25
|
+
validators: {
|
|
26
|
+
enabled: ['architecture', 'packages', 'design-system', 'security'],
|
|
27
|
+
optional: []
|
|
28
|
+
},
|
|
29
|
+
hooks: {
|
|
30
|
+
runTests: false,
|
|
31
|
+
runLinters: true,
|
|
32
|
+
buildCheck: false
|
|
33
|
+
},
|
|
34
|
+
onFailure: {
|
|
35
|
+
blockProgress: true,
|
|
36
|
+
maxRetries: 3,
|
|
37
|
+
escalateAfter: 3
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return JSON.parse(readFileSync(configPath, 'utf8'));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Run a single validator
|
|
48
|
+
* @param {string} validatorName - Validator to run (architecture, packages, etc.)
|
|
49
|
+
* @param {string} featureName - Feature being validated
|
|
50
|
+
* @returns {Promise<Object>} Validation result
|
|
51
|
+
*/
|
|
52
|
+
async function runValidator(validatorName, featureName) {
|
|
53
|
+
try {
|
|
54
|
+
// Use existing validate command
|
|
55
|
+
const result = execSync(
|
|
56
|
+
`node bin/validate.js ${validatorName} --feature=${featureName} --json`,
|
|
57
|
+
{ encoding: 'utf8', stdio: 'pipe' }
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const parsed = JSON.parse(result);
|
|
61
|
+
return {
|
|
62
|
+
validator: validatorName,
|
|
63
|
+
passed: parsed.errors === 0,
|
|
64
|
+
errors: parsed.errors || 0,
|
|
65
|
+
warnings: parsed.warnings || 0,
|
|
66
|
+
details: parsed.issues || []
|
|
67
|
+
};
|
|
68
|
+
} catch (error) {
|
|
69
|
+
return {
|
|
70
|
+
validator: validatorName,
|
|
71
|
+
passed: false,
|
|
72
|
+
errors: 1,
|
|
73
|
+
warnings: 0,
|
|
74
|
+
details: [{ message: error.message, severity: 'error' }]
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Run tests if configured
|
|
81
|
+
* @param {string} featureName - Feature being tested
|
|
82
|
+
* @returns {Promise<Object>} Test result
|
|
83
|
+
*/
|
|
84
|
+
async function runTests(featureName) {
|
|
85
|
+
try {
|
|
86
|
+
execSync('npm test --passWithNoTests', { encoding: 'utf8', stdio: 'pipe' });
|
|
87
|
+
return {
|
|
88
|
+
validator: 'tests',
|
|
89
|
+
passed: true,
|
|
90
|
+
errors: 0,
|
|
91
|
+
warnings: 0,
|
|
92
|
+
details: []
|
|
93
|
+
};
|
|
94
|
+
} catch (error) {
|
|
95
|
+
return {
|
|
96
|
+
validator: 'tests',
|
|
97
|
+
passed: false,
|
|
98
|
+
errors: 1,
|
|
99
|
+
warnings: 0,
|
|
100
|
+
details: [{ message: 'Test suite failed', severity: 'error' }]
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Run linters if configured
|
|
107
|
+
* @param {string} featureName - Feature being linted
|
|
108
|
+
* @returns {Promise<Object>} Linter result
|
|
109
|
+
*/
|
|
110
|
+
async function runLinters(featureName) {
|
|
111
|
+
try {
|
|
112
|
+
// Check if eslint exists
|
|
113
|
+
if (existsSync(join(process.cwd(), 'node_modules/.bin/eslint'))) {
|
|
114
|
+
execSync('npm run lint --if-present', { encoding: 'utf8', stdio: 'pipe' });
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
validator: 'linters',
|
|
119
|
+
passed: true,
|
|
120
|
+
errors: 0,
|
|
121
|
+
warnings: 0,
|
|
122
|
+
details: []
|
|
123
|
+
};
|
|
124
|
+
} catch (error) {
|
|
125
|
+
return {
|
|
126
|
+
validator: 'linters',
|
|
127
|
+
passed: false,
|
|
128
|
+
errors: 0,
|
|
129
|
+
warnings: 1,
|
|
130
|
+
details: [{ message: 'Linting warnings detected', severity: 'warning' }]
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Run checkpoint hooks - orchestrates all validation
|
|
137
|
+
* @param {string} featureName - Feature name
|
|
138
|
+
* @param {number} checkpointNum - Checkpoint number (1, 2, 3, etc.)
|
|
139
|
+
* @returns {Promise<Object>} Checkpoint result
|
|
140
|
+
*/
|
|
141
|
+
export async function runCheckpointHooks(featureName, checkpointNum) {
|
|
142
|
+
const config = loadLLMInteractionConfig();
|
|
143
|
+
const checkpointConfig = config.checkpoints;
|
|
144
|
+
|
|
145
|
+
if (!checkpointConfig.autoValidate) {
|
|
146
|
+
return {
|
|
147
|
+
passed: true,
|
|
148
|
+
skipped: true,
|
|
149
|
+
message: 'Auto-validation disabled in config'
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
console.log(`\n๐ Running CHECKPOINT ${checkpointNum} for feature: ${featureName}`);
|
|
154
|
+
console.log('โ'.repeat(60));
|
|
155
|
+
|
|
156
|
+
const results = [];
|
|
157
|
+
|
|
158
|
+
// 1. Run enabled validators
|
|
159
|
+
console.log('\n๐ Running validators...');
|
|
160
|
+
for (const validatorName of checkpointConfig.validators.enabled) {
|
|
161
|
+
process.stdout.write(` โข ${validatorName}... `);
|
|
162
|
+
const result = await runValidator(validatorName, featureName);
|
|
163
|
+
results.push(result);
|
|
164
|
+
|
|
165
|
+
if (result.passed) {
|
|
166
|
+
console.log('โ
PASSED');
|
|
167
|
+
} else {
|
|
168
|
+
console.log(`โ FAILED (${result.errors} errors, ${result.warnings} warnings)`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// 2. Run tests (if configured)
|
|
173
|
+
if (checkpointConfig.hooks.runTests) {
|
|
174
|
+
console.log('\n๐งช Running tests...');
|
|
175
|
+
process.stdout.write(' โข test suite... ');
|
|
176
|
+
const testResult = await runTests(featureName);
|
|
177
|
+
results.push(testResult);
|
|
178
|
+
|
|
179
|
+
if (testResult.passed) {
|
|
180
|
+
console.log('โ
PASSED');
|
|
181
|
+
} else {
|
|
182
|
+
console.log('โ FAILED');
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// 3. Run linters (if configured)
|
|
187
|
+
if (checkpointConfig.hooks.runLinters) {
|
|
188
|
+
console.log('\n๐จ Running linters...');
|
|
189
|
+
process.stdout.write(' โข code style... ');
|
|
190
|
+
const lintResult = await runLinters(featureName);
|
|
191
|
+
results.push(lintResult);
|
|
192
|
+
|
|
193
|
+
if (lintResult.passed) {
|
|
194
|
+
console.log('โ
PASSED');
|
|
195
|
+
} else {
|
|
196
|
+
console.log('โ ๏ธ WARNINGS');
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Calculate overall pass/fail
|
|
201
|
+
const errorCount = results.reduce((sum, r) => sum + r.errors, 0);
|
|
202
|
+
const warningCount = results.reduce((sum, r) => sum + r.warnings, 0);
|
|
203
|
+
const passed = errorCount === 0;
|
|
204
|
+
|
|
205
|
+
console.log('\n' + 'โ'.repeat(60));
|
|
206
|
+
console.log(`\n๐ Checkpoint ${checkpointNum} Summary:`);
|
|
207
|
+
console.log(` Errors: ${errorCount}`);
|
|
208
|
+
console.log(` Warnings: ${warningCount}`);
|
|
209
|
+
console.log(` Status: ${passed ? 'โ
PASSED' : 'โ FAILED'}`);
|
|
210
|
+
|
|
211
|
+
if (!passed) {
|
|
212
|
+
console.log('\nโ ๏ธ Checkpoint failed! Fix violations before proceeding.');
|
|
213
|
+
|
|
214
|
+
// Show details of failures
|
|
215
|
+
results.filter(r => r.errors > 0).forEach(r => {
|
|
216
|
+
console.log(`\nโ ${r.validator} errors:`);
|
|
217
|
+
r.details.forEach(d => {
|
|
218
|
+
if (d.severity === 'error') {
|
|
219
|
+
console.log(` - ${d.message}`);
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
console.log('\n' + 'โ'.repeat(60) + '\n');
|
|
226
|
+
|
|
227
|
+
return {
|
|
228
|
+
passed,
|
|
229
|
+
checkpointNum,
|
|
230
|
+
timestamp: new Date().toISOString(),
|
|
231
|
+
results,
|
|
232
|
+
summary: {
|
|
233
|
+
errors: errorCount,
|
|
234
|
+
warnings: warningCount,
|
|
235
|
+
validatorsRun: results.length
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Check if checkpoint should run based on task count
|
|
242
|
+
* @param {number} tasksCompleted - Number of tasks completed
|
|
243
|
+
* @param {number} frequency - Checkpoint frequency (default: 3)
|
|
244
|
+
* @returns {boolean} True if checkpoint should run
|
|
245
|
+
*/
|
|
246
|
+
export function shouldRunCheckpoint(tasksCompleted, frequency = 3) {
|
|
247
|
+
return tasksCompleted > 0 && tasksCompleted % frequency === 0;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Get checkpoint number from task count
|
|
252
|
+
* @param {number} tasksCompleted - Number of tasks completed
|
|
253
|
+
* @param {number} frequency - Checkpoint frequency (default: 3)
|
|
254
|
+
* @returns {number} Checkpoint number
|
|
255
|
+
*/
|
|
256
|
+
export function getCheckpointNumber(tasksCompleted, frequency = 3) {
|
|
257
|
+
return Math.floor(tasksCompleted / frequency);
|
|
258
|
+
}
|