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.
Files changed (189) hide show
  1. package/README.md +261 -94
  2. package/bin/cli.js +21 -0
  3. package/flows/aidlc/README.md +372 -0
  4. package/flows/aidlc/agents/construction-agent.md +79 -0
  5. package/flows/aidlc/agents/inception-agent.md +97 -0
  6. package/flows/aidlc/agents/master-agent.md +61 -0
  7. package/flows/aidlc/agents/operations-agent.md +89 -0
  8. package/flows/aidlc/commands/construction-agent.md +63 -0
  9. package/flows/aidlc/commands/inception-agent.md +55 -0
  10. package/flows/aidlc/commands/master-agent.md +47 -0
  11. package/flows/aidlc/commands/operations-agent.md +77 -0
  12. package/flows/aidlc/context-config.yaml +67 -0
  13. package/flows/aidlc/memory-bank.yaml +104 -0
  14. package/flows/aidlc/quick-start.md +322 -0
  15. package/flows/aidlc/skills/construction/bolt-list.md +163 -0
  16. package/flows/aidlc/skills/construction/bolt-replan.md +345 -0
  17. package/flows/aidlc/skills/construction/bolt-start.md +442 -0
  18. package/flows/aidlc/skills/construction/bolt-status.md +185 -0
  19. package/flows/aidlc/skills/construction/navigator.md +196 -0
  20. package/flows/aidlc/skills/inception/bolt-plan.md +372 -0
  21. package/flows/aidlc/skills/inception/context.md +171 -0
  22. package/flows/aidlc/skills/inception/intent-create.md +211 -0
  23. package/flows/aidlc/skills/inception/intent-list.md +124 -0
  24. package/flows/aidlc/skills/inception/navigator.md +207 -0
  25. package/flows/aidlc/skills/inception/requirements.md +227 -0
  26. package/flows/aidlc/skills/inception/review.md +248 -0
  27. package/flows/aidlc/skills/inception/story-create.md +304 -0
  28. package/flows/aidlc/skills/inception/units.md +278 -0
  29. package/flows/aidlc/skills/master/analyze-context.md +239 -0
  30. package/flows/aidlc/skills/master/answer-question.md +141 -0
  31. package/flows/aidlc/skills/master/explain-flow.md +158 -0
  32. package/flows/aidlc/skills/master/project-init.md +281 -0
  33. package/flows/aidlc/skills/master/route-request.md +126 -0
  34. package/flows/aidlc/skills/operations/build.md +237 -0
  35. package/flows/aidlc/skills/operations/deploy.md +259 -0
  36. package/flows/aidlc/skills/operations/monitor.md +265 -0
  37. package/flows/aidlc/skills/operations/navigator.md +209 -0
  38. package/flows/aidlc/skills/operations/verify.md +224 -0
  39. package/{dist/iris_bundle/frameworks/iris-core → flows/aidlc}/templates/construction/bolt-types/ddd-construction-bolt.md +3 -3
  40. package/{dist → flows/aidlc}/templates/construction/bolt-types/spike-bolt.md +2 -2
  41. package/flows/aidlc/templates/construction/construction-log-template.md +129 -0
  42. package/flows/aidlc/templates/construction/standards/coding-standards.md +29 -0
  43. package/flows/aidlc/templates/construction/standards/system-architecture.md +22 -0
  44. package/flows/aidlc/templates/construction/standards/tech-stack.md +19 -0
  45. package/flows/aidlc/templates/inception/inception-log-template.md +134 -0
  46. package/flows/aidlc/templates/inception/project/README.md +55 -0
  47. package/flows/aidlc/templates/standards/catalog.yaml +345 -0
  48. package/flows/aidlc/templates/standards/coding-standards.guide.md +553 -0
  49. package/flows/aidlc/templates/standards/data-stack.guide.md +162 -0
  50. package/flows/aidlc/templates/standards/tech-stack.guide.md +280 -0
  51. package/lib/InstallerFactory.js +36 -0
  52. package/lib/analytics/env-detector.js +92 -0
  53. package/lib/analytics/index.js +22 -0
  54. package/lib/analytics/machine-id.js +33 -0
  55. package/lib/analytics/tracker.js +232 -0
  56. package/lib/cli-utils.js +342 -0
  57. package/lib/constants.js +32 -0
  58. package/lib/installer.js +402 -0
  59. package/lib/installers/AntigravityInstaller.js +22 -0
  60. package/lib/installers/ClaudeInstaller.js +85 -0
  61. package/lib/installers/ClineInstaller.js +21 -0
  62. package/lib/installers/CodexInstaller.js +21 -0
  63. package/lib/installers/CopilotInstaller.js +113 -0
  64. package/lib/installers/CursorInstaller.js +63 -0
  65. package/lib/installers/GeminiInstaller.js +75 -0
  66. package/lib/installers/KiroInstaller.js +22 -0
  67. package/lib/installers/OpenCodeInstaller.js +22 -0
  68. package/lib/installers/RooInstaller.js +22 -0
  69. package/lib/installers/ToolInstaller.js +73 -0
  70. package/lib/installers/WindsurfInstaller.js +22 -0
  71. package/lib/markdown-validator.ts +175 -0
  72. package/lib/yaml-validator.ts +99 -0
  73. package/package.json +105 -32
  74. package/scripts/artifact-validator.js +594 -0
  75. package/scripts/bolt-complete.js +606 -0
  76. package/scripts/status-integrity.js +598 -0
  77. package/dist/bridge/agent-runner.js +0 -190
  78. package/dist/bridge/connector-factory.js +0 -31
  79. package/dist/bridge/connectors/antigravity-connector.js +0 -18
  80. package/dist/bridge/connectors/cursor-connector.js +0 -31
  81. package/dist/bridge/connectors/in-process-connector.js +0 -29
  82. package/dist/bridge/connectors/vscode-connector.js +0 -31
  83. package/dist/bridge/connectors/windsurf-connector.js +0 -23
  84. package/dist/bridge/filesystem-connector.js +0 -110
  85. package/dist/bridge/helper.js +0 -203
  86. package/dist/bridge/types.js +0 -10
  87. package/dist/cli.js +0 -40
  88. package/dist/commands/ask.js +0 -259
  89. package/dist/commands/bridge.js +0 -88
  90. package/dist/commands/create.js +0 -25
  91. package/dist/commands/develop.js +0 -141
  92. package/dist/commands/doctor.js +0 -102
  93. package/dist/commands/flow.js +0 -301
  94. package/dist/commands/framework.js +0 -273
  95. package/dist/commands/generate.js +0 -59
  96. package/dist/commands/install.js +0 -100
  97. package/dist/commands/pack.js +0 -33
  98. package/dist/commands/phase.js +0 -38
  99. package/dist/commands/run.js +0 -199
  100. package/dist/commands/status.js +0 -114
  101. package/dist/commands/uninstall.js +0 -14
  102. package/dist/commands/use.js +0 -20
  103. package/dist/commands/validate.js +0 -102
  104. package/dist/framework/framework-loader.js +0 -97
  105. package/dist/framework/framework-paths.js +0 -48
  106. package/dist/framework/framework-types.js +0 -15
  107. package/dist/iris/artifact-checker.js +0 -78
  108. package/dist/iris/artifacts/config.js +0 -68
  109. package/dist/iris/artifacts/generator.js +0 -88
  110. package/dist/iris/artifacts/types.js +0 -1
  111. package/dist/iris/bundle.js +0 -44
  112. package/dist/iris/doctrine/collector.js +0 -124
  113. package/dist/iris/fixer.js +0 -149
  114. package/dist/iris/flows/manifest.js +0 -124
  115. package/dist/iris/framework-context.js +0 -49
  116. package/dist/iris/framework-manager.js +0 -215
  117. package/dist/iris/fs/atomic.js +0 -22
  118. package/dist/iris/guard.js +0 -38
  119. package/dist/iris/importers/index.js +0 -9
  120. package/dist/iris/importers/types.js +0 -8
  121. package/dist/iris/importers/writer.js +0 -139
  122. package/dist/iris/include.js +0 -49
  123. package/dist/iris/installer.js +0 -334
  124. package/dist/iris/interactive/env.js +0 -21
  125. package/dist/iris/interactive/intent-interview.js +0 -345
  126. package/dist/iris/interactive/intent-schema.js +0 -28
  127. package/dist/iris/interactive/interview-io.js +0 -22
  128. package/dist/iris/interview/config.js +0 -71
  129. package/dist/iris/interview/types.js +0 -16
  130. package/dist/iris/interview/utils.js +0 -38
  131. package/dist/iris/manifest.js +0 -54
  132. package/dist/iris/packer.js +0 -325
  133. package/dist/iris/parsers/unit-parser.js +0 -43
  134. package/dist/iris/paths.js +0 -18
  135. package/dist/iris/policy.js +0 -133
  136. package/dist/iris/proc.js +0 -56
  137. package/dist/iris/report.js +0 -53
  138. package/dist/iris/resolver.js +0 -66
  139. package/dist/iris/router.js +0 -114
  140. package/dist/iris/routes.js +0 -189
  141. package/dist/iris/run-state.js +0 -146
  142. package/dist/iris/state.js +0 -113
  143. package/dist/iris/templates.js +0 -70
  144. package/dist/iris/tmp.js +0 -24
  145. package/dist/iris/uninstaller.js +0 -181
  146. package/dist/iris/utils/interpolate.js +0 -42
  147. package/dist/iris/validator.js +0 -391
  148. package/dist/iris/workflow/config.js +0 -51
  149. package/dist/iris/workflow/engine.js +0 -129
  150. package/dist/iris/workflow/steps.js +0 -448
  151. package/dist/iris/workflow/types.js +0 -1
  152. package/dist/iris_bundle/frameworks/iris-core/framework.yaml +0 -9
  153. package/dist/iris_bundle/frameworks/iris-core/memory/memory-bank.yaml +0 -1
  154. package/dist/iris_bundle/frameworks/iris-core/policy.yaml +0 -7
  155. package/dist/iris_bundle/frameworks/iris-core/templates/config/memory-bank.yaml +0 -1
  156. package/dist/iris_bundle/frameworks/iris-core/templates/construction/bolt-types/spike-bolt.md +0 -240
  157. package/dist/lib.js +0 -96
  158. package/dist/templates/construction/bolt-template.md +0 -226
  159. package/dist/templates/construction/bolt-types/ddd-construction-bolt/adr-template.md +0 -49
  160. package/dist/templates/construction/bolt-types/ddd-construction-bolt/ddd-01-domain-model-template.md +0 -55
  161. package/dist/templates/construction/bolt-types/ddd-construction-bolt/ddd-02-technical-design-template.md +0 -67
  162. package/dist/templates/construction/bolt-types/ddd-construction-bolt/ddd-03-test-report-template.md +0 -62
  163. package/dist/templates/construction/bolt-types/ddd-construction-bolt.md +0 -528
  164. package/dist/templates/construction/bolt-types/simple-construction-bolt.md +0 -347
  165. package/dist/templates/inception/requirements-template.md +0 -144
  166. package/dist/templates/inception/stories-template.md +0 -38
  167. package/dist/templates/inception/story-template.md +0 -147
  168. package/dist/templates/inception/system-context-template.md +0 -29
  169. package/dist/templates/inception/unit-brief-template.md +0 -177
  170. package/dist/templates/inception/units-template.md +0 -52
  171. package/dist/utils/exit-codes.js +0 -7
  172. package/dist/utils/logo.js +0 -17
  173. package/dist/workflows/bolt-execution.js +0 -238
  174. package/dist/workflows/bolt-plan.js +0 -221
  175. package/dist/workflows/intent-inception.js +0 -285
  176. package/dist/workflows/memory-bank-generator.js +0 -180
  177. package/dist/workflows/reporting.js +0 -74
  178. /package/{dist/iris_bundle/frameworks/iris-core → flows/aidlc}/templates/construction/bolt-template.md +0 -0
  179. /package/{dist/iris_bundle/frameworks/iris-core → flows/aidlc}/templates/construction/bolt-types/ddd-construction-bolt/adr-template.md +0 -0
  180. /package/{dist/iris_bundle/frameworks/iris-core → flows/aidlc}/templates/construction/bolt-types/ddd-construction-bolt/ddd-01-domain-model-template.md +0 -0
  181. /package/{dist/iris_bundle/frameworks/iris-core → flows/aidlc}/templates/construction/bolt-types/ddd-construction-bolt/ddd-02-technical-design-template.md +0 -0
  182. /package/{dist/iris_bundle/frameworks/iris-core → flows/aidlc}/templates/construction/bolt-types/ddd-construction-bolt/ddd-03-test-report-template.md +0 -0
  183. /package/{dist/iris_bundle/frameworks/iris-core → flows/aidlc}/templates/construction/bolt-types/simple-construction-bolt.md +0 -0
  184. /package/{dist/iris_bundle/frameworks/iris-core → flows/aidlc}/templates/inception/requirements-template.md +0 -0
  185. /package/{dist/iris_bundle/frameworks/iris-core → flows/aidlc}/templates/inception/stories-template.md +0 -0
  186. /package/{dist/iris_bundle/frameworks/iris-core → flows/aidlc}/templates/inception/story-template.md +0 -0
  187. /package/{dist/iris_bundle/frameworks/iris-core → flows/aidlc}/templates/inception/system-context-template.md +0 -0
  188. /package/{dist/iris_bundle/frameworks/iris-core → flows/aidlc}/templates/inception/unit-brief-template.md +0 -0
  189. /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;