iriai-build 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/bin/iriai-build.js +78 -0
  2. package/bridge-v3.js +98 -0
  3. package/cli/bootstrap.js +83 -0
  4. package/cli/commands/implementation.js +64 -0
  5. package/cli/commands/index.js +46 -0
  6. package/cli/commands/launch.js +153 -0
  7. package/cli/commands/plan.js +117 -0
  8. package/cli/commands/setup.js +80 -0
  9. package/cli/commands/slack.js +97 -0
  10. package/cli/commands/transfer.js +111 -0
  11. package/cli/config.js +92 -0
  12. package/cli/display.js +121 -0
  13. package/cli/terminal-input.js +666 -0
  14. package/cli/wait.js +82 -0
  15. package/index.js +1488 -0
  16. package/lib/agent-process.js +170 -0
  17. package/lib/bridge-state.js +126 -0
  18. package/lib/constants.js +137 -0
  19. package/lib/health-monitor.js +113 -0
  20. package/lib/prompt-builder.js +565 -0
  21. package/lib/signal-watcher.js +215 -0
  22. package/lib/slack-helpers.js +224 -0
  23. package/lib/state-machines/feature-lead.js +408 -0
  24. package/lib/state-machines/operator-agent.js +173 -0
  25. package/lib/state-machines/planning-role.js +161 -0
  26. package/lib/state-machines/role-agent.js +186 -0
  27. package/lib/state-machines/team-orchestrator.js +160 -0
  28. package/package.json +31 -0
  29. package/v3/.handover-html-evidence.md +35 -0
  30. package/v3/KICKOFF-HTML-EVIDENCE.md +98 -0
  31. package/v3/PLAN-HTML-EVIDENCE-HARDENING.md +603 -0
  32. package/v3/adapters/desktop-adapter.js +78 -0
  33. package/v3/adapters/interface.js +146 -0
  34. package/v3/adapters/slack-adapter.js +608 -0
  35. package/v3/adapters/slack-helpers.js +179 -0
  36. package/v3/adapters/terminal-adapter.js +249 -0
  37. package/v3/agent-supervisor.js +320 -0
  38. package/v3/artifact-portal.js +1184 -0
  39. package/v3/bridge.db +0 -0
  40. package/v3/constants.js +170 -0
  41. package/v3/db.js +76 -0
  42. package/v3/file-io.js +216 -0
  43. package/v3/helpers.js +174 -0
  44. package/v3/operator.js +364 -0
  45. package/v3/orchestrator.js +2886 -0
  46. package/v3/plan-compiler.js +440 -0
  47. package/v3/prompt-builder.js +849 -0
  48. package/v3/queries.js +461 -0
  49. package/v3/recovery.js +508 -0
  50. package/v3/review-sessions.js +360 -0
  51. package/v3/roles/accessibility-auditor/CLAUDE.md +50 -0
  52. package/v3/roles/analytics-engineer/CLAUDE.md +40 -0
  53. package/v3/roles/architect/CLAUDE.md +809 -0
  54. package/v3/roles/backend-implementer/CLAUDE.md +97 -0
  55. package/v3/roles/code-reviewer/CLAUDE.md +89 -0
  56. package/v3/roles/database-implementer/CLAUDE.md +97 -0
  57. package/v3/roles/deployer/CLAUDE.md +42 -0
  58. package/v3/roles/designer/CLAUDE.md +386 -0
  59. package/v3/roles/documentation/CLAUDE.md +40 -0
  60. package/v3/roles/feature-lead/CLAUDE.md +233 -0
  61. package/v3/roles/frontend-implementer/CLAUDE.md +97 -0
  62. package/v3/roles/implementer/CLAUDE.md +97 -0
  63. package/v3/roles/integration-tester/CLAUDE.md +174 -0
  64. package/v3/roles/observability-engineer/CLAUDE.md +40 -0
  65. package/v3/roles/operator/CLAUDE.md +322 -0
  66. package/v3/roles/orchestrator/CLAUDE.md +288 -0
  67. package/v3/roles/package-implementer/CLAUDE.md +47 -0
  68. package/v3/roles/performance-analyst/CLAUDE.md +49 -0
  69. package/v3/roles/plan-compiler/CLAUDE.md +163 -0
  70. package/v3/roles/planning-lead/CLAUDE.md +41 -0
  71. package/v3/roles/pm/CLAUDE.md +806 -0
  72. package/v3/roles/regression-tester/CLAUDE.md +135 -0
  73. package/v3/roles/release-manager/CLAUDE.md +43 -0
  74. package/v3/roles/security-auditor/CLAUDE.md +90 -0
  75. package/v3/roles/smoke-tester/CLAUDE.md +97 -0
  76. package/v3/roles/test-author/CLAUDE.md +42 -0
  77. package/v3/roles/verifier/CLAUDE.md +90 -0
  78. package/v3/schema.sql +134 -0
  79. package/v3/slack-adapter.js +510 -0
  80. package/v3/slack-helpers.js +346 -0
