@moretea-labs/forge 1.7.0 → 1.7.2

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 (175) hide show
  1. package/AGENTS.md +2 -0
  2. package/CHANGELOG.md +26 -0
  3. package/CLAUDE.md +2 -0
  4. package/THIRD_PARTY_NOTICES.md +1 -1
  5. package/agents/openai.yaml +1 -1
  6. package/assets/partials-agents/02-operating-mode.partial.md +2 -0
  7. package/assets/plugin-registry.v1.json +3 -3
  8. package/assets/skill-version.json +2 -2
  9. package/assets/templates/helpers/check-architecture-sync.sh +17 -0
  10. package/assets/templates/helpers/inspect-project-state.ts +8 -6
  11. package/assets/templates/helpers/migrate-workflow-docs.ts +9 -7
  12. package/assets/templates/helpers/workflow-contract.ts +5 -1
  13. package/docs/architecture/CURRENT.md +12 -4
  14. package/docs/forge-plugin-management.md +1 -1
  15. package/docs/operations/controller-performance-and-502.md +19 -0
  16. package/docs/operations/homebrew.md +2 -2
  17. package/package.json +5 -2
  18. package/scripts/architecture-event.ts +2 -820
  19. package/scripts/architecture-queue.sh +2 -645
  20. package/scripts/archive-architecture-request.sh +2 -242
  21. package/scripts/archive-workflow.sh +1 -291
  22. package/scripts/benchmark-work-efficiency.ts +0 -0
  23. package/scripts/capability-config.ts +2 -392
  24. package/scripts/capability-resolver.ts +2 -503
  25. package/scripts/capture-plan.sh +1 -332
  26. package/scripts/check-agent-tooling.sh +2 -1531
  27. package/scripts/check-architecture-sync.sh +2 -302
  28. package/scripts/check-brain-manifest.sh +2 -253
  29. package/scripts/check-context-files.sh +2 -47
  30. package/scripts/check-deploy-sql-order.sh +2 -115
  31. package/scripts/check-runtime-architecture.mjs +243 -16
  32. package/scripts/check-skill-version.ts +3 -191
  33. package/scripts/check-source-duplication.mjs +61 -0
  34. package/scripts/check-stable-baseline.ts +0 -0
  35. package/scripts/check-task-sync.sh +2 -63
  36. package/scripts/check-task-workflow.sh +2 -1000
  37. package/scripts/codex-handoff-resume.sh +2 -267
  38. package/scripts/context-contract-sync.sh +2 -514
  39. package/scripts/contract-run.ts +2 -463
  40. package/scripts/contract-worktree.sh +1 -702
  41. package/scripts/ensure-task-workflow.sh +1 -1192
  42. package/scripts/inspect-project-state.ts +4 -279
  43. package/scripts/lib/project-init-lib.sh +7 -2
  44. package/scripts/lib/run-forge-helper.sh +19 -0
  45. package/scripts/lib/run-forge-helper.ts +26 -0
  46. package/scripts/migrate-project-template.sh +1 -33
  47. package/scripts/migrate-workflow-docs.ts +4 -412
  48. package/scripts/new-plan.sh +1 -180
  49. package/scripts/new-spec.sh +1 -47
  50. package/scripts/new-sprint.sh +1 -46
  51. package/scripts/plan-to-todo.sh +1 -771
  52. package/scripts/prepare-codex-handoff.sh +2 -201
  53. package/scripts/prepare-handoff.sh +1 -78
  54. package/scripts/refresh-current-status.sh +2 -467
  55. package/scripts/run-governed-gate.ts +3 -0
  56. package/scripts/select-agent-context-blocks.sh +2 -64
  57. package/scripts/ship-worktrees.sh +1 -531
  58. package/scripts/sprint-backlog.sh +1 -834
  59. package/scripts/stage-runtime-release.ts +2 -1
  60. package/scripts/summarize-failures.sh +2 -123
  61. package/scripts/switch-plan.sh +1 -150
  62. package/scripts/sync-brain-docs.sh +2 -379
  63. package/scripts/sync-generated-authority.mjs +175 -0
  64. package/scripts/verify-contract.sh +2 -593
  65. package/scripts/verify-sprint.sh +1 -500
  66. package/scripts/workflow-contract.ts +4 -201
  67. package/scripts/workstream-sync.sh +2 -261
  68. package/src/cli/agent-jobs/executor-health.ts +1 -1
  69. package/src/cli/commands/plugin.ts +12 -3
  70. package/src/cli/controller/check-runner.ts +7 -2
  71. package/src/cli/controller/context-pack.ts +46 -21
  72. package/src/cli/github/contracts.ts +16 -0
  73. package/src/cli/github/github.ts +2 -16
  74. package/src/cli/github/session.ts +2 -1
  75. package/src/cli/local-bridge/facade-api.ts +37 -47
  76. package/src/cli/local-bridge/server.ts +2 -2
  77. package/src/cli/mcp/legacy-tool-service.ts +10 -33
  78. package/src/cli/mcp/post-finalize-work-attribution.ts +150 -0
  79. package/src/cli/mcp/repository-tools.ts +125 -101
  80. package/src/cli/mcp/server.ts +57 -13
  81. package/src/cli/mcp/tool-contract.ts +14 -0
  82. package/src/cli/mcp/toolset.ts +2 -2
  83. package/src/cli/mcp/transports/http.ts +23 -0
  84. package/src/cli/repositories/command-classifier.ts +2 -2
  85. package/src/cli/repositories/command-executor.ts +24 -6
  86. package/src/cli/repositories/repository-snapshot.ts +5 -2
  87. package/src/cli/repositories/structured-git.ts +57 -3
  88. package/src/cli/repository/inspector.ts +11 -3
  89. package/src/runtime/context/generic-lsp-provider.ts +1 -1
  90. package/src/runtime/context/semantic-navigation-contract.ts +61 -0
  91. package/src/runtime/context/semantic-navigation.ts +9 -61
  92. package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +69 -29
  93. package/src/runtime/control-plane/execution/repository-work-attribution.ts +106 -0
  94. package/src/runtime/control-plane/execution/retained-work-resume.ts +203 -0
  95. package/src/runtime/control-plane/execution/work-completion-authority.ts +58 -0
  96. package/src/runtime/control-plane/execution/work-evidence-policy.ts +200 -0
  97. package/src/runtime/control-plane/execution/work-execution-support.ts +386 -0
  98. package/src/runtime/control-plane/execution/work-finalization-service.ts +1913 -0
  99. package/src/runtime/control-plane/execution/work-handle-authority.ts +150 -0
  100. package/src/runtime/control-plane/execution/work-handle-store.ts +1 -1
  101. package/src/runtime/control-plane/execution/work-operation-service.ts +468 -0
  102. package/src/runtime/control-plane/execution/work-path-scope.ts +37 -0
  103. package/src/runtime/control-plane/execution/work-preparation-service.ts +479 -0
  104. package/src/runtime/control-plane/execution/work-process-evidence.ts +129 -0
  105. package/src/runtime/control-plane/execution/work-task-receipt.ts +4 -43
  106. package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +13 -3
  107. package/src/runtime/control-plane/execution/work-verification-context.ts +56 -0
  108. package/src/runtime/control-plane/execution/work-verification-service.ts +388 -0
  109. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +5 -1
  110. package/src/runtime/control-plane/facade/controller-round-relay.ts +164 -37
  111. package/src/runtime/control-plane/facade/controller-session-store.ts +183 -24
  112. package/src/runtime/control-plane/facade/goal-workloop-access.ts +15 -1
  113. package/src/runtime/control-plane/facade/goal-workloop.ts +329 -148
  114. package/src/runtime/control-plane/facade/index.ts +2 -0
  115. package/src/runtime/control-plane/facade/plan-contract-store.ts +90 -22
  116. package/src/runtime/control-plane/facade/repository-work-admission.ts +147 -0
  117. package/src/runtime/control-plane/facade/requirement-authority.ts +83 -0
  118. package/src/runtime/control-plane/facade/types.ts +68 -5
  119. package/src/runtime/control-plane/facade/work-contract-store.ts +3 -209
  120. package/src/runtime/control-plane/facade/work-state-machine.ts +250 -0
  121. package/src/runtime/control-plane/global-scheduler/maintenance.ts +14 -7
  122. package/src/runtime/control-plane/global-scheduler/scheduler.ts +5 -1
  123. package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +13 -0
  124. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +15 -6
  125. package/src/runtime/control-plane/launcher/launch-reservation-store.ts +54 -5
  126. package/src/runtime/control-plane/launcher/thin-launcher.ts +56 -7
  127. package/src/runtime/control-plane/persistence/requirement-store.ts +0 -12
  128. package/src/runtime/control-plane/repo-actor/registry.ts +4 -2
  129. package/src/runtime/control-plane/routing/route-policy.ts +18 -6
  130. package/src/runtime/control-plane/routing/workspace-admission.ts +70 -0
  131. package/src/runtime/evidence/process-check-execution.ts +18 -0
  132. package/src/runtime/execution/jobs/child-reference.ts +6 -2
  133. package/src/runtime/execution/process-runtime/check-result.ts +53 -18
  134. package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +7 -1
  135. package/src/runtime/execution/process-runtime/command-facade.ts +39 -17
  136. package/src/runtime/execution/process-runtime/lightweight-managed.ts +20 -3
  137. package/src/runtime/execution/process-runtime/persisted-check.ts +4 -1
  138. package/src/runtime/execution/process-runtime/runtime.ts +10 -0
  139. package/src/runtime/execution/process-runtime/types.ts +2 -12
  140. package/src/runtime/execution/work-activity.ts +21 -0
  141. package/src/runtime/execution/workers/executor.ts +18 -13
  142. package/src/runtime/gateway/mcp/execution-tools.ts +35 -2930
  143. package/src/runtime/gateway/mcp/legacy-ios-tool-adapter.ts +61 -0
  144. package/src/runtime/gateway/mcp/router.ts +2 -2
  145. package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +21 -2
  146. package/src/runtime/gateway/mcp/runtime-tools.ts +815 -1015
  147. package/src/runtime/plugins/app-store-connect-adapter.ts +93 -28
  148. package/src/runtime/plugins/browser-adapter.ts +256 -1083
  149. package/src/runtime/plugins/browser-handoff-host.ts +2 -25
  150. package/src/runtime/plugins/browser-handoff.ts +18 -3
  151. package/src/runtime/plugins/browser-macos-bridge.ts +121 -16
  152. package/src/runtime/plugins/browser-manifest-surface.ts +603 -0
  153. package/src/runtime/plugins/browser-native-session-service.ts +222 -0
  154. package/src/runtime/plugins/browser-runtime-contract.ts +2 -0
  155. package/src/runtime/plugins/browser-runtime.ts +10 -0
  156. package/src/runtime/plugins/browser-session-store.ts +121 -0
  157. package/src/runtime/plugins/browser-session-types.ts +98 -0
  158. package/src/runtime/plugins/desktop-operator-registration.ts +4 -4
  159. package/src/runtime/plugins/external-adapter.ts +92 -58
  160. package/src/runtime/plugins/macos-capability-broker.ts +0 -8
  161. package/src/runtime/plugins/store.ts +66 -9
  162. package/src/runtime/plugins/types.ts +3 -1
  163. package/src/runtime/recovery/maintenance-executor.ts +61 -25
  164. package/src/runtime/root/package-connector-service.ts +46 -4
  165. package/src/runtime/root/package-runtime-release.ts +3 -2
  166. package/src/runtime/root/package-runtime-service.ts +48 -3
  167. package/src/runtime/root/release-manifest.ts +137 -16
  168. package/src/runtime/root/release-materialize.ts +109 -35
  169. package/src/runtime/root/types.ts +10 -0
  170. package/src/runtime/standalone-recovery/core.ts +425 -194
  171. package/src/runtime/standalone-recovery/entry.ts +1 -1
  172. package/src/runtime/standalone-recovery/installer.ts +1 -1
  173. package/src/runtime/workflow/schedules/engine.ts +113 -87
  174. package/src/runtime/workflow/schedules/settlement.ts +26 -23
  175. package/src/runtime/workflow/schedules/store.ts +79 -10
