project-iris 0.0.13 → 0.0.14
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 +261 -94
- package/bin/cli.js +21 -0
- package/flows/aidlc/README.md +372 -0
- package/flows/aidlc/agents/construction-agent.md +79 -0
- package/flows/aidlc/agents/inception-agent.md +97 -0
- package/flows/aidlc/agents/master-agent.md +61 -0
- package/flows/aidlc/agents/operations-agent.md +89 -0
- package/flows/aidlc/commands/construction-agent.md +63 -0
- package/flows/aidlc/commands/inception-agent.md +55 -0
- package/flows/aidlc/commands/master-agent.md +47 -0
- package/flows/aidlc/commands/operations-agent.md +77 -0
- package/flows/aidlc/context-config.yaml +67 -0
- package/flows/aidlc/memory-bank.yaml +104 -0
- package/flows/aidlc/quick-start.md +322 -0
- package/flows/aidlc/skills/construction/bolt-list.md +163 -0
- package/flows/aidlc/skills/construction/bolt-replan.md +345 -0
- package/flows/aidlc/skills/construction/bolt-start.md +442 -0
- package/flows/aidlc/skills/construction/bolt-status.md +185 -0
- package/flows/aidlc/skills/construction/navigator.md +196 -0
- package/flows/aidlc/skills/inception/bolt-plan.md +372 -0
- package/flows/aidlc/skills/inception/context.md +171 -0
- package/flows/aidlc/skills/inception/intent-create.md +211 -0
- package/flows/aidlc/skills/inception/intent-list.md +124 -0
- package/flows/aidlc/skills/inception/navigator.md +207 -0
- package/flows/aidlc/skills/inception/requirements.md +227 -0
- package/flows/aidlc/skills/inception/review.md +248 -0
- package/flows/aidlc/skills/inception/story-create.md +304 -0
- package/flows/aidlc/skills/inception/units.md +278 -0
- package/flows/aidlc/skills/master/analyze-context.md +239 -0
- package/flows/aidlc/skills/master/answer-question.md +141 -0
- package/flows/aidlc/skills/master/explain-flow.md +158 -0
- package/flows/aidlc/skills/master/project-init.md +281 -0
- package/flows/aidlc/skills/master/route-request.md +126 -0
- package/flows/aidlc/skills/operations/build.md +237 -0
- package/flows/aidlc/skills/operations/deploy.md +259 -0
- package/flows/aidlc/skills/operations/monitor.md +265 -0
- package/flows/aidlc/skills/operations/navigator.md +209 -0
- package/flows/aidlc/skills/operations/verify.md +224 -0
- package/{dist/iris_bundle/frameworks/iris-core → flows/aidlc}/templates/construction/bolt-types/ddd-construction-bolt.md +3 -3
- package/{dist → flows/aidlc}/templates/construction/bolt-types/spike-bolt.md +2 -2
- package/flows/aidlc/templates/construction/construction-log-template.md +129 -0
- package/flows/aidlc/templates/construction/standards/coding-standards.md +29 -0
- package/flows/aidlc/templates/construction/standards/system-architecture.md +22 -0
- package/flows/aidlc/templates/construction/standards/tech-stack.md +19 -0
- package/flows/aidlc/templates/inception/inception-log-template.md +134 -0
- package/flows/aidlc/templates/inception/project/README.md +55 -0
- package/flows/aidlc/templates/standards/catalog.yaml +345 -0
- package/flows/aidlc/templates/standards/coding-standards.guide.md +553 -0
- package/flows/aidlc/templates/standards/data-stack.guide.md +162 -0
- package/flows/aidlc/templates/standards/tech-stack.guide.md +280 -0
- package/lib/InstallerFactory.js +36 -0
- package/lib/analytics/env-detector.js +92 -0
- package/lib/analytics/index.js +22 -0
- package/lib/analytics/machine-id.js +33 -0
- package/lib/analytics/tracker.js +232 -0
- package/lib/cli-utils.js +342 -0
- package/lib/constants.js +32 -0
- package/lib/installer.js +402 -0
- package/lib/installers/AntigravityInstaller.js +22 -0
- package/lib/installers/ClaudeInstaller.js +85 -0
- package/lib/installers/ClineInstaller.js +21 -0
- package/lib/installers/CodexInstaller.js +21 -0
- package/lib/installers/CopilotInstaller.js +113 -0
- package/lib/installers/CursorInstaller.js +63 -0
- package/lib/installers/GeminiInstaller.js +75 -0
- package/lib/installers/KiroInstaller.js +22 -0
- package/lib/installers/OpenCodeInstaller.js +22 -0
- package/lib/installers/RooInstaller.js +22 -0
- package/lib/installers/ToolInstaller.js +73 -0
- package/lib/installers/WindsurfInstaller.js +22 -0
- package/lib/markdown-validator.ts +175 -0
- package/lib/yaml-validator.ts +99 -0
- package/package.json +105 -32
- package/scripts/artifact-validator.js +594 -0
- package/scripts/bolt-complete.js +606 -0
- package/scripts/status-integrity.js +598 -0
- package/dist/bridge/agent-runner.js +0 -190
- package/dist/bridge/connector-factory.js +0 -31
- package/dist/bridge/connectors/antigravity-connector.js +0 -18
- package/dist/bridge/connectors/cursor-connector.js +0 -31
- package/dist/bridge/connectors/in-process-connector.js +0 -29
- package/dist/bridge/connectors/vscode-connector.js +0 -31
- package/dist/bridge/connectors/windsurf-connector.js +0 -23
- package/dist/bridge/filesystem-connector.js +0 -110
- package/dist/bridge/helper.js +0 -203
- package/dist/bridge/types.js +0 -10
- package/dist/cli.js +0 -40
- package/dist/commands/ask.js +0 -259
- package/dist/commands/bridge.js +0 -88
- package/dist/commands/create.js +0 -25
- package/dist/commands/develop.js +0 -141
- package/dist/commands/doctor.js +0 -102
- package/dist/commands/flow.js +0 -301
- package/dist/commands/framework.js +0 -273
- package/dist/commands/generate.js +0 -59
- package/dist/commands/install.js +0 -100
- package/dist/commands/pack.js +0 -33
- package/dist/commands/phase.js +0 -38
- package/dist/commands/run.js +0 -199
- package/dist/commands/status.js +0 -114
- package/dist/commands/uninstall.js +0 -14
- package/dist/commands/use.js +0 -20
- package/dist/commands/validate.js +0 -102
- package/dist/framework/framework-loader.js +0 -97
- package/dist/framework/framework-paths.js +0 -48
- package/dist/framework/framework-types.js +0 -15
- package/dist/iris/artifact-checker.js +0 -78
- package/dist/iris/artifacts/config.js +0 -68
- package/dist/iris/artifacts/generator.js +0 -88
- package/dist/iris/artifacts/types.js +0 -1
- package/dist/iris/bundle.js +0 -44
- package/dist/iris/doctrine/collector.js +0 -124
- package/dist/iris/fixer.js +0 -149
- package/dist/iris/flows/manifest.js +0 -124
- package/dist/iris/framework-context.js +0 -49
- package/dist/iris/framework-manager.js +0 -215
- package/dist/iris/fs/atomic.js +0 -22
- package/dist/iris/guard.js +0 -38
- package/dist/iris/importers/index.js +0 -9
- package/dist/iris/importers/types.js +0 -8
- package/dist/iris/importers/writer.js +0 -139
- package/dist/iris/include.js +0 -49
- package/dist/iris/installer.js +0 -334
- package/dist/iris/interactive/env.js +0 -21
- package/dist/iris/interactive/intent-interview.js +0 -345
- package/dist/iris/interactive/intent-schema.js +0 -28
- package/dist/iris/interactive/interview-io.js +0 -22
- package/dist/iris/interview/config.js +0 -71
- package/dist/iris/interview/types.js +0 -16
- package/dist/iris/interview/utils.js +0 -38
- package/dist/iris/manifest.js +0 -54
- package/dist/iris/packer.js +0 -325
- package/dist/iris/parsers/unit-parser.js +0 -43
- package/dist/iris/paths.js +0 -18
- package/dist/iris/policy.js +0 -133
- package/dist/iris/proc.js +0 -56
- package/dist/iris/report.js +0 -53
- package/dist/iris/resolver.js +0 -66
- package/dist/iris/router.js +0 -114
- package/dist/iris/routes.js +0 -189
- package/dist/iris/run-state.js +0 -146
- package/dist/iris/state.js +0 -113
- package/dist/iris/templates.js +0 -70
- package/dist/iris/tmp.js +0 -24
- package/dist/iris/uninstaller.js +0 -181
- package/dist/iris/utils/interpolate.js +0 -42
- package/dist/iris/validator.js +0 -391
- package/dist/iris/workflow/config.js +0 -51
- package/dist/iris/workflow/engine.js +0 -129
- package/dist/iris/workflow/steps.js +0 -448
- package/dist/iris/workflow/types.js +0 -1
- package/dist/iris_bundle/frameworks/iris-core/framework.yaml +0 -9
- package/dist/iris_bundle/frameworks/iris-core/memory/memory-bank.yaml +0 -1
- package/dist/iris_bundle/frameworks/iris-core/policy.yaml +0 -7
- package/dist/iris_bundle/frameworks/iris-core/templates/config/memory-bank.yaml +0 -1
- package/dist/iris_bundle/frameworks/iris-core/templates/construction/bolt-types/spike-bolt.md +0 -240
- package/dist/lib.js +0 -96
- package/dist/templates/construction/bolt-template.md +0 -226
- package/dist/templates/construction/bolt-types/ddd-construction-bolt/adr-template.md +0 -49
- package/dist/templates/construction/bolt-types/ddd-construction-bolt/ddd-01-domain-model-template.md +0 -55
- package/dist/templates/construction/bolt-types/ddd-construction-bolt/ddd-02-technical-design-template.md +0 -67
- package/dist/templates/construction/bolt-types/ddd-construction-bolt/ddd-03-test-report-template.md +0 -62
- package/dist/templates/construction/bolt-types/ddd-construction-bolt.md +0 -528
- package/dist/templates/construction/bolt-types/simple-construction-bolt.md +0 -347
- package/dist/templates/inception/requirements-template.md +0 -144
- package/dist/templates/inception/stories-template.md +0 -38
- package/dist/templates/inception/story-template.md +0 -147
- package/dist/templates/inception/system-context-template.md +0 -29
- package/dist/templates/inception/unit-brief-template.md +0 -177
- package/dist/templates/inception/units-template.md +0 -52
- package/dist/utils/exit-codes.js +0 -7
- package/dist/utils/logo.js +0 -17
- package/dist/workflows/bolt-execution.js +0 -238
- package/dist/workflows/bolt-plan.js +0 -221
- package/dist/workflows/intent-inception.js +0 -285
- package/dist/workflows/memory-bank-generator.js +0 -180
- package/dist/workflows/reporting.js +0 -74
- /package/{dist/iris_bundle/frameworks/iris-core → flows/aidlc}/templates/construction/bolt-template.md +0 -0
- /package/{dist/iris_bundle/frameworks/iris-core → flows/aidlc}/templates/construction/bolt-types/ddd-construction-bolt/adr-template.md +0 -0
- /package/{dist/iris_bundle/frameworks/iris-core → flows/aidlc}/templates/construction/bolt-types/ddd-construction-bolt/ddd-01-domain-model-template.md +0 -0
- /package/{dist/iris_bundle/frameworks/iris-core → flows/aidlc}/templates/construction/bolt-types/ddd-construction-bolt/ddd-02-technical-design-template.md +0 -0
- /package/{dist/iris_bundle/frameworks/iris-core → flows/aidlc}/templates/construction/bolt-types/ddd-construction-bolt/ddd-03-test-report-template.md +0 -0
- /package/{dist/iris_bundle/frameworks/iris-core → flows/aidlc}/templates/construction/bolt-types/simple-construction-bolt.md +0 -0
- /package/{dist/iris_bundle/frameworks/iris-core → flows/aidlc}/templates/inception/requirements-template.md +0 -0
- /package/{dist/iris_bundle/frameworks/iris-core → flows/aidlc}/templates/inception/stories-template.md +0 -0
- /package/{dist/iris_bundle/frameworks/iris-core → flows/aidlc}/templates/inception/story-template.md +0 -0
- /package/{dist/iris_bundle/frameworks/iris-core → flows/aidlc}/templates/inception/system-context-template.md +0 -0
- /package/{dist/iris_bundle/frameworks/iris-core → flows/aidlc}/templates/inception/unit-brief-template.md +0 -0
- /package/{dist/iris_bundle/frameworks/iris-core → flows/aidlc}/templates/inception/units-template.md +0 -0
|
@@ -0,0 +1,594 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Artifact Validator for AI-DLC Memory Bank
|
|
5
|
+
*
|
|
6
|
+
* Validates structural consistency across all artifacts:
|
|
7
|
+
* - Naming conventions (folder/file patterns)
|
|
8
|
+
* - ID-filename consistency (frontmatter matches filenames)
|
|
9
|
+
* - Cross-reference integrity (references point to existing files)
|
|
10
|
+
* - Timestamp format (ISO 8601 without milliseconds)
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* node .iris/scripts/artifact-validator.js
|
|
14
|
+
* node .iris/scripts/artifact-validator.js --json
|
|
15
|
+
* node .iris/scripts/artifact-validator.js --fix
|
|
16
|
+
*
|
|
17
|
+
* Cross-platform: Works on Linux, macOS, Windows via Node.js
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const fs = require('fs-extra');
|
|
21
|
+
const path = require('path');
|
|
22
|
+
const yaml = require('js-yaml');
|
|
23
|
+
|
|
24
|
+
// Theme colors for output
|
|
25
|
+
const colors = {
|
|
26
|
+
reset: '\x1b[0m',
|
|
27
|
+
green: '\x1b[32m',
|
|
28
|
+
red: '\x1b[31m',
|
|
29
|
+
yellow: '\x1b[33m',
|
|
30
|
+
blue: '\x1b[34m',
|
|
31
|
+
dim: '\x1b[90m',
|
|
32
|
+
bright: '\x1b[1m'
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// Memory bank paths
|
|
36
|
+
const MEMORY_BANK_DIR = 'memory-bank';
|
|
37
|
+
const INTENTS_DIR = path.join(MEMORY_BANK_DIR, 'intents');
|
|
38
|
+
const BOLTS_DIR = path.join(MEMORY_BANK_DIR, 'bolts');
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Extract frontmatter from a markdown file
|
|
42
|
+
*/
|
|
43
|
+
function extractFrontmatter(content) {
|
|
44
|
+
const match = content.match(/^---\n([\s\S]+?)\n---/);
|
|
45
|
+
if (!match) return null;
|
|
46
|
+
try {
|
|
47
|
+
return yaml.load(match[1]);
|
|
48
|
+
} catch (error) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Update frontmatter in a markdown file
|
|
55
|
+
*/
|
|
56
|
+
function updateFrontmatter(content, newFrontmatter) {
|
|
57
|
+
const match = content.match(/^---\n([\s\S]+?)\n---/);
|
|
58
|
+
if (!match) return null;
|
|
59
|
+
|
|
60
|
+
const newYaml = yaml.dump(newFrontmatter, {
|
|
61
|
+
lineWidth: -1,
|
|
62
|
+
noRefs: true,
|
|
63
|
+
quotingType: '"',
|
|
64
|
+
forceQuotes: false,
|
|
65
|
+
sortKeys: false
|
|
66
|
+
}).trim();
|
|
67
|
+
|
|
68
|
+
return `---\n${newYaml}\n---${content.slice(match[0].length)}`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Regex patterns for naming conventions
|
|
73
|
+
*/
|
|
74
|
+
const patterns = {
|
|
75
|
+
intent: /^\d{3}-.+$/, // {NNN}-{name}
|
|
76
|
+
unit: /^\d{3}-.+$/, // {UUU}-{name}
|
|
77
|
+
story: /^\d{3}-.+$/, // {SSS}-{title-slug}
|
|
78
|
+
bolt: /^\d{3}-.+$/, // {BBB}-{unit-name}
|
|
79
|
+
timestamp: /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/ // ISO 8601 without ms
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
class ArtifactValidator {
|
|
83
|
+
constructor(memoryBankPath = 'memory-bank') {
|
|
84
|
+
this.memoryBankPath = memoryBankPath;
|
|
85
|
+
this.results = [];
|
|
86
|
+
this.fixCount = 0;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Add a validation result
|
|
91
|
+
*/
|
|
92
|
+
addResult(result) {
|
|
93
|
+
this.results.push(result);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Add a fix count
|
|
98
|
+
*/
|
|
99
|
+
addFix(count = 1) {
|
|
100
|
+
this.fixCount += count;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Test Suite 1: Naming Conventions
|
|
105
|
+
*/
|
|
106
|
+
async validateNamingConventions() {
|
|
107
|
+
console.log(`${colors.dim}[1/4] Validating naming conventions...${colors.reset}`);
|
|
108
|
+
let issues = 0;
|
|
109
|
+
|
|
110
|
+
// Check intent folders
|
|
111
|
+
if (await fs.pathExists(INTENTS_DIR)) {
|
|
112
|
+
const intentDirs = await fs.readdir(INTENTS_DIR);
|
|
113
|
+
for (const dir of intentDirs) {
|
|
114
|
+
if (!patterns.intent.test(dir)) {
|
|
115
|
+
this.addResult({
|
|
116
|
+
type: 'naming',
|
|
117
|
+
severity: 'error',
|
|
118
|
+
file: path.join(INTENTS_DIR, dir),
|
|
119
|
+
rule: 'intent.folder-pattern',
|
|
120
|
+
message: `Intent folder "${dir}" doesn't match {NNN}-{name} pattern`,
|
|
121
|
+
fixable: false
|
|
122
|
+
});
|
|
123
|
+
issues++;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Check bolt folders
|
|
129
|
+
if (await fs.pathExists(BOLTS_DIR)) {
|
|
130
|
+
const boltDirs = await fs.readdir(BOLTS_DIR);
|
|
131
|
+
for (const dir of boltDirs) {
|
|
132
|
+
if (!patterns.bolt.test(dir)) {
|
|
133
|
+
this.addResult({
|
|
134
|
+
type: 'naming',
|
|
135
|
+
severity: 'error',
|
|
136
|
+
file: path.join(BOLTS_DIR, dir),
|
|
137
|
+
rule: 'bolt.folder-pattern',
|
|
138
|
+
message: `Bolt folder "${dir}" doesn't match {BBB}-{unit-name} pattern`,
|
|
139
|
+
fixable: false
|
|
140
|
+
});
|
|
141
|
+
issues++;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Check unit folders and story files recursively
|
|
147
|
+
if (await fs.pathExists(INTENTS_DIR)) {
|
|
148
|
+
const intentDirs = await fs.readdir(INTENTS_DIR);
|
|
149
|
+
for (const intentDir of intentDirs) {
|
|
150
|
+
const unitsDir = path.join(INTENTS_DIR, intentDir, 'units');
|
|
151
|
+
if (await fs.pathExists(unitsDir)) {
|
|
152
|
+
const unitDirs = await fs.readdir(unitsDir);
|
|
153
|
+
for (const unitDir of unitDirs) {
|
|
154
|
+
// Check unit folder pattern
|
|
155
|
+
if (!patterns.unit.test(unitDir)) {
|
|
156
|
+
this.addResult({
|
|
157
|
+
type: 'naming',
|
|
158
|
+
severity: 'error',
|
|
159
|
+
file: path.join(unitsDir, unitDir),
|
|
160
|
+
rule: 'unit.folder-pattern',
|
|
161
|
+
message: `Unit folder "${unitDir}" doesn't match {UUU}-{unit-name} pattern`,
|
|
162
|
+
fixable: false
|
|
163
|
+
});
|
|
164
|
+
issues++;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Check story files
|
|
168
|
+
const storiesDir = path.join(unitsDir, unitDir, 'stories');
|
|
169
|
+
if (await fs.pathExists(storiesDir)) {
|
|
170
|
+
const storyFiles = await fs.readdir(storiesDir);
|
|
171
|
+
for (const storyFile of storyFiles) {
|
|
172
|
+
if (path.extname(storyFile) === '.md') {
|
|
173
|
+
const basename = path.basename(storyFile, '.md');
|
|
174
|
+
if (!patterns.story.test(basename)) {
|
|
175
|
+
this.addResult({
|
|
176
|
+
type: 'naming',
|
|
177
|
+
severity: 'error',
|
|
178
|
+
file: path.join(storiesDir, storyFile),
|
|
179
|
+
rule: 'story.file-pattern',
|
|
180
|
+
message: `Story file "${storyFile}" doesn't match {SSS}-{title-slug}.md pattern`,
|
|
181
|
+
fixable: false
|
|
182
|
+
});
|
|
183
|
+
issues++;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return issues;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Test Suite 2: ID-Filename Consistency
|
|
198
|
+
*/
|
|
199
|
+
async validateIdFilenameConsistency() {
|
|
200
|
+
console.log(`${colors.dim}[2/4] Validating ID-filename consistency...${colors.reset}`);
|
|
201
|
+
let issues = 0;
|
|
202
|
+
|
|
203
|
+
// Check story files: id should match filename
|
|
204
|
+
if (await fs.pathExists(INTENTS_DIR)) {
|
|
205
|
+
const intentDirs = await fs.readdir(INTENTS_DIR);
|
|
206
|
+
for (const intentDir of intentDirs) {
|
|
207
|
+
const unitsDir = path.join(INTENTS_DIR, intentDir, 'units');
|
|
208
|
+
if (await fs.pathExists(unitsDir)) {
|
|
209
|
+
const unitDirs = await fs.readdir(unitsDir);
|
|
210
|
+
for (const unitDir of unitDirs) {
|
|
211
|
+
const storiesDir = path.join(unitsDir, unitDir, 'stories');
|
|
212
|
+
if (await fs.pathExists(storiesDir)) {
|
|
213
|
+
const storyFiles = (await fs.readdir(storiesDir)).filter(f => path.extname(f) === '.md');
|
|
214
|
+
for (const storyFile of storyFiles) {
|
|
215
|
+
const storyPath = path.join(storiesDir, storyFile);
|
|
216
|
+
const content = await fs.readFile(storyPath, 'utf8');
|
|
217
|
+
const frontmatter = extractFrontmatter(content);
|
|
218
|
+
|
|
219
|
+
if (frontmatter && frontmatter.id) {
|
|
220
|
+
const expectedId = path.basename(storyFile, '.md');
|
|
221
|
+
if (expectedId !== frontmatter.id) {
|
|
222
|
+
this.addResult({
|
|
223
|
+
type: 'consistency',
|
|
224
|
+
severity: 'error',
|
|
225
|
+
file: storyPath,
|
|
226
|
+
rule: 'story.id-matches-filename',
|
|
227
|
+
message: `Story id "${frontmatter.id}" doesn't match filename "${expectedId}.md"`,
|
|
228
|
+
expected: expectedId,
|
|
229
|
+
actual: frontmatter.id,
|
|
230
|
+
fixable: true
|
|
231
|
+
});
|
|
232
|
+
issues++;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Check bolt files: id should match folder name
|
|
243
|
+
if (await fs.pathExists(BOLTS_DIR)) {
|
|
244
|
+
const boltDirs = await fs.readdir(BOLTS_DIR);
|
|
245
|
+
for (const boltDir of boltDirs) {
|
|
246
|
+
const boltPath = path.join(BOLTS_DIR, boltDir, 'bolt.md');
|
|
247
|
+
if (await fs.pathExists(boltPath)) {
|
|
248
|
+
const content = await fs.readFile(boltPath, 'utf8');
|
|
249
|
+
const frontmatter = extractFrontmatter(content);
|
|
250
|
+
|
|
251
|
+
if (frontmatter && frontmatter.id) {
|
|
252
|
+
const expectedId = boltDir;
|
|
253
|
+
if (expectedId !== frontmatter.id) {
|
|
254
|
+
this.addResult({
|
|
255
|
+
type: 'consistency',
|
|
256
|
+
severity: 'error',
|
|
257
|
+
file: boltPath,
|
|
258
|
+
rule: 'bolt.id-matches-folder',
|
|
259
|
+
message: `Bolt id "${frontmatter.id}" doesn't match folder "${boltDir}/"`,
|
|
260
|
+
expected: expectedId,
|
|
261
|
+
actual: frontmatter.id,
|
|
262
|
+
fixable: true
|
|
263
|
+
});
|
|
264
|
+
issues++;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return issues;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Test Suite 3: Cross-Reference Integrity
|
|
276
|
+
*/
|
|
277
|
+
async validateCrossReferences() {
|
|
278
|
+
console.log(`${colors.dim}[3/4] Validating cross-references...${colors.reset}`);
|
|
279
|
+
let issues = 0;
|
|
280
|
+
|
|
281
|
+
// Check bolt story references
|
|
282
|
+
if (await fs.pathExists(BOLTS_DIR)) {
|
|
283
|
+
const boltDirs = await fs.readdir(BOLTS_DIR);
|
|
284
|
+
for (const boltDir of boltDirs) {
|
|
285
|
+
const boltPath = path.join(BOLTS_DIR, boltDir, 'bolt.md');
|
|
286
|
+
if (await fs.pathExists(boltPath)) {
|
|
287
|
+
const content = await fs.readFile(boltPath, 'utf8');
|
|
288
|
+
const frontmatter = extractFrontmatter(content);
|
|
289
|
+
|
|
290
|
+
if (frontmatter) {
|
|
291
|
+
const intent = frontmatter.intent;
|
|
292
|
+
const unit = frontmatter.unit;
|
|
293
|
+
|
|
294
|
+
// Check each story reference exists
|
|
295
|
+
for (const storyId of frontmatter.stories || []) {
|
|
296
|
+
const storyPath = path.join(INTENTS_DIR, intent, 'units', unit, 'stories', `${storyId}.md`);
|
|
297
|
+
if (!await fs.pathExists(storyPath)) {
|
|
298
|
+
this.addResult({
|
|
299
|
+
type: 'reference',
|
|
300
|
+
severity: 'error',
|
|
301
|
+
file: boltPath,
|
|
302
|
+
rule: 'bolt.story-exists',
|
|
303
|
+
message: `Bolt references non-existent story "${storyId}"`,
|
|
304
|
+
reference: storyId,
|
|
305
|
+
expectedPath: storyPath,
|
|
306
|
+
fixable: false
|
|
307
|
+
});
|
|
308
|
+
issues++;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Check unit folder exists
|
|
313
|
+
if (unit) {
|
|
314
|
+
const unitPath = path.join(INTENTS_DIR, intent, 'units', unit);
|
|
315
|
+
if (!await fs.pathExists(unitPath)) {
|
|
316
|
+
this.addResult({
|
|
317
|
+
type: 'reference',
|
|
318
|
+
severity: 'error',
|
|
319
|
+
file: boltPath,
|
|
320
|
+
rule: 'bolt.unit-exists',
|
|
321
|
+
message: `Bolt references non-existent unit "${unit}"`,
|
|
322
|
+
reference: unit,
|
|
323
|
+
expectedPath: unitPath,
|
|
324
|
+
fixable: false
|
|
325
|
+
});
|
|
326
|
+
issues++;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Check intent folder exists
|
|
331
|
+
if (intent) {
|
|
332
|
+
const intentPath = path.join(INTENTS_DIR, intent);
|
|
333
|
+
if (!await fs.pathExists(intentPath)) {
|
|
334
|
+
this.addResult({
|
|
335
|
+
type: 'reference',
|
|
336
|
+
severity: 'error',
|
|
337
|
+
file: boltPath,
|
|
338
|
+
rule: 'bolt.intent-exists',
|
|
339
|
+
message: `Bolt references non-existent intent "${intent}"`,
|
|
340
|
+
reference: intent,
|
|
341
|
+
expectedPath: intentPath,
|
|
342
|
+
fixable: false
|
|
343
|
+
});
|
|
344
|
+
issues++;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return issues;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Test Suite 4: Timestamp Format
|
|
357
|
+
*/
|
|
358
|
+
async validateTimestamps() {
|
|
359
|
+
console.log(`${colors.dim}[4/4] Validating timestamp formats...${colors.reset}`);
|
|
360
|
+
let issues = 0;
|
|
361
|
+
|
|
362
|
+
const timestampFields = ['created', 'updated', 'started', 'completed', 'timestamp', 'last_updated'];
|
|
363
|
+
|
|
364
|
+
// Function to check all frontmatter fields recursively
|
|
365
|
+
const checkFields = (obj, filePath) => {
|
|
366
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
367
|
+
if (value === null || value === undefined) continue;
|
|
368
|
+
|
|
369
|
+
if (typeof value === 'string' && timestampFields.includes(key)) {
|
|
370
|
+
// Check if it looks like a timestamp (contains T and ends with Z)
|
|
371
|
+
if (value.includes('T') && value.endsWith('Z')) {
|
|
372
|
+
// Extract just the time portion for format check
|
|
373
|
+
if (!patterns.timestamp.test(value)) {
|
|
374
|
+
// Check for milliseconds
|
|
375
|
+
if (/\.\d+Z$/.test(value)) {
|
|
376
|
+
this.addResult({
|
|
377
|
+
type: 'format',
|
|
378
|
+
severity: 'warning',
|
|
379
|
+
file: filePath,
|
|
380
|
+
rule: 'timestamp.no-milliseconds',
|
|
381
|
+
message: `Timestamp "${value}" has milliseconds (should be YYYY-MM-DDTHH:MM:SSZ)`,
|
|
382
|
+
field: key,
|
|
383
|
+
value: value,
|
|
384
|
+
fixable: true
|
|
385
|
+
});
|
|
386
|
+
issues++;
|
|
387
|
+
} else {
|
|
388
|
+
this.addResult({
|
|
389
|
+
type: 'format',
|
|
390
|
+
severity: 'warning',
|
|
391
|
+
file: filePath,
|
|
392
|
+
rule: 'timestamp.iso8601',
|
|
393
|
+
message: `Timestamp "${value}" doesn't match ISO 8601 format`,
|
|
394
|
+
field: key,
|
|
395
|
+
value: value,
|
|
396
|
+
fixable: false
|
|
397
|
+
});
|
|
398
|
+
issues++;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
} else if (typeof value === 'object' && !Array.isArray(value)) {
|
|
403
|
+
checkFields(value, filePath);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
// Check all markdown files in memory-bank
|
|
409
|
+
const checkDir = async (dir) => {
|
|
410
|
+
if (!await fs.pathExists(dir)) return;
|
|
411
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
412
|
+
for (const entry of entries) {
|
|
413
|
+
const fullPath = path.join(dir, entry.name);
|
|
414
|
+
if (entry.isDirectory()) {
|
|
415
|
+
await checkDir(fullPath);
|
|
416
|
+
} else if (entry.isFile() && path.extname(entry.name) === '.md') {
|
|
417
|
+
const content = await fs.readFile(fullPath, 'utf8');
|
|
418
|
+
const frontmatter = extractFrontmatter(content);
|
|
419
|
+
if (frontmatter) {
|
|
420
|
+
checkFields(frontmatter, fullPath);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
await checkDir(MEMORY_BANK_DIR);
|
|
427
|
+
return issues;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Run all validation suites
|
|
432
|
+
*/
|
|
433
|
+
async validateAll() {
|
|
434
|
+
console.log(`${colors.bright}${colors.blue}════════════════════════════════════════${colors.reset}`);
|
|
435
|
+
console.log(`${colors.bright}${colors.blue}Artifact Validator${colors.reset}`);
|
|
436
|
+
console.log(`${colors.bright}${colors.blue}════════════════════════════════════════${colors.reset}\n`);
|
|
437
|
+
|
|
438
|
+
const totalIssues =
|
|
439
|
+
await this.validateNamingConventions() +
|
|
440
|
+
await this.validateIdFilenameConsistency() +
|
|
441
|
+
await this.validateCrossReferences() +
|
|
442
|
+
await this.validateTimestamps();
|
|
443
|
+
|
|
444
|
+
return { totalIssues, results: this.results };
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Fix safe issues (ID-filename consistency, timestamp milliseconds)
|
|
449
|
+
*/
|
|
450
|
+
async fixSafeIssues() {
|
|
451
|
+
let fixed = 0;
|
|
452
|
+
|
|
453
|
+
console.log(`${colors.bright}Fixing safe issues...${colors.reset}\n`);
|
|
454
|
+
|
|
455
|
+
for (const result of this.results) {
|
|
456
|
+
if (!result.fixable) continue;
|
|
457
|
+
|
|
458
|
+
if (result.rule === 'story.id-matches-filename') {
|
|
459
|
+
const content = await fs.readFile(result.file, 'utf8');
|
|
460
|
+
const frontmatter = extractFrontmatter(content);
|
|
461
|
+
if (frontmatter) {
|
|
462
|
+
frontmatter.id = result.expected;
|
|
463
|
+
const newContent = updateFrontmatter(content, frontmatter);
|
|
464
|
+
if (newContent) {
|
|
465
|
+
await fs.writeFile(result.file, newContent, 'utf8');
|
|
466
|
+
console.log(` ${colors.green}✓${colors.reset} Fixed story ID: ${result.file}`);
|
|
467
|
+
fixed++;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
if (result.rule === 'bolt.id-matches-folder') {
|
|
473
|
+
const content = await fs.readFile(result.file, 'utf8');
|
|
474
|
+
const frontmatter = extractFrontmatter(content);
|
|
475
|
+
if (frontmatter) {
|
|
476
|
+
frontmatter.id = result.expected;
|
|
477
|
+
const newContent = updateFrontmatter(content, frontmatter);
|
|
478
|
+
if (newContent) {
|
|
479
|
+
await fs.writeFile(result.file, newContent, 'utf8');
|
|
480
|
+
console.log(` ${colors.green}✓${colors.reset} Fixed bolt ID: ${result.file}`);
|
|
481
|
+
fixed++;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
if (result.rule === 'timestamp.no-milliseconds') {
|
|
487
|
+
const content = await fs.readFile(result.file, 'utf8');
|
|
488
|
+
const frontmatter = extractFrontmatter(content);
|
|
489
|
+
if (frontmatter && frontmatter[result.field]) {
|
|
490
|
+
frontmatter[result.field] = frontmatter[result.field].replace(/\.\d+Z$/, 'Z');
|
|
491
|
+
const newContent = updateFrontmatter(content, frontmatter);
|
|
492
|
+
if (newContent) {
|
|
493
|
+
await fs.writeFile(result.file, newContent, 'utf8');
|
|
494
|
+
console.log(` ${colors.green}✓${colors.reset} Fixed timestamp: ${result.file}`);
|
|
495
|
+
fixed++;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
return fixed;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Output results to console
|
|
506
|
+
*/
|
|
507
|
+
toConsole() {
|
|
508
|
+
console.log();
|
|
509
|
+
|
|
510
|
+
if (this.results.length === 0) {
|
|
511
|
+
console.log(`${colors.green}✓${colors.reset} All validations passed!\n`);
|
|
512
|
+
} else {
|
|
513
|
+
const errors = this.results.filter(r => r.severity === 'error').length;
|
|
514
|
+
const warnings = this.results.filter(r => r.severity === 'warning').length;
|
|
515
|
+
|
|
516
|
+
console.log(`${colors.bright}${colors.yellow}⚠ ${this.results.length} issues found${colors.reset}`);
|
|
517
|
+
console.log(`${colors.dim}(${errors} errors, ${warnings} warnings)${colors.reset}\n`);
|
|
518
|
+
|
|
519
|
+
// Group by type
|
|
520
|
+
const byType = {};
|
|
521
|
+
for (const result of this.results) {
|
|
522
|
+
if (!byType[result.type]) byType[result.type] = [];
|
|
523
|
+
byType[result.type].push(result);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
for (const [type, issues] of Object.entries(byType)) {
|
|
527
|
+
console.log(`${colors.bright}${type.toUpperCase()} Issues:${colors.reset}\n`);
|
|
528
|
+
for (const issue of issues) {
|
|
529
|
+
const icon = issue.severity === 'error' ? colors.red + '✗' : colors.yellow + '⚠';
|
|
530
|
+
const shortPath = issue.file.replace(/^memory-bank\//, '');
|
|
531
|
+
console.log(` ${icon}${colors.reset} ${shortPath}`);
|
|
532
|
+
console.log(` ${colors.dim}${issue.message}${colors.reset}`);
|
|
533
|
+
}
|
|
534
|
+
console.log();
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
console.log(`${colors.bright}${colors.blue}════════════════════════════════════════${colors.reset}`);
|
|
539
|
+
console.log(`${colors.bright}${colors.blue}Summary${colors.reset}`);
|
|
540
|
+
console.log(`${colors.bright}${colors.blue}════════════════════════════════════════${colors.reset}\n`);
|
|
541
|
+
console.log(`Total issues: ${this.results.length}`);
|
|
542
|
+
if (this.fixCount > 0) {
|
|
543
|
+
console.log(`Fixed: ${this.fixCount}`);
|
|
544
|
+
}
|
|
545
|
+
console.log();
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* Output results as JSON
|
|
550
|
+
*/
|
|
551
|
+
toJSON() {
|
|
552
|
+
return JSON.stringify({
|
|
553
|
+
summary: {
|
|
554
|
+
total: this.results.length,
|
|
555
|
+
errors: this.results.filter(r => r.severity === 'error').length,
|
|
556
|
+
warnings: this.results.filter(r => r.severity === 'warning').length,
|
|
557
|
+
fixed: this.fixCount
|
|
558
|
+
},
|
|
559
|
+
results: this.results
|
|
560
|
+
}, null, 2);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// CLI entry point
|
|
565
|
+
async function main() {
|
|
566
|
+
const args = process.argv.slice(2);
|
|
567
|
+
const jsonOutput = args.includes('--json');
|
|
568
|
+
const shouldFix = args.includes('--fix');
|
|
569
|
+
|
|
570
|
+
const validator = new ArtifactValidator();
|
|
571
|
+
await validator.validateAll();
|
|
572
|
+
|
|
573
|
+
if (shouldFix) {
|
|
574
|
+
await validator.fixSafeIssues();
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
if (jsonOutput) {
|
|
578
|
+
console.log(validator.toJSON());
|
|
579
|
+
} else {
|
|
580
|
+
validator.toConsole();
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
const hasErrors = validator.results.some(r => r.severity === 'error');
|
|
584
|
+
process.exit(hasErrors && !shouldFix ? 1 : 0);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
if (require.main === module) {
|
|
588
|
+
main().catch(error => {
|
|
589
|
+
console.error(`\n${colors.red}Error:${colors.reset}`, error.message);
|
|
590
|
+
process.exit(1);
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
module.exports = ArtifactValidator;
|