@@ -0,0 +1,161 @@
1
+ // planning-role.js — Planning pipeline role (PM, Designer, Architect, Plan-Compiler).
2
+ // Replaces run-planning-role.sh in Slack mode. Sequential dispatch via bridge.
3
+ //
4
+ // IDLE ──[dispatch(task)]──→ RUNNING ──[signal:done]──→ COMPLETE
5
+ // ├──[exit + no .done]──→ RETRYING → RUNNING
6
+ // └──[signal:agentResponse]──→ post to Slack, stay RUNNING
7
+
8
+ import { EventEmitter } from "node:events";
9
+ import fs from "node:fs";
10
+ import path from "node:path";
11
+ import AgentProcess from "../agent-process.js";
12
+ import { buildPlanningRolePrompt } from "../prompt-builder.js";
13
+ import {
14
+ PLANNING_BASE, IRIAI_TEAM_DIR, MAX_PLANNING_RETRIES,
15
+ FAST_EXIT_THRESHOLD_MS, FAST_EXIT_BACKOFF_S, NORMAL_BACKOFF_S,
16
+ SIGNAL, ROLE_DIRS,
17
+ } from "../constants.js";
18
+
19
+ export default class PlanningRole extends EventEmitter {
20
+ /**
21
+ * @param {object} opts
22
+ * @param {string} opts.role - Role name (pm, designer, architect, plan-compiler)
23
+ */
24
+ constructor({ role }) {
25
+ super();
26
+ this.role = role;
27
+ this.signalDir = ROLE_DIRS[role];
28
+ this.key = `planning-${role}`;
29
+
30
+ this._agent = null;
31
+ this._state = "idle";
32
+ this._crashCount = 0;
33
+ this._taskContent = "";
34
+ this._featureSlug = "";
35
+ }
36
+
37
+ get state() { return this._state; }
38
+
39
+ /**
40
+ * Dispatch this role with a task.
41
+ * @param {string} featureSlug
42
+ * @param {string} thread_ts
43
+ */
44
+ dispatch(featureSlug, thread_ts) {
45
+ if (this._state === "running") {
46
+ this.kill();
47
+ }
48
+
49
+ this._featureSlug = featureSlug;
50
+
51
+ // Build task header (same as index.js dispatchToRole)
52
+ const taskHeader = [
53
+ "SLACK_MODE=true",
54
+ `FEATURE_SLUG=${featureSlug}`,
55
+ `SIGNAL_DIR=${this.signalDir}`,
56
+ `THREAD_TS=${thread_ts}`,
57
+ "---",
58
+ ].join("\n");
59
+
60
+ // Check if there's an existing .task from planning lead
61
+ const taskPath = path.join(this.signalDir, SIGNAL.TASK);
62
+ let existingTask = "";
63
+ try {
64
+ if (fs.existsSync(taskPath)) {
65
+ existingTask = fs.readFileSync(taskPath, "utf-8");
66
+ }
67
+ } catch { /* ok */ }
68
+
69
+ this._taskContent = existingTask
70
+ ? `${taskHeader}\n${existingTask}`
71
+ : `${taskHeader}\nStart the ${this.role} phase for feature: ${featureSlug}`;
72
+
73
+ // Write task file
74
+ fs.writeFileSync(taskPath, this._taskContent);
75
+
76
+ // Parse Slack metadata from task content
77
+ const bodyMatch = this._taskContent.split("---\n").slice(1).join("---\n");
78
+ const taskBody = bodyMatch || this._taskContent;
79
+
80
+ this._crashCount = 0;
81
+ this._spawn(taskBody);
82
+ }
83
+
84
+ kill() {
85
+ if (this._agent) {
86
+ this._agent.kill();
87
+ this._agent = null;
88
+ }
89
+ this._state = "idle";
90
+ }
91
+
92
+ _spawn(taskBody) {
93
+ // Clean done/output signals
94
+ for (const sig of [SIGNAL.DONE, SIGNAL.OUTPUT]) {
95
+ try { fs.unlinkSync(path.join(this.signalDir, sig)); } catch { /* ok */ }
96
+ }
97
+
98
+ const prompt = buildPlanningRolePrompt({
99
+ task: taskBody || this._taskContent,
100
+ signalDir: this.signalDir,
101
+ featureSlug: this._featureSlug,
102
+ });
103
+
104
+ this._state = "running";
105
+ this._agent = new AgentProcess({
106
+ key: this.key,
107
+ cwd: this.signalDir,
108
+ extraEnv: { PLANNING_SIGNAL_BASE: PLANNING_BASE },
109
+ signalDir: this.signalDir,
110
+ });
111
+ this._agent.spawnClaude(prompt);
112
+
113
+ this._agent.on("exit", ({ exitCode, elapsed }) => {
114
+ this._agent = null;
115
+
116
+ // Check for .done (success)
117
+ if (fs.existsSync(path.join(this.signalDir, SIGNAL.DONE))) {
118
+ this._state = "complete";
119
+ this.emit("done", { key: this.key, role: this.role });
120
+ this.emit("lifecycle", { key: this.key, event: "done" });
121
+ return;
122
+ }
123
+
124
+ // Crash — retry
125
+ this._crashCount++;
126
+ if (this._crashCount <= MAX_PLANNING_RETRIES) {
127
+ const isFast = elapsed < FAST_EXIT_THRESHOLD_MS;
128
+ const backoffS = isFast
129
+ ? this._crashCount * FAST_EXIT_BACKOFF_S
130
+ : this._crashCount * NORMAL_BACKOFF_S;
131
+
132
+ this._state = "retrying";
133
+ this.emit("lifecycle", {
134
+ key: this.key,
135
+ event: "crash-retry",
136
+ retryCount: this._crashCount,
137
+ backoffS,
138
+ });
139
+
140
+ // Re-create .task for retry
141
+ try {
142
+ fs.writeFileSync(
143
+ path.join(this.signalDir, SIGNAL.TASK),
144
+ this._taskContent
145
+ );
146
+ } catch { /* ok */ }
147
+
148
+ setTimeout(() => {
149
+ const bodyMatch = this._taskContent.split("---\n").slice(1).join("---\n");
150
+ this._spawn(bodyMatch || this._taskContent);
151
+ }, backoffS * 1000);
152
+ } else {
153
+ this._state = "crashed";
154
+ this.emit("crashed", { key: this.key, role: this.role });
155
+ this.emit("lifecycle", { key: this.key, event: "crashed" });
156
+ }
157
+ });
158
+
159
+ this.emit("lifecycle", { key: this.key, event: "started" });
160
+ }
161
+ }
@@ -0,0 +1,186 @@
1
+ // role-agent.js — Task lifecycle with retry for implementation roles.
2
+ // Replaces run-role.sh. Direct PTY spawn, event-driven exit, crash retry.
3
+ //
4
+ // IDLE ──[signal:task]──→ RUNNING ──[signal:done]──→ IDLE
5
+ // ├──[exit + no .done + retries left]──→ RETRYING → RUNNING
6
+ // ├──[exit + no .done + no retries]──→ CRASHED
7
+ // └──[signal:needsRestart]──→ RUNNING (handover)
8
+
9
+ import { EventEmitter } from "node:events";
10
+ import fs from "node:fs";
11
+ import path from "node:path";
12
+ import AgentProcess from "../agent-process.js";
13
+ import { buildRolePrompt } from "../prompt-builder.js";
14
+ import {
15
+ IMPL_BASE, MAX_ROLE_RETRIES,
16
+ FAST_EXIT_THRESHOLD_MS, FAST_EXIT_BACKOFF_S, NORMAL_BACKOFF_S,
17
+ SIGNAL,
18
+ } from "../constants.js";
19
+
20
+ export default class RoleAgent extends EventEmitter {
21
+ /**
22
+ * @param {object} opts
23
+ * @param {string} opts.slug - Feature slug
24
+ * @param {string} opts.role - Role name
25
+ * @param {string} opts.signalDir - Role's signal directory
26
+ * @param {string} opts.cwd - Working directory (worktree)
27
+ * @param {string} [opts.teamNum] - Team number (for key generation)
28
+ * @param {string} [opts.model] - Claude model (default: "opus")
29
+ */
30
+ constructor({ slug, role, signalDir, cwd, teamNum, model }) {
31
+ super();
32
+ this.slug = slug;
33
+ this.role = role;
34
+ this.signalDir = signalDir;
35
+ this.cwd = cwd;
36
+ this.model = model || "opus";
37
+ this.key = teamNum
38
+ ? `role-${slug}-${teamNum}-${role}`
39
+ : `review-${slug}-${role}`;
40
+
41
+ this._agent = null;
42
+ this._state = "idle";
43
+ this._retryCount = 0;
44
+ this._handoverContent = "";
45
+ this._task = "";
46
+ }
47
+
48
+ get state() { return this._state; }
49
+
50
+ /**
51
+ * Handle a new .task file (triggered by SignalWatcher or orchestrator).
52
+ */
53
+ handleTask() {
54
+ if (this._state === "running") {
55
+ console.log(`[role] ${this.key}: already running, ignoring new task`);
56
+ return;
57
+ }
58
+
59
+ const taskPath = path.join(this.signalDir, SIGNAL.TASK);
60
+ try {
61
+ this._task = fs.readFileSync(taskPath, "utf-8").trim();
62
+ // Atomic rename: .task → .active-task
63
+ fs.renameSync(taskPath, path.join(this.signalDir, SIGNAL.ACTIVE_TASK));
64
+ } catch {
65
+ return;
66
+ }
67
+ if (!this._task) return;
68
+
69
+ this._retryCount = 0;
70
+ this._handoverContent = "";
71
+ this._spawn();
72
+ }
73
+
74
+ /**
75
+ * Handle .needs-restart signal (context handover).
76
+ */
77
+ handleNeedsRestart() {
78
+ // Read handover content before killing
79
+ const handoverPath = path.join(this.signalDir, SIGNAL.HANDOVER);
80
+ if (fs.existsSync(handoverPath)) {
81
+ this._handoverContent = fs.readFileSync(handoverPath, "utf-8");
82
+ fs.unlinkSync(handoverPath);
83
+ }
84
+
85
+ // Clean signal
86
+ const restartPath = path.join(this.signalDir, SIGNAL.NEEDS_RESTART);
87
+ try { fs.unlinkSync(restartPath); } catch { /* ignore */ }
88
+
89
+ // Kill current and respawn (NOT counted as crash)
90
+ if (this._agent) {
91
+ this._agent.kill();
92
+ this._agent = null;
93
+ }
94
+
95
+ this.emit("lifecycle", { key: this.key, event: "handover-restart" });
96
+ this._spawn();
97
+ }
98
+
99
+ kill() {
100
+ if (this._agent) {
101
+ this._agent.kill();
102
+ this._agent = null;
103
+ }
104
+ this._state = "idle";
105
+ }
106
+
107
+ _spawn() {
108
+ // Clean signals
109
+ for (const sig of [SIGNAL.DONE, SIGNAL.OUTPUT, SIGNAL.NEEDS_RESTART]) {
110
+ try { fs.unlinkSync(path.join(this.signalDir, sig)); } catch { /* ok */ }
111
+ }
112
+
113
+ let recoveryContext = null;
114
+ if (this._handoverContent) {
115
+ recoveryContext = { type: "handover", content: this._handoverContent };
116
+ this._handoverContent = "";
117
+ } else if (this._retryCount > 0) {
118
+ recoveryContext = { type: "crash", retryCount: this._retryCount };
119
+ }
120
+
121
+ const prompt = buildRolePrompt({
122
+ role: this.role,
123
+ signalDir: this.signalDir,
124
+ task: this._task,
125
+ recoveryContext,
126
+ });
127
+
128
+ this._state = "running";
129
+ this._agent = new AgentProcess({
130
+ key: this.key,
131
+ cwd: this.cwd,
132
+ extraEnv: { IMPL_SIGNAL_BASE: IMPL_BASE },
133
+ signalDir: this.signalDir,
134
+ });
135
+ this._agent.spawnClaude(prompt, { model: this.model });
136
+
137
+ // Write PID to .running so recovery can kill orphans
138
+ try { fs.writeFileSync(path.join(this.signalDir, SIGNAL.RUNNING), String(this._agent.pid)); } catch { /* ok */ }
139
+
140
+ this._agent.on("exit", ({ exitCode, elapsed }) => {
141
+ this._agent = null;
142
+ try { fs.unlinkSync(path.join(this.signalDir, SIGNAL.RUNNING)); } catch { /* ok */ }
143
+
144
+ // Check for .done
145
+ const donePath = path.join(this.signalDir, SIGNAL.DONE);
146
+ if (fs.existsSync(donePath)) {
147
+ this._state = "idle";
148
+ this.emit("done", { key: this.key, role: this.role });
149
+ this.emit("lifecycle", { key: this.key, event: "done" });
150
+ return;
151
+ }
152
+
153
+ // No .done — crash or unexpected exit
154
+ this._retryCount++;
155
+ if (this._retryCount <= MAX_ROLE_RETRIES) {
156
+ const isFast = elapsed < FAST_EXIT_THRESHOLD_MS;
157
+ const backoffS = isFast
158
+ ? this._retryCount * FAST_EXIT_BACKOFF_S
159
+ : this._retryCount * NORMAL_BACKOFF_S;
160
+
161
+ this._state = "retrying";
162
+ this.emit("lifecycle", {
163
+ key: this.key,
164
+ event: "crash-retry",
165
+ retryCount: this._retryCount,
166
+ backoffS,
167
+ fastExit: isFast,
168
+ });
169
+
170
+ setTimeout(() => this._spawn(), backoffS * 1000);
171
+ } else {
172
+ // Max retries exceeded
173
+ this._state = "crashed";
174
+ try {
175
+ fs.writeFileSync(path.join(this.signalDir, SIGNAL.DONE), "CRASHED");
176
+ fs.writeFileSync(path.join(this.signalDir, SIGNAL.CRASHED), "CRASHED");
177
+ } catch { /* ignore */ }
178
+
179
+ this.emit("crashed", { key: this.key, role: this.role, retries: MAX_ROLE_RETRIES });
180
+ this.emit("lifecycle", { key: this.key, event: "crashed" });
181
+ }
182
+ });
183
+
184
+ this.emit("lifecycle", { key: this.key, event: "started" });
185
+ }
186
+ }
@@ -0,0 +1,160 @@
1
+ // team-orchestrator.js — Team orchestrator state machine.
2
+ // Replaces run-team.sh orchestrator logic. Direct PTY spawn.
3
+ //
4
+ // IDLE ──[signal:task]──→ RUNNING ──[signal:gateReady]──→ GATE_READY
5
+ // └──[exit + no .gate-ready + retry ≤1]──→ RETRYING → RUNNING
6
+
7
+ import { EventEmitter } from "node:events";
8
+ import fs from "node:fs";
9
+ import path from "node:path";
10
+ import AgentProcess from "../agent-process.js";
11
+ import { buildOrchestratorPrompt } from "../prompt-builder.js";
12
+ import {
13
+ IMPL_BASE, IRIAI_TEAM_DIR, MAX_ORCH_RETRIES,
14
+ FAST_EXIT_THRESHOLD_MS, FAST_EXIT_BACKOFF_S, NORMAL_BACKOFF_S,
15
+ SIGNAL,
16
+ } from "../constants.js";
17
+
18
+ export default class TeamOrchestrator extends EventEmitter {
19
+ /**
20
+ * @param {object} opts
21
+ * @param {string} opts.slug - Feature slug
22
+ * @param {string} opts.teamNum - Team number
23
+ * @param {string} opts.teamDir - Team directory (signal base for team)
24
+ * @param {string} opts.orchDir - Orchestrator signal directory
25
+ * @param {string} [opts.model] - Claude model (default: "opus")
26
+ */
27
+ constructor({ slug, teamNum, teamDir, orchDir, model }) {
28
+ super();
29
+ this.slug = slug;
30
+ this.teamNum = teamNum;
31
+ this.teamDir = teamDir;
32
+ this.orchDir = orchDir;
33
+ this.model = model || "opus";
34
+ this.key = `team-${slug}-${teamNum}`;
35
+
36
+ this._agent = null;
37
+ this._state = "idle";
38
+ this._crashCount = 0;
39
+ this._task = "";
40
+ }
41
+
42
+ get state() { return this._state; }
43
+
44
+ /**
45
+ * Handle a new .task file from Feature Lead.
46
+ */
47
+ handleTask() {
48
+ if (this._state === "running") {
49
+ console.log(`[orch] ${this.key}: already running, ignoring task`);
50
+ return;
51
+ }
52
+
53
+ const taskPath = path.join(this.orchDir, SIGNAL.TASK);
54
+ try {
55
+ this._task = fs.readFileSync(taskPath, "utf-8").trim();
56
+ fs.unlinkSync(taskPath);
57
+ } catch {
58
+ return;
59
+ }
60
+ if (!this._task) return;
61
+
62
+ // Clean previous gate signals
63
+ for (const sig of [SIGNAL.DONE, SIGNAL.GATE_READY, SIGNAL.CRASHED,
64
+ SIGNAL.GATE_APPROVED, SIGNAL.QUESTION, SIGNAL.ANSWER]) {
65
+ try { fs.unlinkSync(path.join(this.orchDir, sig)); } catch { /* ok */ }
66
+ }
67
+
68
+ this._crashCount = 0;
69
+ this._spawn();
70
+ }
71
+
72
+ kill() {
73
+ if (this._agent) {
74
+ this._agent.kill();
75
+ this._agent = null;
76
+ }
77
+ this._state = "idle";
78
+ }
79
+
80
+ _spawn() {
81
+ let recoveryContext = null;
82
+ if (this._crashCount > 0) {
83
+ recoveryContext = { retryCount: this._crashCount };
84
+ }
85
+
86
+ const prompt = buildOrchestratorPrompt({
87
+ teamDir: this.teamDir,
88
+ orchDir: this.orchDir,
89
+ task: this._task,
90
+ recoveryContext,
91
+ });
92
+
93
+ // Determine CWD — prefer worktree if available
94
+ const teamsRoot = path.join(path.dirname(IRIAI_TEAM_DIR), ".teams", `team-${this.teamNum}`);
95
+ const cwd = fs.existsSync(teamsRoot) ? teamsRoot : this.teamDir;
96
+
97
+ this._state = "running";
98
+
99
+ this._agent = new AgentProcess({
100
+ key: this.key,
101
+ cwd,
102
+ extraEnv: { IMPL_SIGNAL_BASE: IMPL_BASE },
103
+ signalDir: this.orchDir,
104
+ });
105
+ this._agent.spawnClaude(prompt, { model: this.model });
106
+
107
+ // Write PID to .running so recovery can kill orphans
108
+ fs.writeFileSync(path.join(this.orchDir, SIGNAL.RUNNING), String(this._agent.pid));
109
+
110
+ this._agent.on("exit", ({ exitCode, elapsed }) => {
111
+ this._agent = null;
112
+ try { fs.unlinkSync(path.join(this.orchDir, SIGNAL.RUNNING)); } catch { /* ok */ }
113
+
114
+ // Check for gate-ready (success)
115
+ if (fs.existsSync(path.join(this.orchDir, SIGNAL.GATE_READY))) {
116
+ this._state = "gate-ready";
117
+ this.emit("gateReady", { key: this.key, teamNum: this.teamNum });
118
+ this.emit("lifecycle", { key: this.key, event: "gate-ready" });
119
+ return;
120
+ }
121
+
122
+ // Check for .done (single-team fallback)
123
+ if (fs.existsSync(path.join(this.orchDir, SIGNAL.DONE))) {
124
+ this._state = "idle";
125
+ this.emit("done", { key: this.key, teamNum: this.teamNum });
126
+ this.emit("lifecycle", { key: this.key, event: "done" });
127
+ return;
128
+ }
129
+
130
+ // Crash — retry with backoff
131
+ this._crashCount++;
132
+ if (this._crashCount <= MAX_ORCH_RETRIES) {
133
+ const isFast = elapsed < FAST_EXIT_THRESHOLD_MS;
134
+ const backoffS = isFast
135
+ ? this._crashCount * FAST_EXIT_BACKOFF_S
136
+ : this._crashCount * NORMAL_BACKOFF_S;
137
+
138
+ this._state = "retrying";
139
+ this.emit("lifecycle", {
140
+ key: this.key,
141
+ event: "crash-retry",
142
+ retryCount: this._crashCount,
143
+ backoffS,
144
+ });
145
+
146
+ setTimeout(() => this._spawn(), backoffS * 1000);
147
+ } else {
148
+ this._state = "crashed";
149
+ try {
150
+ fs.writeFileSync(path.join(this.orchDir, SIGNAL.CRASHED), "CRASHED");
151
+ } catch { /* ignore */ }
152
+
153
+ this.emit("crashed", { key: this.key, teamNum: this.teamNum });
154
+ this.emit("lifecycle", { key: this.key, event: "crashed" });
155
+ }
156
+ });
157
+
158
+ this.emit("lifecycle", { key: this.key, event: "started" });
159
+ }
160
+ }
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "iriai-build",
3
+ "version": "0.1.0",
4
+ "description": "Iriai Build tool — AI agent orchestration CLI",
5
+ "main": "index.js",
6
+ "type": "module",
7
+ "bin": {
8
+ "iriai-build": "./bin/iriai-build.js"
9
+ },
10
+ "files": [
11
+ "bin/",
12
+ "cli/",
13
+ "lib/",
14
+ "v3/",
15
+ "index.js",
16
+ "bridge-v3.js"
17
+ ],
18
+ "scripts": {
19
+ "start": "node index.js",
20
+ "start:v2": "node bridge-v2.js",
21
+ "start:v3": "node bridge-v3.js"
22
+ },
23
+ "dependencies": {
24
+ "@inquirer/prompts": "^8.3.0",
25
+ "@slack/socket-mode": "^2.0.1",
26
+ "@slack/web-api": "^7.8.0",
27
+ "chokidar": "^4.0.3",
28
+ "commander": "^14.0.3",
29
+ "iriai-feedback": "^0.1.0"
30
+ }
31
+ }
@@ -0,0 +1,35 @@
1
+ # Handover: HTML Evidence Hardening
2
+
3
+ ## Session 1 (2026-03-05)
4
+
5
+ ### Completed Files
6
+ - [x] File 1: `v3/roles/implementer/CLAUDE.md` — Added deviations, self_reported_risks to Output, steps 6-7 to Process, replaced Context Management with .output.partial protocol (file_complete, deviation, risk entry types)
7
+ - [x] File 2: `v3/roles/backend-implementer/CLAUDE.md` — Same as #1 (steps 5-6 added as "Process continued")
8
+ - [x] File 3: `v3/roles/frontend-implementer/CLAUDE.md` — Same as #1
9
+ - [x] File 4: `v3/roles/database-implementer/CLAUDE.md` — Same as #1
10
+ - [x] File 5: `v3/roles/code-reviewer/CLAUDE.md` — Added gaps field (categories: error-handling, input-validation, pattern-compliance, edge-cases, test-coverage), .output.partial protocol (file_review, gap entry types)
11
+ - [x] File 6: `v3/roles/security-auditor/CLAUDE.md` — Added gaps field (categories: auth, injection, rate-limiting, secrets, cors, csrf, data-exposure), .output.partial protocol (endpoint_review, gap entry types)
12
+ - [x] File 7: `v3/roles/integration-tester/CLAUDE.md` — Added comprehensive E2E mandate section (happy path + error cases per journey + gap reporting), gaps field (categories: untested-journey, missing-error-case, missing-edge-case, visual-gap), .output.partial protocol (journey, gap entry types)
13
+ - [x] File 8: `v3/roles/regression-tester/CLAUDE.md` — Added gaps field (categories: untested-regression, missing-backward-compat, skipped-test-suite), .output.partial protocol (test_suite, gap entry types)
14
+ - [x] File 9: `v3/roles/verifier/CLAUDE.md` — Added gaps field (categories: unverified-criterion, insufficient-evidence, missing-acceptance-check), .output.partial protocol (criterion_check, gap entry types)
15
+ - [x] File 10: `v3/roles/orchestrator/CLAUDE.md` — Added steps 4b (review gaps), 4c (aggregate deviations/risks), 4d (build coverage matrix). Updated gate evidence YAML example with coverage_matrix, deviations, self_reported_risks, reviewer_comments, error-case journeys, gaps in qa_verdicts. Added note about team HTML compilation (no buttons, doc_type: "team")
16
+ - [x] File 11: `v3/roles/feature-lead/CLAUDE.md` — Added steps 4b (review gaps across levels), 4c (cross-team integration surface), 4d (feature-level coverage matrix), 4e (FL comments). Updated step 6 (merge evidence with new fields), step 7 (doc_type: "feature", team_html_paths), step 8 (HTML IS the message, no text summary)
17
+ - [x] File 12: `tools/visual-verification-mcp/evidence-compiler.js` — Major overhaul: validateEvidence() warns on missing gaps/deviations/coverage_matrix, errors on missing reviewer_comments for feature docs. buildHtml() now renders: coverage matrix table, deviation cards, self-reported risk cards, happy path / error case journey grouping, QA gaps subsections, pending QA sections, reviewer comments, cross-team surface (feature only), team evidence links (feature only). New CSS: .section-pending, .gap-item, .coverage-* classes. handleCompileGateEvidence() accepts doc_type and team_html_paths params.
18
+ - [x] File 13: `iriai-build/v3/prompt-builder.js` — Updated buildGateReviewInstructions() step 6 (include coverage_matrix, deviations, self_reported_risks, reviewer_comments, cross_team_surface), step 7 (doc_type: "feature", team_html_paths), step 8 (HTML IS the message, no text summary)
19
+ - [x] File 14: `iriai-build/v3/constants.js` — Added OUTPUT_PARTIAL: ".output.partial" to SIGNAL object
20
+ - [x] File 15: `iriai-build/v3/orchestrator.js` — Removed text status messages: _requestPhaseReview (summary post), handlePhaseReviewApproval ("Phase approved..."), handlePhaseReviewRejection ("phase rejected..."), handlePlanApproval ("Plan approved!", "Creating branches...", "Feature branches created...", "Implementation launched..."), handleGateApproval ("Gate approved!"), handleGateRejection ("Gate rejected...")
21
+
22
+ ### In Progress
23
+ (none — all complete)
24
+
25
+ ### Not Started
26
+ (none — all complete)
27
+
28
+ ### Decisions Made
29
+ - Used `_doc_type` and `_team_html_links` as internal properties on the evidence object (prefixed with underscore) to pass doc_type/team_html_paths from handler to buildHtml/validateEvidence without polluting the YAML schema
30
+ - Kept `nextLabel` variable in handlePhaseReviewApproval even though channel post was removed — it may be used elsewhere or for logging
31
+ - Removed ALL text status posts from planning thread in handlePlanApproval including "Implementation launched" (not explicitly listed in plan but follows the principle of "NO text-based status messages in the planning thread")
32
+ - For pending QA sections, check against EXPECTED_QA_ROLES constant defined in buildHtml
33
+
34
+ ### Notes for Next Session
35
+ All 15 files are complete. No further work needed.
@@ -0,0 +1,98 @@
1
+ # Kickoff: HTML Evidence Document Hardening
2
+
3
+ ## Your Mission
4
+
5
+ Implement the plan at `iriai-build/v3/PLAN-HTML-EVIDENCE-HARDENING.md`. Read it fully before starting.
6
+
7
+ ## Key Files (absolute paths)
8
+
9
+ ### Plan & Handover
10
+ - Plan: `/Users/danielzhang/src/iriai/iriai-build/v3/PLAN-HTML-EVIDENCE-HARDENING.md`
11
+ - Handover: `/Users/danielzhang/src/iriai/iriai-build/v3/.handover-html-evidence.md`
12
+
13
+ ### Files to Modify (15 total)
14
+
15
+ **Phase 1 — Implementer CLAUDE.md (add deviations, self_reported_risks, .output.partial):**
16
+ 1. `/Users/danielzhang/src/iriai/iriai-build/v3/roles/implementer/CLAUDE.md`
17
+ 2. `/Users/danielzhang/src/iriai/iriai-build/v3/roles/backend-implementer/CLAUDE.md`
18
+ 3. `/Users/danielzhang/src/iriai/iriai-build/v3/roles/frontend-implementer/CLAUDE.md`
19
+ 4. `/Users/danielzhang/src/iriai/iriai-build/v3/roles/database-implementer/CLAUDE.md`
20
+
21
+ **Phase 1 — Review agent CLAUDE.md (add gaps, .output.partial):**
22
+ 5. `/Users/danielzhang/src/iriai/iriai-build/v3/roles/code-reviewer/CLAUDE.md`
23
+ 6. `/Users/danielzhang/src/iriai/iriai-build/v3/roles/security-auditor/CLAUDE.md`
24
+ 7. `/Users/danielzhang/src/iriai/iriai-build/v3/roles/integration-tester/CLAUDE.md` (also: comprehensive E2E mandate)
25
+ 8. `/Users/danielzhang/src/iriai/iriai-build/v3/roles/regression-tester/CLAUDE.md`
26
+ 9. `/Users/danielzhang/src/iriai/iriai-build/v3/roles/verifier/CLAUDE.md`
27
+
28
+ **Phase 1 — Constants:**
29
+ 14. `/Users/danielzhang/src/iriai/iriai-build/v3/constants.js` (add OUTPUT_PARTIAL signal)
30
+
31
+ **Phase 2 — Orchestrator & Feature Lead CLAUDE.md:**
32
+ 10. `/Users/danielzhang/src/iriai/iriai-build/v3/roles/orchestrator/CLAUDE.md` (coverage matrix, deviation aggregation, reviewer comments, team HTML compilation — no buttons)
33
+ 11. `/Users/danielzhang/src/iriai/iriai-build/v3/roles/feature-lead/CLAUDE.md` (cross-team surface, FL comments, feature-level coverage matrix, sole poster of approve/reject with HTML)
34
+
35
+ **Phase 3 — Code changes:**
36
+ 12. `/Users/danielzhang/src/iriai/tools/visual-verification-mcp/evidence-compiler.js` (HTML template overhaul, new sections, pending styling, doc_type param)
37
+ 13. `/Users/danielzhang/src/iriai/iriai-build/v3/prompt-builder.js` (gate review instructions update)
38
+ 15. `/Users/danielzhang/src/iriai/iriai-build/v3/orchestrator.js` (remove text status messages from planning thread)
39
+
40
+ ## Execution Rules
41
+
42
+ 1. Read the plan fully before making any changes
43
+ 2. Read each file before editing it
44
+ 3. Work through phases in order (Phase 1 files can be done in parallel, Phase 2 depends on Phase 1, Phase 3 parallel with Phase 2)
45
+ 4. For CLAUDE.md files: ADD new sections/fields to existing content — do not rewrite entire files
46
+ 5. For evidence-compiler.js: this is a major overhaul of the buildHtml() function and validateEvidence() — read the full file first
47
+ 6. For orchestrator.js: surgical removal of specific `postToChannel`/`postToThread` calls in planning thread — do not change dispatch logic
48
+
49
+ ## Recursive Handover Protocol
50
+
51
+ You will likely not finish all 15 files in one context window. Before your context runs low:
52
+
53
+ 1. Write your progress to `/Users/danielzhang/src/iriai/iriai-build/v3/.handover-html-evidence.md` using this format:
54
+
55
+ ```markdown
56
+ # Handover: HTML Evidence Hardening
57
+
58
+ ## Session N (date)
59
+
60
+ ### Completed Files
61
+ - [x] File 1: `/path/to/file` — description of changes made
62
+ - [x] File 2: ...
63
+
64
+ ### In Progress
65
+ - [ ] File X: `/path/to/file` — what was started, what remains
66
+
67
+ ### Not Started
68
+ - [ ] File Y: `/path/to/file`
69
+ - [ ] File Z: ...
70
+
71
+ ### Decisions Made
72
+ - [any judgment calls or deviations from the plan]
73
+
74
+ ### Notes for Next Session
75
+ - [anything the next session needs to know]
76
+ ```
77
+
78
+ 2. Tell the user: "Context getting low. Handover written. Start a new session with this prompt."
79
+
80
+ ## Resuming From Handover
81
+
82
+ If `.handover-html-evidence.md` exists, read it FIRST. It contains:
83
+ - Which files are done (do NOT re-edit them)
84
+ - Which file was in progress (finish it)
85
+ - Which files remain (continue in order)
86
+
87
+ Then continue executing the plan from where the previous session left off. Update the handover file before your own context runs low, incrementing the session number.
88
+
89
+ ## Summary of What We're Building
90
+
91
+ - **Incremental output (.output.partial)**: Append-only multi-doc YAML so agents don't lose work on context exhaustion
92
+ - **Enriched schemas**: Implementers report deviations + risks. Review agents report gaps. Orchestrator/FL add reviewer comments.
93
+ - **Integration-tester mandate**: Comprehensive GIFs for every golden path AND every error case
94
+ - **Coverage matrix**: Plan items mapped to implemented/verified/missing status
95
+ - **HTML overhaul**: New sections (coverage matrix, deviations, risks, gaps per agent, reviewer comments, cross-team surface), pending section styling, feature-level doc links to team docs
96
+ - **Approve/reject buttons**: ONLY on feature gate HTML in impl channel (not per-team)
97
+ - **Planning thread**: HTML evidence docs only, no text status messages
98
+ - **Impl channel**: Gate approvals must now include the feature gate HTML attachment. Everything else unchanged.