@@ -1,465 +1,4 @@
1
1
  #!/usr/bin/env bun
2
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
3
- import { dirname, isAbsolute, join, relative, resolve } from "path";
4
- import { spawnSync } from "child_process";
2
+ import { runTrackedForgeHelper } from "./lib/run-forge-helper";
5
3
 
6
- type Mode = "dry-run" | "run";
7
-
8
- interface Options {
9
- mode: Mode;
10
- repo: string;
11
- contract: string;
12
- workerCommand?: string;
13
- verifierCommand?: string;
14
- out?: string;
15
- json: boolean;
16
- maxToolCalls?: number;
17
- }
18
-
19
- interface DelegationBudget {
20
- tokens: number | null;
21
- tool_calls: number | null;
22
- wall_time_minutes: number | null;
23
- }
24
-
25
- interface DelegationContract {
26
- budget: DelegationBudget;
27
- permission_scope: {
28
- mode: string;
29
- writable_paths: string[];
30
- network: string;
31
- };
32
- roles: Record<string, DelegationRole>;
33
- }
34
-
35
- interface DelegationRole {
36
- mode: string;
37
- purpose: string;
38
- }
39
-
40
- interface ChildResult {
41
- role: "worker" | "verifier";
42
- command: string;
43
- exit_code: number | null;
44
- stdout_path: string;
45
- stderr_path: string;
46
- skipped?: boolean;
47
- }
48
-
49
- function usage(): string {
50
- return [
51
- "Usage:",
52
- " bun scripts/contract-run.ts dry-run --contract <contract-file> [--repo <path>] [--out <dir>] [--json]",
53
- " bun scripts/contract-run.ts run --contract <contract-file> --worker-command <cmd> --verifier-command <cmd> [--repo <path>] [--out <dir>] [--max-tool-calls <n>] [--json]",
54
- ].join("\n");
55
- }
56
-
57
- function parseArgs(argv: string[]): Options {
58
- let mode: Mode = "dry-run";
59
- let index = 0;
60
- if (argv[0] === "run" || argv[0] === "dry-run") {
61
- mode = argv[0];
62
- index = 1;
63
- }
64
-
65
- const opts: Options = {
66
- mode,
67
- repo: process.cwd(),
68
- contract: "",
69
- json: false,
70
- };
71
-
72
- while (index < argv.length) {
73
- const arg = argv[index];
74
- switch (arg) {
75
- case "--repo":
76
- opts.repo = requireValue(argv, ++index, arg);
77
- index++;
78
- break;
79
- case "--contract":
80
- opts.contract = requireValue(argv, ++index, arg);
81
- index++;
82
- break;
83
- case "--worker-command":
84
- opts.workerCommand = requireValue(argv, ++index, arg);
85
- index++;
86
- break;
87
- case "--verifier-command":
88
- opts.verifierCommand = requireValue(argv, ++index, arg);
89
- index++;
90
- break;
91
- case "--out":
92
- opts.out = requireValue(argv, ++index, arg);
93
- index++;
94
- break;
95
- case "--max-tool-calls":
96
- opts.maxToolCalls = parsePositiveInt(requireValue(argv, ++index, arg), arg);
97
- index++;
98
- break;
99
- case "--json":
100
- opts.json = true;
101
- index++;
102
- break;
103
- case "--help":
104
- case "-h":
105
- console.log(usage());
106
- process.exit(0);
107
- default:
108
- throw new CliError(`contract-run: unknown argument ${arg}`, 2);
109
- }
110
- }
111
-
112
- if (!opts.contract) {
113
- throw new CliError("contract-run: --contract is required", 2);
114
- }
115
- if (opts.mode === "run" && (!opts.workerCommand || !opts.verifierCommand)) {
116
- throw new CliError("contract-run: run requires --worker-command and --verifier-command", 2);
117
- }
118
- return opts;
119
- }
120
-
121
- function requireValue(argv: string[], index: number, flag: string): string {
122
- const value = argv[index];
123
- if (!value || value.startsWith("--")) {
124
- throw new CliError(`contract-run: ${flag} requires a value`, 2);
125
- }
126
- return value;
127
- }
128
-
129
- function parsePositiveInt(value: string, flag: string): number {
130
- const parsed = Number(value);
131
- if (!Number.isInteger(parsed) || parsed < 1) {
132
- throw new CliError(`contract-run: ${flag} must be a positive integer`, 2);
133
- }
134
- return parsed;
135
- }
136
-
137
- class CliError extends Error {
138
- constructor(message: string, readonly exitCode: number) {
139
- super(message);
140
- }
141
- }
142
-
143
- function repoPath(repo: string, path: string): string {
144
- return isAbsolute(path) ? path : join(repo, path);
145
- }
146
-
147
- function repoRelative(repo: string, path: string): string {
148
- const rel = relative(repo, path);
149
- return rel && !rel.startsWith("..") ? rel : path;
150
- }
151
-
152
- function stripTicks(value: string): string {
153
- return value.trim().replace(/^`/, "").replace(/`$/, "");
154
- }
155
-
156
- function readHeader(markdown: string, name: string): string {
157
- const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
158
- const match = markdown.match(new RegExp(`^> \\*\\*${escaped}\\*\\*:\\s*(.+)$`, "m"));
159
- return match ? stripTicks(match[1]) : "";
160
- }
161
-
162
- function fencedYamlBlock(markdown: string, key: string): string {
163
- const fence = /```yaml\s*\n([\s\S]*?)\n```/g;
164
- let match: RegExpExecArray | null;
165
- while ((match = fence.exec(markdown)) !== null) {
166
- if (new RegExp(`(^|\\n)${key}:`).test(match[1])) {
167
- return match[1];
168
- }
169
- }
170
- return "";
171
- }
172
-
173
- function parseScalar(block: string, key: string): string | null {
174
- const match = block.match(new RegExp(`^\\s*${key}:\\s*(.+)$`, "m"));
175
- if (!match) return null;
176
- const value = match[1].trim();
177
- return value === "null" ? null : value.replace(/^["']|["']$/g, "");
178
- }
179
-
180
- function parseNullableNumber(block: string, key: string): number | null {
181
- const value = parseScalar(block, key);
182
- if (value === null || value === "null") return null;
183
- const parsed = Number(value);
184
- return Number.isFinite(parsed) ? parsed : null;
185
- }
186
-
187
- function parseList(block: string, key: string): string[] {
188
- const lines = block.split("\n");
189
- const values: string[] = [];
190
- let inList = false;
191
- const keyPattern = new RegExp(`^\\s*${key}:\\s*$`);
192
- for (const line of lines) {
193
- if (keyPattern.test(line)) {
194
- inList = true;
195
- continue;
196
- }
197
- if (!inList) continue;
198
- if (/^\S/.test(line) || /^\s+[a-zA-Z0-9_-]+:/.test(line)) break;
199
- const match = line.match(/^\s*-\s*(.+)$/);
200
- if (match) values.push(match[1].trim().replace(/^["']|["']$/g, ""));
201
- }
202
- return values;
203
- }
204
-
205
- function parseRoles(block: string): Record<string, DelegationRole> {
206
- const roles: Record<string, DelegationRole> = {};
207
- let inRoles = false;
208
- let currentRole = "";
209
- for (const line of block.split("\n")) {
210
- if (/^\s*roles:\s*$/.test(line)) {
211
- inRoles = true;
212
- continue;
213
- }
214
- if (!inRoles) continue;
215
- if (/^\S/.test(line)) break;
216
- const scalar = line.match(/^\s{4}([a-zA-Z0-9_-]+):\s*(.+)$/);
217
- if (scalar) {
218
- roles[scalar[1]] = { mode: scalar[2].trim(), purpose: scalar[2].trim() };
219
- currentRole = scalar[1];
220
- continue;
221
- }
222
- const nested = line.match(/^\s{4}([a-zA-Z0-9_-]+):\s*$/);
223
- if (nested) {
224
- currentRole = nested[1];
225
- roles[currentRole] = roles[currentRole] ?? { mode: "", purpose: "" };
226
- continue;
227
- }
228
- const field = line.match(/^\s{6}(mode|purpose):\s*(.+)$/);
229
- if (field && currentRole) {
230
- roles[currentRole] = {
231
- ...roles[currentRole],
232
- [field[1]]: field[2].trim().replace(/^["']|["']$/g, ""),
233
- };
234
- }
235
- }
236
- return roles;
237
- }
238
-
239
- function parseDelegation(markdown: string): DelegationContract {
240
- const block = fencedYamlBlock(markdown, "delegation");
241
- return {
242
- budget: {
243
- tokens: parseNullableNumber(block, "tokens"),
244
- tool_calls: parseNullableNumber(block, "tool_calls"),
245
- wall_time_minutes: parseNullableNumber(block, "wall_time_minutes"),
246
- },
247
- permission_scope: {
248
- mode: parseScalar(block, "mode") ?? "inherit_allowed_paths",
249
- writable_paths: parseList(block, "writable_paths"),
250
- network: parseScalar(block, "network") ?? "inherited",
251
- },
252
- roles: {
253
- parent: { mode: "narrate_and_gatekeep", purpose: "approval_checkpoint_owner" },
254
- explorer: { mode: "read_only", purpose: "codebase_research" },
255
- worker: { mode: "edit_within_allowed_paths", purpose: "implementation" },
256
- verifier: { mode: "read_only", purpose: "exit_criteria_review" },
257
- ...parseRoles(block),
258
- },
259
- };
260
- }
261
-
262
- function runChild(
263
- role: "worker" | "verifier",
264
- command: string,
265
- repo: string,
266
- runDir: string,
267
- env: NodeJS.ProcessEnv,
268
- ): ChildResult {
269
- const stdoutPath = join(runDir, `${role}.stdout.log`);
270
- const stderrPath = join(runDir, `${role}.stderr.log`);
271
- const result = spawnSync(command, {
272
- cwd: repo,
273
- shell: true,
274
- encoding: "utf-8",
275
- env: {
276
- ...process.env,
277
- ...env,
278
- CONTRACT_RUN_ROLE: role,
279
- },
280
- });
281
- writeFileSync(stdoutPath, result.stdout ?? "");
282
- writeFileSync(stderrPath, result.stderr ?? "");
283
- return {
284
- role,
285
- command,
286
- exit_code: result.status,
287
- stdout_path: repoRelative(repo, stdoutPath),
288
- stderr_path: repoRelative(repo, stderrPath),
289
- };
290
- }
291
-
292
- function writePrompt(path: string, title: string, lines: string[]) {
293
- writeFileSync(path, [`# ${title}`, "", ...lines, ""].join("\n"));
294
- }
295
-
296
- function buildRun(opts: Options) {
297
- const repo = resolve(opts.repo);
298
- const contractPath = repoPath(repo, opts.contract);
299
- if (!existsSync(contractPath)) {
300
- throw new CliError(`contract-run: contract not found: ${opts.contract}`, 2);
301
- }
302
-
303
- const contractText = readFileSync(contractPath, "utf-8");
304
- const plan = readHeader(contractText, "Plan");
305
- const reviewFile = readHeader(contractText, "Review File");
306
- const notesFile = readHeader(contractText, "Notes File");
307
- const exitCriteria = fencedYamlBlock(contractText, "exit_criteria");
308
- const delegation = parseDelegation(contractText);
309
- const allowedPaths = parseList(fencedYamlBlock(contractText, "allowed_paths"), "allowed_paths");
310
- const toolLimit = opts.maxToolCalls ?? delegation.budget.tool_calls;
311
- const slug = contractPath
312
- .split("/")
313
- .pop()!
314
- .replace(/\.contract\.md$/, "")
315
- .replace(/[^a-zA-Z0-9_-]+/g, "-");
316
- const runDir = repoPath(
317
- repo,
318
- opts.out ?? `.ai/harness/runs/contract-run-${new Date().toISOString().replace(/[-:]/g, "").replace(/\..+$/, "")}-${slug}`,
319
- );
320
- mkdirSync(runDir, { recursive: true });
321
-
322
- const workerPrompt = join(runDir, "worker-prompt.md");
323
- const verifierPrompt = join(runDir, "verifier-prompt.md");
324
- writePrompt(workerPrompt, "Contract Worker Task", [
325
- `Contract: ${repoRelative(repo, contractPath)}`,
326
- `Plan: ${plan || "(none)"}`,
327
- `Notes: ${notesFile || "(none)"}`,
328
- `Role mode: ${delegation.roles.worker?.mode ?? "edit_within_allowed_paths"}`,
329
- `Role purpose: ${delegation.roles.worker?.purpose ?? "implementation"}`,
330
- `Permission scope: ${delegation.permission_scope.mode}`,
331
- `Writable paths: ${(delegation.permission_scope.writable_paths.length ? delegation.permission_scope.writable_paths : allowedPaths).join(", ") || "(none)"}`,
332
- "",
333
- "Implement only the contract scope. Do not mark the task done; the verifier owns review.",
334
- "",
335
- "## Contract",
336
- contractText,
337
- ]);
338
- writePrompt(verifierPrompt, "Contract Verifier Task", [
339
- `Contract: ${repoRelative(repo, contractPath)}`,
340
- `Review file: ${reviewFile || "(none)"}`,
341
- `Role mode: ${delegation.roles.verifier?.mode ?? "read_only"}`,
342
- `Role purpose: ${delegation.roles.verifier?.purpose ?? "exit_criteria_review"}`,
343
- "",
344
- "Review only against the contract exit criteria. Do not invent another rubric.",
345
- "",
346
- "## Exit Criteria",
347
- exitCriteria || "(none)",
348
- ]);
349
-
350
- const children: ChildResult[] = [];
351
- let toolCalls = 0;
352
- let status: "dry_run" | "pass" | "fail" = opts.mode === "dry-run" ? "dry_run" : "pass";
353
- let failureClass = "";
354
-
355
- const manifestPath = join(runDir, "manifest.json");
356
- const baseEnv = {
357
- CONTRACT_RUN_CONTRACT: repoRelative(repo, contractPath),
358
- CONTRACT_RUN_PLAN: plan,
359
- CONTRACT_RUN_REVIEW: reviewFile,
360
- CONTRACT_RUN_NOTES: notesFile,
361
- CONTRACT_RUN_DIR: repoRelative(repo, runDir),
362
- CONTRACT_RUN_WORKER_PROMPT: repoRelative(repo, workerPrompt),
363
- CONTRACT_RUN_VERIFIER_PROMPT: repoRelative(repo, verifierPrompt),
364
- CONTRACT_RUN_ALLOWED_PATHS: allowedPaths.join("\n"),
365
- CONTRACT_RUN_VERIFIER_RUBRIC: exitCriteria,
366
- };
367
-
368
- const consume = (role: "worker" | "verifier") => {
369
- if (toolLimit !== null && toolCalls + 1 > toolLimit) {
370
- status = "fail";
371
- failureClass = "budget_exceeded";
372
- children.push({
373
- role,
374
- command: role === "worker" ? opts.workerCommand ?? "" : opts.verifierCommand ?? "",
375
- exit_code: null,
376
- stdout_path: "",
377
- stderr_path: "",
378
- skipped: true,
379
- });
380
- return false;
381
- }
382
- toolCalls++;
383
- return true;
384
- };
385
-
386
- if (opts.mode === "run") {
387
- if (consume("worker")) {
388
- const worker = runChild("worker", opts.workerCommand!, repo, runDir, {
389
- ...baseEnv,
390
- CONTRACT_RUN_PROMPT: baseEnv.CONTRACT_RUN_WORKER_PROMPT,
391
- });
392
- children.push(worker);
393
- if (worker.exit_code !== 0) {
394
- status = "fail";
395
- failureClass = "worker_failed";
396
- }
397
- }
398
- if (status === "pass" && consume("verifier")) {
399
- const verifier = runChild("verifier", opts.verifierCommand!, repo, runDir, {
400
- ...baseEnv,
401
- CONTRACT_RUN_PROMPT: baseEnv.CONTRACT_RUN_VERIFIER_PROMPT,
402
- });
403
- children.push(verifier);
404
- if (verifier.exit_code !== 0) {
405
- status = "fail";
406
- failureClass = "verifier_failed";
407
- } else if (reviewFile && !existsSync(repoPath(repo, reviewFile))) {
408
- status = "fail";
409
- failureClass = "missing_review";
410
- }
411
- }
412
- }
413
-
414
- const manifest = {
415
- version: 1,
416
- kind: "forge-contract-run",
417
- status,
418
- failure_class: failureClass || null,
419
- repo,
420
- contract: repoRelative(repo, contractPath),
421
- plan,
422
- review_file: reviewFile,
423
- notes_file: notesFile,
424
- run_dir: repoRelative(repo, runDir),
425
- prompts: {
426
- worker: repoRelative(repo, workerPrompt),
427
- verifier: repoRelative(repo, verifierPrompt),
428
- },
429
- delegation,
430
- delegation_plan: {
431
- parent_owner: delegation.roles.parent,
432
- explorer: delegation.roles.explorer,
433
- worker: delegation.roles.worker,
434
- verifier: delegation.roles.verifier,
435
- allowed_paths: allowedPaths,
436
- verifier_rubric: "contract exit_criteria",
437
- budget_semantics: "null uses session default; explicit numbers are enforced where the runner supports that dimension",
438
- permission_semantics: "explorer and verifier are read-only; worker is constrained to allowed_paths or narrower writable_paths",
439
- },
440
- budget_usage: {
441
- tool_calls: toolCalls,
442
- tool_call_limit: toolLimit,
443
- },
444
- children,
445
- };
446
- writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + "\n");
447
- return { manifest, manifestPath };
448
- }
449
-
450
- try {
451
- const opts = parseArgs(process.argv.slice(2));
452
- const { manifest, manifestPath } = buildRun(opts);
453
- if (opts.json) {
454
- console.log(JSON.stringify(manifest, null, 2));
455
- } else {
456
- console.log(`[ContractRun] ${manifest.status}: ${manifest.contract}`);
457
- console.log(`[ContractRun] manifest: ${repoRelative(resolve(opts.repo), manifestPath)}`);
458
- if (manifest.failure_class) console.log(`[ContractRun] failure_class: ${manifest.failure_class}`);
459
- }
460
- process.exit(manifest.status === "fail" ? 1 : 0);
461
- } catch (err) {
462
- const error = err as Error & { exitCode?: number };
463
- console.error(error.message);
464
- process.exit(error.exitCode ?? 1);
465
- }
4
+ if (import.meta.main) runTrackedForgeHelper("contract-run.ts");