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
package/dist/iris/tmp.js DELETED
@@ -1,24 +0,0 @@
1
- import * as fs from 'fs';
2
- import * as os from 'os';
3
- import * as path from 'path';
4
- export function makeTempDir(prefix) {
5
- // Ensure the prefix is safe and doesn't contain path separators
6
- const safePrefix = prefix.replace(/[^a-zA-Z0-9_-]/g, '_');
7
- return fs.mkdtempSync(path.join(os.tmpdir(), `iris-${safePrefix}-`));
8
- }
9
- export function removeTempDir(dir) {
10
- try {
11
- if (fs.existsSync(dir)) {
12
- // Use rmSync with force and recursive options for robust deletion
13
- // This is available in Node.js 14.14.0+
14
- fs.rmSync(dir, { recursive: true, force: true });
15
- }
16
- }
17
- catch (err) {
18
- // Log but don't throw, cleanup failure shouldn't crash the main process
19
- // unless strictly required. For CLI tools, silence is usually gold for cleanup.
20
- // However, if verbose logging was available here we'd use it.
21
- // For now, we swallow the error to prevent crashing.
22
- // console.warn(`Failed to remove temp dir ${dir}:`, err);
23
- }
24
- }
@@ -1,181 +0,0 @@
1
- import fs from "fs";
2
- import path from "path";
3
- import inquirer from "inquirer";
4
- import kleur from "kleur";
5
- import { removeDir, repoRoot } from "../lib.js";
6
- import { loadManifest } from "./manifest.js";
7
- export async function uninstallIris(options) {
8
- const root = repoRoot();
9
- const manifest = loadManifest();
10
- if (!manifest) {
11
- console.log("No IRIS manifest found; nothing to uninstall.");
12
- return;
13
- }
14
- // Step B: Remove installed tool wrapper files
15
- const manifestTools = manifest.paths_installed || [];
16
- let removedCount = 0;
17
- let skippedCount = 0;
18
- for (const entry of manifestTools) {
19
- // Only remove stuff outside .iris (we delete .iris wholesale later)
20
- if (entry.path.startsWith(".iris/"))
21
- continue;
22
- // Only remove if status was installed or overwritten
23
- if (entry.status !== "installed" && entry.status !== "overwritten")
24
- continue;
25
- const targetPath = path.join(root, entry.path);
26
- if (!fs.existsSync(targetPath))
27
- continue;
28
- // Check if modified by user?
29
- // Method 1: Compare with mirror in .iris/tools/
30
- // entry.path is like .claude/commands/foo.md
31
- // We know which tool it belongs to? Not directly from installedPath.
32
- // But we have tools_selected.
33
- // Let's rely on finding it in one of the mirrors?
34
- // Or cleaner: we know the 'source' from manifest? I didn't save 'source' in installedPath in installer.ts (oops).
35
- // I saved: path, type, status.
36
- // Recover source bundle path from mirror?
37
- // We mirrored relevant tools to .iris/tools/<tool>.
38
- // Check if file exists in ANY mirror that matches content?
39
- // Or simpler: Check against ALL mirrors.
40
- let matchFound = false;
41
- for (const tool of manifest.tools_selected) {
42
- const mirrorFile = path.join(root, ".iris/tools", tool, entry.path); // Mirror structure mimics repo root?
43
- // Wrapper in installer.ts: copyDirRec(toolBundlePath, mirrorPath) -> mirrorPath = .iris/tools/<tool>
44
- // And installToolWrapper copied from toolBundlePath to root.
45
- // So yes, relative path inside .iris/tools/<tool> should match relative path from root?
46
- // Wait. toolBundlePath might be .../tools/claude. Content is .claude/...
47
- // So in mirror: .iris/tools/claude/.claude/...
48
- // relative path of entry: .claude/...
49
- // So yes: join(.iris/tools/<tool>, entry.path) should point to the backup.
50
- if (fs.existsSync(mirrorFile)) {
51
- if (areFilesEqual(targetPath, mirrorFile)) {
52
- matchFound = true;
53
- break;
54
- }
55
- }
56
- }
57
- if (matchFound) {
58
- fs.unlinkSync(targetPath);
59
- // Cleanup parent dirs if empty?
60
- tryRemoveEmptyParents(targetPath, root);
61
- removedCount++;
62
- console.log(kleur.gray(`Removed ${entry.path}`));
63
- }
64
- else {
65
- console.log(kleur.yellow(`Modified by user; skipping: ${entry.path}`));
66
- skippedCount++;
67
- }
68
- }
69
- // Step C: Remove .iris/tools mirrors (Implicitly done by removing .iris later, but specs say remove mirrors)
70
- // Actually if we remove .iris entire folder, we don't need to do C.
71
- // Step D: Remove doctrine prompt
72
- let removeDoctrine = false;
73
- const isNonInteractive = options.force || options.yes || !!process.env.CI;
74
- if (options.force) {
75
- removeDoctrine = true;
76
- }
77
- else if (isNonInteractive) {
78
- // Safe default: Preserve doctrine in non-interactive/CI unless --force is used
79
- removeDoctrine = false;
80
- console.log(kleur.gray("Non-interactive mode (CI/--yes): Preserving .iris/ directory by default."));
81
- }
82
- else {
83
- const { confirm } = await inquirer.prompt([
84
- {
85
- type: "confirm",
86
- name: "confirm",
87
- message: "Remove IRIS doctrine (.iris/aidlc) and policy files?",
88
- default: true
89
- }
90
- ]);
91
- removeDoctrine = confirm;
92
- }
93
- if (removeDoctrine) {
94
- removeDir(path.join(root, ".iris"));
95
- console.log(kleur.green("Removed .iris/ directory."));
96
- }
97
- else {
98
- console.log(kleur.gray("Kept .iris/ directory."));
99
- }
100
- // Step E: Memory Bank
101
- if (!options.keepMemory && !options.force) {
102
- let confirmMemory = true; // Default Keep
103
- if (isNonInteractive) {
104
- // Safe default: Preserve memory bank
105
- confirmMemory = true;
106
- console.log(kleur.gray("Non-interactive mode: Preserving memory-bank/ by default."));
107
- }
108
- else {
109
- const { confirm } = await inquirer.prompt([
110
- {
111
- type: "confirm",
112
- name: "confirm",
113
- message: "Keep memory-bank/ folder? (default: Yes, keep)",
114
- default: true
115
- }
116
- ]);
117
- confirmMemory = confirm;
118
- }
119
- if (!confirmMemory) { // User said "No" to keeping -> Delete
120
- // Check if empty or created by us
121
- // Created dirs = manifest.created_dirs
122
- // Safety check: contains anything we didn't create?
123
- // Actually difficult to track every file in memory bank.
124
- // Safe approach: Only delete if empty?
125
- // Or prompt: "memory-bank contains files. Type DELETE to confirm."
126
- const mbPath = path.join(root, "memory-bank");
127
- if (fs.existsSync(mbPath)) {
128
- const isEmpty = fs.readdirSync(mbPath).length === 0;
129
- if (!isEmpty) {
130
- const { typeDelete } = await inquirer.prompt([
131
- {
132
- type: "input",
133
- name: "typeDelete",
134
- message: "memory-bank contains files. Type DELETE to confirm removal:",
135
- }
136
- ]);
137
- if (typeDelete === "DELETE") {
138
- removeDir(mbPath);
139
- console.log(kleur.green("Removed memory-bank/."));
140
- }
141
- else {
142
- console.log(kleur.yellow("Skipped memory-bank removal (safety)."));
143
- }
144
- }
145
- else {
146
- removeDir(mbPath);
147
- console.log(kleur.green("Removed empty memory-bank/."));
148
- }
149
- }
150
- }
151
- }
152
- // Step F: Summary
153
- console.log("");
154
- console.log(kleur.bold("Uninstall Summary:"));
155
- console.log(`- Removed files: ${removedCount}`);
156
- console.log(`- Skipped files (modified): ${skippedCount}`);
157
- console.log("");
158
- }
159
- function areFilesEqual(a, b) {
160
- try {
161
- const bufA = fs.readFileSync(a);
162
- const bufB = fs.readFileSync(b);
163
- return bufA.equals(bufB);
164
- }
165
- catch {
166
- return false;
167
- }
168
- }
169
- function tryRemoveEmptyParents(filePath, root) {
170
- let dir = path.dirname(filePath);
171
- while (dir && path.relative(root, dir) !== "" && !path.isAbsolute(path.relative(root, dir)) && fs.existsSync(dir)) {
172
- const files = fs.readdirSync(dir);
173
- if (files.length === 0) {
174
- fs.rmdirSync(dir);
175
- dir = path.dirname(dir);
176
- }
177
- else {
178
- break;
179
- }
180
- }
181
- }
@@ -1,42 +0,0 @@
1
- /**
2
- * Interpolates tokens in a string.
3
- * Currently supports: {intentId}
4
- * Throws on unknown tokens.
5
- */
6
- export function interpolateTokens(str, tokens) {
7
- return str.replace(/\{(\w+)\}/g, (match, key) => {
8
- if (key === 'intentId') {
9
- if (!tokens.intentId) {
10
- throw new Error(`Token {intentId} requires intentId value but none provided`);
11
- }
12
- return tokens.intentId;
13
- }
14
- throw new Error(`Unknown token {${key}} in path: ${str}`);
15
- });
16
- }
17
- /**
18
- * Generates a unique intentId from draft fields.
19
- * Format: YYYYMMDD-HHMMSS-<slug>
20
- * Slug priority: goal > problem > valueProp > fallback
21
- */
22
- export function generateIntentId(draft) {
23
- const now = new Date();
24
- const date = [
25
- now.getFullYear(),
26
- String(now.getMonth() + 1).padStart(2, '0'),
27
- String(now.getDate()).padStart(2, '0')
28
- ].join('');
29
- const time = [
30
- String(now.getHours()).padStart(2, '0'),
31
- String(now.getMinutes()).padStart(2, '0'),
32
- String(now.getSeconds()).padStart(2, '0')
33
- ].join('');
34
- const source = draft?.goal || draft?.problem || draft?.valueProp || 'intent';
35
- const slug = source
36
- .toLowerCase()
37
- .replace(/[^a-z0-9]+/g, '-')
38
- .replace(/-+/g, '-')
39
- .replace(/^-|-$/g, '')
40
- .slice(0, 40);
41
- return `${date}-${time}-${slug || 'intent'}`;
42
- }
@@ -1,391 +0,0 @@
1
- import fs from "fs";
2
- import path from "path";
3
- import matter from "gray-matter";
4
- import { loadEffectivePolicy } from "./policy.js";
5
- import { loadState, saveState } from "./state.js";
6
- import { checkArtifact } from "./artifact-checker.js";
7
- import glob from "fast-glob";
8
- import { interpolateTokens } from "./utils/interpolate.js";
9
- export async function validate(options) {
10
- const shouldWrite = options.writeBack !== false;
11
- // Step A: Load & validate config
12
- // May throw PolicyLoadError or PolicyOverlayMissingError - caller must handle
13
- const state = loadState();
14
- const policy = loadEffectivePolicy(options.frameworkResolution, undefined, state.active.flow);
15
- const errors = [];
16
- // Step B: Resolve target phase
17
- let targetPhase = state.phase.current;
18
- if (options.phase) {
19
- targetPhase = options.phase;
20
- }
21
- // Check if phase exists in policy
22
- if (!policy.phases[targetPhase]) {
23
- errors.push({
24
- code: "UNKNOWN_PHASE",
25
- message: `Target phase '${targetPhase}' does not exist in policy.`,
26
- severity: "error"
27
- });
28
- return finalizeResult(errors, state, false, shouldWrite);
29
- }
30
- // Step C: Required artifacts (FAIL FAST)
31
- // Gather requirements for the phase
32
- const requirements = policy.phases[targetPhase].requires || [];
33
- for (const req of requirements) {
34
- let reqPath = req.path;
35
- try {
36
- // Interpolate path with intentId if available
37
- reqPath = interpolateTokens(req.path, { intentId: state.active.intent_id || undefined });
38
- }
39
- catch (e) {
40
- // If interpolation fails (e.g. missing intentId), we treat it as an error
41
- errors.push({
42
- code: "CONFIG_ERROR",
43
- message: `Policy requirement path interpolation failed: ${e.message}`,
44
- severity: "error"
45
- });
46
- continue;
47
- }
48
- if (glob.isDynamicPattern(reqPath)) {
49
- const matches = glob.sync(reqPath, { cwd: process.cwd(), onlyFiles: req.type === 'file', onlyDirectories: req.type === 'directory' });
50
- if (matches.length === 0) {
51
- errors.push({
52
- code: "MISSING_ARTIFACT",
53
- path: reqPath,
54
- message: `No artifacts found matching required pattern: ${reqPath}`,
55
- remediation: req.template
56
- ? `Create artifact matching pattern`
57
- : `Create ${req.type} matching ${reqPath}`,
58
- severity: "error"
59
- });
60
- }
61
- else {
62
- // Check quality for all matches?
63
- for (const match of matches) {
64
- const check = checkArtifact(process.cwd(), match, req.type);
65
- if (req.type === "file") {
66
- errors.push(...checkContentQuality(check.absolutePath));
67
- }
68
- }
69
- }
70
- continue;
71
- }
72
- const check = checkArtifact(process.cwd(), reqPath, req.type);
73
- if (!check.exists) {
74
- errors.push({
75
- code: "MISSING_ARTIFACT",
76
- path: check.normalized,
77
- message: `Required ${req.type} missing: ${check.normalized}`,
78
- remediation: req.template
79
- ? `Create from template: ${req.template}`
80
- : `Create ${req.type} at ${check.normalized}`,
81
- template: req.template,
82
- artifactType: req.type,
83
- severity: "error"
84
- });
85
- }
86
- else {
87
- // Validate type matches
88
- if (req.type === "directory" && !check.isDirectory) {
89
- errors.push({
90
- code: "INVALID_TYPE",
91
- path: check.normalized,
92
- message: `Expected directory but found file: ${check.normalized}`,
93
- remediation: "Remove file and create directory",
94
- severity: "error"
95
- });
96
- }
97
- else if (req.type === "file" && !check.isFile) {
98
- errors.push({
99
- code: "INVALID_TYPE",
100
- path: check.normalized,
101
- message: `Expected file but found directory: ${check.normalized}`,
102
- remediation: "Remove directory and create file",
103
- severity: "error"
104
- });
105
- }
106
- else if (req.type === "file") {
107
- // Content Quality Check (Warnings)
108
- const qualityWarnings = checkContentQuality(check.absolutePath);
109
- errors.push(...qualityWarnings);
110
- }
111
- }
112
- }
113
- // Fail fast if strict artifact check fails?
114
- // Specs say "Missing artifacts -> fail fast errors".
115
- // Usually means return immediately or don't proceed to gates?
116
- // Let's collect all missing artifacts first, but if ANY are missing, we probably shouldn't check gates that rely on them.
117
- if (errors.length > 0) {
118
- return finalizeResult(errors, state, false, shouldWrite);
119
- }
120
- // Optional Structure Check: Units
121
- // IF units/ exists under any intent, IT MUST contain unit-brief.md
122
- checkOptionalUnits(errors);
123
- if (errors.length > 0) {
124
- return finalizeResult(errors, state, false, shouldWrite);
125
- }
126
- // Step D: Gate checks (HARD ERRORS)
127
- // "Support frontmatter gates"
128
- const gates = policy.phases[targetPhase].gates || [];
129
- for (const gate of gates) {
130
- if (gate.kind === "frontmatter") {
131
- try {
132
- // Create a copy with interpolated file path
133
- const interpolatedGate = { ...gate };
134
- interpolatedGate.file = interpolateTokens(gate.file, { intentId: state.active.intent_id || undefined });
135
- const gateError = checkFrontmatterGate(interpolatedGate);
136
- if (gateError) {
137
- errors.push(gateError);
138
- }
139
- }
140
- catch (e) {
141
- errors.push({
142
- code: "CONFIG_ERROR",
143
- message: `Gate file path interpolation failed: ${e.message}`,
144
- severity: "error"
145
- });
146
- }
147
- }
148
- else {
149
- console.warn(`Unknown gate kind: ${gate.kind}`);
150
- }
151
- }
152
- if (errors.length > 0) {
153
- return finalizeResult(errors, state, false, shouldWrite);
154
- }
155
- // Step E: Phase transition enforcement
156
- let applying = false;
157
- // Only check transition if we have a requested phase and we are NOT just probing a specific phase with --phase
158
- if (state.phase.requested && !options.phase) {
159
- // Validate transition
160
- const from = state.phase.current;
161
- const to = state.phase.requested;
162
- const transition = policy.transitions.find(t => t.from === from && t.to === to);
163
- if (!transition) {
164
- errors.push({
165
- code: "INVALID_TRANSITION",
166
- message: `Transition from '${from}' to '${to}' is not defined in policy.`,
167
- remediation: "Reset requested phase or update policy.",
168
- severity: "error"
169
- });
170
- }
171
- else {
172
- // Validate requires_gates[] for transition
173
- // These reference gates defined in the phase? OR are they ids?
174
- // "Validate requires_gates[] for transition" implies the transition object has a list of gate IDs that must pass.
175
- // But where are these gates defined? Usually gates are on the phase.
176
- // Interpretation: Check if the gates identified by ID in the CURRENT phase (or destination?) are passed?
177
- // "If transition not allowed -> ERROR"
178
- // "Validate requires_gates[] for transition" -> "If any gate fails -> ERROR"
179
- // Let's assume gates are identified by 'id' in policy.phases[...].gates
180
- // AND the transition rules apply to the CURRENT state config or the TARGET?
181
- // Usually transition gates check if you are ready to LEAVE 'from' or ENTER 'to'.
182
- // Given "inception.intent.approved", it likely checks the state of the *current* phase artifacts to allow moving forward.
183
- if (transition.requires_gates) {
184
- // Find these gates. They could be in the 'from' phase or 'to' phase.
185
- // A safe bet is to search all defined gates in 'from' phase, or global?
186
- // The snippet shows: id: "inception.intent.approved" -> implies inception phase.
187
- // We need to look up these gates.
188
- // NOTE: We only ran checks for 'targetPhase' above.
189
- // If targetPhase == current, we checked current.
190
- // If targetPhase == requested (via --phase), we checked requested.
191
- // Transition logic is: checking if we can move FROM current TO requested.
192
- // So we should validate gates required by the transition.
193
- for (const gateId of transition.requires_gates) {
194
- // Find the gate definition.
195
- // We search in 'from' phase first, then 'to' phase?
196
- // Or just iterate all phases?
197
- let foundGate;
198
- // Optimization: check 'from' first
199
- if (policy.phases[from]?.gates) {
200
- foundGate = policy.phases[from].gates?.find(g => g.id === gateId);
201
- }
202
- // check 'to' next
203
- if (!foundGate && policy.phases[to]?.gates) {
204
- foundGate = policy.phases[to].gates?.find(g => g.id === gateId);
205
- }
206
- if (!foundGate) {
207
- errors.push({
208
- code: "MISSING_GATE_DEF",
209
- message: `Transition requires gate '${gateId}' but it is not found in '${from}' or '${to}' phases.`,
210
- severity: "error"
211
- });
212
- continue;
213
- }
214
- // Run the gate check
215
- if (foundGate.kind === "frontmatter") {
216
- const gateError = checkFrontmatterGate(foundGate);
217
- if (gateError) {
218
- errors.push(gateError);
219
- }
220
- }
221
- }
222
- }
223
- }
224
- }
225
- // Step F: Apply transition (--apply)
226
- // Only apply if we are allowed to write back
227
- if (shouldWrite && errors.length === 0 && options.apply && state.phase.requested && !options.phase) {
228
- // Valid and apply requested
229
- // Safety: ensure transition valid again? (already done in Step E if we had errors, it would fail)
230
- // If we are here, Step E passed.
231
- state.phase.current = state.phase.requested;
232
- state.phase.requested = null; // Clear request
233
- applying = true;
234
- }
235
- return finalizeResult(errors, state, applying, shouldWrite);
236
- }
237
- function checkFrontmatterGate(gate) {
238
- const filePath = path.join(process.cwd(), gate.file);
239
- if (!fs.existsSync(filePath)) {
240
- return {
241
- code: "GATE_FILE_MISSING",
242
- path: gate.file,
243
- message: `Gate '${gate.id}' failed: File missing`,
244
- remediation: `Create ${gate.file}`,
245
- severity: "error"
246
- };
247
- }
248
- try {
249
- const content = fs.readFileSync(filePath, 'utf8');
250
- const parsed = matter(content);
251
- if (!parsed.data) {
252
- return {
253
- code: "GATE_NO_FRONTMATTER",
254
- path: gate.file,
255
- message: `Gate '${gate.id}' failed: No frontmatter found`,
256
- remediation: "Add frontmatter variables",
257
- severity: "error"
258
- };
259
- }
260
- if (gate.field) {
261
- // Support dot-notation for nested fields
262
- const fields = gate.field.split('.');
263
- let val = parsed.data;
264
- for (const f of fields) {
265
- if (val && typeof val === 'object') {
266
- val = val[f];
267
- }
268
- else {
269
- val = undefined;
270
- break;
271
- }
272
- }
273
- if (val === undefined) {
274
- return {
275
- code: "GATE_FIELD_MISSING",
276
- path: gate.file,
277
- message: `Gate '${gate.id}' failed: Field '${gate.field}' missing`,
278
- remediation: `Add '${gate.field}' to frontmatter`,
279
- severity: "error"
280
- };
281
- }
282
- if (gate.equals !== undefined && String(val) !== String(gate.equals)) {
283
- return {
284
- code: "GATE_VALUE_MISMATCH",
285
- path: gate.file,
286
- message: `Gate '${gate.id}' failed: Field '${gate.field}' is '${val}', expected '${gate.equals}'`,
287
- remediation: `Set '${gate.field}: ${gate.equals}' in ${gate.file}`,
288
- severity: "error"
289
- };
290
- }
291
- }
292
- return null;
293
- }
294
- catch (e) {
295
- return {
296
- code: "GATE_PARSE_ERROR",
297
- path: gate.file,
298
- message: `Gate '${gate.id}' failed: Error parsing file: ${e}`,
299
- severity: "error"
300
- };
301
- }
302
- }
303
- function checkOptionalUnits(errors) {
304
- const intentsDir = path.join(process.cwd(), "memory-bank", "intents");
305
- if (!fs.existsSync(intentsDir))
306
- return;
307
- try {
308
- const intents = fs.readdirSync(intentsDir).filter(f => fs.statSync(path.join(intentsDir, f)).isDirectory());
309
- for (const intent of intents) {
310
- const unitsDir = path.join(intentsDir, intent, "units");
311
- if (fs.existsSync(unitsDir) && fs.statSync(unitsDir).isDirectory()) {
312
- // Units folder exists, check contents
313
- const units = fs.readdirSync(unitsDir).filter(f => fs.statSync(path.join(unitsDir, f)).isDirectory());
314
- for (const unit of units) {
315
- const briefPath = path.join(unitsDir, unit, "unit-brief.md");
316
- if (!fs.existsSync(briefPath)) {
317
- const relPath = path.relative(process.cwd(), briefPath);
318
- errors.push({
319
- code: "MISSING_ARTIFACT",
320
- path: relPath,
321
- message: `Unit '${unit}' requires unit-brief.md`,
322
- remediation: "Create from template",
323
- template: ".iris/aidlc/templates/unit-brief.md",
324
- artifactType: "file",
325
- severity: "error"
326
- });
327
- }
328
- else {
329
- // Check quality of unit brief too
330
- errors.push(...checkContentQuality(briefPath));
331
- }
332
- }
333
- }
334
- }
335
- }
336
- catch (e) {
337
- // Ignore read errors
338
- }
339
- }
340
- function checkContentQuality(filePath) {
341
- const warnings = [];
342
- try {
343
- const content = fs.readFileSync(filePath, "utf8");
344
- const relativePath = path.relative(process.cwd(), filePath);
345
- // 1. Effective emptiness
346
- if (content.trim().length < 20) {
347
- warnings.push({
348
- code: "QUALITY_EMPTY",
349
- path: relativePath,
350
- message: `File is effectively empty (<20 chars): ${relativePath}`,
351
- severity: "warning",
352
- remediation: "Add meaningful content"
353
- });
354
- }
355
- // 2. Placeholders
356
- const placeholderRegex = /\b(TODO|FIXME|TBD)\b|<[^>]+>/;
357
- if (placeholderRegex.test(content)) {
358
- warnings.push({
359
- code: "QUALITY_PLACEHOLDER",
360
- path: relativePath,
361
- message: `File contains placeholders (TODO/FIXME/TBD/<...>): ${relativePath}`,
362
- severity: "warning",
363
- remediation: "Replace placeholders with actual content"
364
- });
365
- }
366
- }
367
- catch (e) {
368
- // Ignore read errors
369
- }
370
- return warnings;
371
- }
372
- function finalizeResult(errors, state, applied, writeBack) {
373
- const hasErrors = errors.some(e => e.severity === "error");
374
- const hasWarnings = errors.some(e => e.severity === "warning");
375
- const isValid = !hasErrors;
376
- if (writeBack) {
377
- state.last_validation.at = new Date().toISOString();
378
- state.last_validation.result = isValid ? "valid" : "invalid";
379
- saveState(state);
380
- }
381
- return {
382
- valid: isValid,
383
- warnings: hasWarnings,
384
- phase: {
385
- current: state.phase.current,
386
- requested: state.phase.requested,
387
- applied: applied
388
- },
389
- errors: errors
390
- };
391
- }