project-iris 0.0.13 → 0.0.15
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 +112 -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 +106 -33
- 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
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
|
-
}
|
package/dist/iris/uninstaller.js
DELETED
|
@@ -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
|
-
}
|
package/dist/iris/validator.js
DELETED
|
@@ -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
|
-
}
|