@uchibeke/gallivant 0.0.0-stage → 0.2.1

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 (168) hide show
  1. package/CONTRIBUTING.md +26 -0
  2. package/LICENSE +204 -0
  3. package/README.md +239 -2
  4. package/SECURITY.md +19 -0
  5. package/bin/gallivant.mjs +261 -0
  6. package/bin/package-plugin.mjs +16 -0
  7. package/bin/release-live.mjs +29 -0
  8. package/bin/release-package.mjs +23 -0
  9. package/bin/session-runtime.mjs +13 -0
  10. package/bin/task-delivery-state.mjs +12 -0
  11. package/docs/README.md +72 -0
  12. package/docs/agent-setup.md +228 -0
  13. package/docs/app.md +111 -0
  14. package/docs/architecture.md +265 -0
  15. package/docs/chains.md +102 -0
  16. package/docs/codex-cloud.md +284 -0
  17. package/docs/commands.md +649 -0
  18. package/docs/config.md +437 -0
  19. package/docs/execution.md +796 -0
  20. package/docs/guardrails.md +311 -0
  21. package/docs/install.md +398 -0
  22. package/docs/launch.md +45 -0
  23. package/docs/learning.md +369 -0
  24. package/docs/network-recovery.md +101 -0
  25. package/docs/openai-agents.md +457 -0
  26. package/docs/prd.md +246 -0
  27. package/docs/preparation.md +110 -0
  28. package/docs/release-qualification.md +196 -0
  29. package/docs/releasing.md +149 -0
  30. package/docs/runtime-environment.md +113 -0
  31. package/docs/task-delivery.md +476 -0
  32. package/docs/tasks.md +739 -0
  33. package/lib/aport.mjs +95 -0
  34. package/lib/attribution.mjs +95 -0
  35. package/lib/block.mjs +89 -0
  36. package/lib/capabilities.mjs +3 -0
  37. package/lib/chains.mjs +222 -0
  38. package/lib/codex_cloud.mjs +160 -0
  39. package/lib/codex_cloud_config.mjs +20 -0
  40. package/lib/codex_cloud_input.mjs +49 -0
  41. package/lib/codex_cloud_result.mjs +74 -0
  42. package/lib/commands/doctor.mjs +466 -0
  43. package/lib/commands/init.mjs +245 -0
  44. package/lib/commands/learn.mjs +54 -0
  45. package/lib/commands/new.mjs +47 -0
  46. package/lib/commands/ready.mjs +45 -0
  47. package/lib/commands/register.mjs +249 -0
  48. package/lib/commands/resume.mjs +224 -0
  49. package/lib/commands/serve.mjs +349 -0
  50. package/lib/commands/start.mjs +247 -0
  51. package/lib/commands/start_services.mjs +22 -0
  52. package/lib/commands/status.mjs +150 -0
  53. package/lib/commands/task.mjs +73 -0
  54. package/lib/commands/up.mjs +228 -0
  55. package/lib/commands/why.mjs +143 -0
  56. package/lib/config.mjs +461 -0
  57. package/lib/context.mjs +102 -0
  58. package/lib/control.mjs +202 -0
  59. package/lib/control_contract.mjs +16 -0
  60. package/lib/coordinator.mjs +19 -0
  61. package/lib/credential.mjs +161 -0
  62. package/lib/daemon.mjs +156 -0
  63. package/lib/deliverable.mjs +357 -0
  64. package/lib/events.mjs +261 -0
  65. package/lib/executable.mjs +31 -0
  66. package/lib/execution.mjs +707 -0
  67. package/lib/execution_contract.mjs +15 -0
  68. package/lib/execution_input.mjs +4 -0
  69. package/lib/execution_io.mjs +94 -0
  70. package/lib/execution_journal.mjs +69 -0
  71. package/lib/execution_preparation.mjs +29 -0
  72. package/lib/execution_result.mjs +266 -0
  73. package/lib/fs.mjs +140 -0
  74. package/lib/gh.mjs +289 -0
  75. package/lib/git.mjs +434 -0
  76. package/lib/harness.mjs +521 -0
  77. package/lib/harness_log.mjs +432 -0
  78. package/lib/hash.mjs +16 -0
  79. package/lib/identity.mjs +98 -0
  80. package/lib/learning.mjs +654 -0
  81. package/lib/learning_contract.mjs +2 -0
  82. package/lib/learning_data.mjs +135 -0
  83. package/lib/learning_input.mjs +111 -0
  84. package/lib/learning_qualification.mjs +67 -0
  85. package/lib/learning_signer.mjs +151 -0
  86. package/lib/learning_store.mjs +88 -0
  87. package/lib/log.mjs +53 -0
  88. package/lib/network_diagnostics.mjs +64 -0
  89. package/lib/network_recovery.mjs +42 -0
  90. package/lib/notify.mjs +58 -0
  91. package/lib/openai_agents.mjs +373 -0
  92. package/lib/openai_agents_config.mjs +14 -0
  93. package/lib/openai_agents_http.mjs +87 -0
  94. package/lib/openai_agents_input.mjs +15 -0
  95. package/lib/openai_agents_sources.mjs +112 -0
  96. package/lib/owner_delivery.mjs +443 -0
  97. package/lib/plugin_package.mjs +340 -0
  98. package/lib/prd.mjs +635 -0
  99. package/lib/preparation_agents.mjs +33 -0
  100. package/lib/preparation_codex.mjs +212 -0
  101. package/lib/probe.mjs +126 -0
  102. package/lib/prompt.mjs +238 -0
  103. package/lib/provider_health.mjs +55 -0
  104. package/lib/public-format.mjs +31 -0
  105. package/lib/public_output.mjs +49 -0
  106. package/lib/release_package.mjs +154 -0
  107. package/lib/repository_files.mjs +86 -0
  108. package/lib/run.mjs +385 -0
  109. package/lib/run_log.mjs +94 -0
  110. package/lib/runtime-environment.mjs +87 -0
  111. package/lib/runtime_dependencies.mjs +15 -0
  112. package/lib/runtime_node.mjs +107 -0
  113. package/lib/runtime_state.mjs +174 -0
  114. package/lib/sources/index.mjs +27 -0
  115. package/lib/sources/prd.mjs +50 -0
  116. package/lib/start_contract.mjs +37 -0
  117. package/lib/state.mjs +246 -0
  118. package/lib/task_allowance.mjs +31 -0
  119. package/lib/task_completion.mjs +61 -0
  120. package/lib/task_contract.mjs +450 -0
  121. package/lib/task_delivery.mjs +219 -0
  122. package/lib/task_delivery_cancel.mjs +34 -0
  123. package/lib/task_delivery_contract.mjs +61 -0
  124. package/lib/task_delivery_mcp.mjs +40 -0
  125. package/lib/task_delivery_store.mjs +43 -0
  126. package/lib/task_delivery_view.mjs +88 -0
  127. package/lib/task_environment.mjs +281 -0
  128. package/lib/task_prepare.mjs +224 -0
  129. package/lib/task_store.mjs +38 -0
  130. package/lib/tasks.mjs +513 -0
  131. package/lib/tick.mjs +1654 -0
  132. package/lib/time.mjs +36 -0
  133. package/lib/tracker/assets.mjs +43 -0
  134. package/lib/tracker/pages.mjs +320 -0
  135. package/lib/tracker/runs.mjs +102 -0
  136. package/lib/tracker/stream.mjs +74 -0
  137. package/lib/tracker_process.mjs +107 -0
  138. package/lib/verification_sandbox.mjs +181 -0
  139. package/node_modules/smol-toml/LICENSE +24 -0
  140. package/node_modules/smol-toml/README.md +605 -0
  141. package/node_modules/smol-toml/dist/date.d.ts +41 -0
  142. package/node_modules/smol-toml/dist/date.js +156 -0
  143. package/node_modules/smol-toml/dist/error.d.ts +37 -0
  144. package/node_modules/smol-toml/dist/error.js +67 -0
  145. package/node_modules/smol-toml/dist/extract.js +289 -0
  146. package/node_modules/smol-toml/dist/index.cjs +770 -0
  147. package/node_modules/smol-toml/dist/index.d.ts +44 -0
  148. package/node_modules/smol-toml/dist/index.js +34 -0
  149. package/node_modules/smol-toml/dist/parse.d.ts +73 -0
  150. package/node_modules/smol-toml/dist/parse.js +164 -0
  151. package/node_modules/smol-toml/dist/primitive.js +151 -0
  152. package/node_modules/smol-toml/dist/stringify.d.ts +32 -0
  153. package/node_modules/smol-toml/dist/stringify.js +219 -0
  154. package/node_modules/smol-toml/dist/struct.js +156 -0
  155. package/node_modules/smol-toml/dist/util.d.ts +43 -0
  156. package/node_modules/smol-toml/dist/util.js +57 -0
  157. package/node_modules/smol-toml/package.json +68 -0
  158. package/npm-shrinkwrap.json +1823 -0
  159. package/package.json +105 -4
  160. package/templates/claude-settings.json +18 -0
  161. package/templates/passport.gallivant.json +83 -0
  162. package/templates/plugin/bundled-runtime.json +26 -0
  163. package/templates/plugin/schemas/mcp.schema.json +120 -0
  164. package/templates/plugin/schemas/plugin.schema.json +65 -0
  165. package/templates/plugin/task-delivery.SKILL.md +60 -0
  166. package/templates/prd.md +32 -0
  167. package/templates/tracker/tracker.css +301 -0
  168. package/templates/tracker/tracker.js +721 -0
@@ -0,0 +1,261 @@
1
+ #!/usr/bin/env node
2
+ // CLI entry. Parses arguments, builds the context, calls one command. No logic lives here.
3
+ import { acquireCliMutation } from "../lib/daemon.mjs";
4
+ import { parseArgs } from "node:util";
5
+ import { makeContext } from "../lib/context.mjs";
6
+ import { start } from "../lib/commands/start.mjs";
7
+ import { register } from "../lib/commands/register.mjs";
8
+ import { up, upInBackground, readDaemon, down } from "../lib/commands/up.mjs";
9
+ import { status } from "../lib/commands/status.mjs";
10
+ import { why } from "../lib/commands/why.mjs";
11
+ import { doctor, renderDoctor } from "../lib/commands/doctor.mjs";
12
+ import { diagnoseNetwork, renderNetworkDiagnostics } from "../lib/network_diagnostics.mjs";
13
+ import { ready } from "../lib/commands/ready.mjs";
14
+ import { newPrd } from "../lib/commands/new.mjs";
15
+ import { resume } from "../lib/commands/resume.mjs";
16
+ import { startServices } from "../lib/commands/start_services.mjs";
17
+ import { trackerPort, stopTracker } from "../lib/tracker_process.mjs";
18
+ import { init, renderInit } from "../lib/commands/init.mjs";
19
+ import { VERSION } from "../lib/fs.mjs";
20
+ import { hasConfig } from "../lib/config.mjs";
21
+ import { resolveHandle } from "../lib/state.mjs";
22
+ import { requestControl } from "../lib/control.mjs";
23
+ import { queueChain, retryChain, cancelChain, renderChains } from "../lib/chains.mjs";
24
+ import { taskCommand, renderTask } from "../lib/commands/task.mjs";
25
+ import { TASK_VERSION } from "../lib/tasks.mjs";
26
+ import { learnCommand } from "../lib/commands/learn.mjs";
27
+ import { safeDisplay } from "../lib/learning_data.mjs";
28
+
29
+ const HELP = `gallivant: give an agent a task, let the review loop run, go outside.
30
+
31
+ gallivant init [--harness codex|claude] [--worktrees <directory>]
32
+ set up the repository you are in: private config, this repo in repos,
33
+ then doctor's report. Safe to repeat; never signs in or overwrites settings
34
+ gallivant --version print the installed version
35
+ gallivant doctor [--send] [--probe] install checklist and health probe
36
+ gallivant doctor --network <https-origin> [--json] bounded DNS/HTTPS diagnosis
37
+ gallivant learn [--repo owner/repo] [--since YYYY-MM-DD] [--until YYYY-MM-DD] [--dry-run]
38
+ bounded historical feedback collection; independent coding cursor
39
+ gallivant learn doctor [--repo owner/repo]
40
+ gallivant learn setup --key-path <absolute-path> [--ssh-keygen /usr/bin/ssh-keygen]
41
+ explicit interactive encrypted-key enrollment; isolation required
42
+ gallivant learn configure --repo owner/repo --mode off|collect|apply
43
+ gallivant learn list|report --repo owner/repo [--status pending]
44
+ gallivant learn show|approve|retire|reject <id> --repo owner/repo --revision <SHA256> [--reason text]
45
+ gallivant learn revise <id> --repo owner/repo --from reviewed-rule.json
46
+ gallivant learn explain --repo owner/repo --run RUN_ID
47
+ gallivant learn recover|prune --repo owner/repo
48
+ gallivant learn extract --repo owner/repo --learning-budget <USD>
49
+ data-only qualified profile required; list/report never infer
50
+ gallivant new <name> [--harness <name>] [--dir <repo>]
51
+ write a starting PRD into docs/prds/ of the repo you are in
52
+ gallivant task [prepare] "<request>" [--stdin] [--dir <checkout>] [--repo <owner/repo>]
53
+ [--base <branch>] [--harness <name>] [--context <file>] [--request-id <id>] [--run] [--json]
54
+ [--completion-target delivery_ready|integrated] [--verify <command>] [--no-daemon]
55
+ prepare an isolated brief; --run authorizes its exact ready revision
56
+ Claude, Codex and OpenAI Agents support isolated preparation.
57
+ Other local harnesses use written PRDs; Codex Cloud dispatch is unavailable
58
+ gallivant task answer <task-id> --revision <n> --request-id <answer-id> --answer <question-id=text>
59
+ resume saved questions; --answer, --context and --verify may repeat
60
+ gallivant task inspect <task-id> [--json] | task list [--json]
61
+ gallivant task resume|validate|start <task-id> --revision <n> [--run] [--no-daemon] [--json]
62
+ shared versioned operations; start needs prior or --run authorization
63
+ gallivant ready <prd.md> [--check] check the PRD against the contract; flip it to ready when it passes
64
+ gallivant start [prd.md] [--no-daemon] [--no-server] [--no-open] [--port 8791] [--base <branch>]
65
+ start the daemon and dashboard, then open it; optionally start a ready PRD
66
+ --base sets the branch the PR targets and remembers it for this repository
67
+ gallivant register <pr-url> [--harness <name>] [--session <id>] [--worktree <dir>]
68
+ watch a hand-made PR; on an existing entry, reset it
69
+ gallivant chain <name> <prd1.md> <prd2.md> ... [--base <branch>]
70
+ queue ready PRDs; each starts after the previous PR is merged
71
+ gallivant chain [<name>] [--cancel | --retry [--resnapshot]]
72
+ inspect chains, stop future work, or retry after fixing a blocker
73
+ gallivant up [--once] the daemon: recover from GitHub, then tick every poll_seconds
74
+ gallivant down stop the daemon and standalone dashboard; active runs keep going
75
+ gallivant status [--all] [--json] needs-you, running, daemon health
76
+ gallivant serve [--port 8791] [--no-open] [--no-daemon] [--foreground]
77
+ same startup as start without a PRD; foreground keeps the tracker attached
78
+ gallivant why <handle> why a PR stopped; handle is <repo>#<pr>, a PRD path, or the id
79
+ gallivant resume <handle> [--note "..."] [--resnapshot] [--fresh-session]
80
+ put a stopped entry back to work in the session that was doing it;
81
+ --resnapshot makes an edited brief the one the gate judges
82
+ --fresh-session replaces retired learned memory after settlement
83
+ gallivant recheck <handle> queue a PR recheck without resetting its budget
84
+ gallivant cancel <handle> stop further dispatch; request provider cancellation
85
+ gallivant say <handle> --note "..." [--interrupt]
86
+ send an update in the same session; --interrupt stops the active run first
87
+ gallivant remove <handle> forget an entry; the PR, the branch and the checkout are left alone
88
+
89
+ Config: $GALLIVANT_HOME/config.json (default ~/.gallivant). Run doctor for the file to write.`;
90
+
91
+ const OPTIONS = {
92
+ network: { type: "string" },
93
+ check: { type: "boolean" }, once: { type: "boolean" }, all: { type: "boolean" }, json: { type: "boolean" },
94
+ send: { type: "boolean" }, probe: { type: "boolean" }, help: { type: "boolean", short: "h" }, version: { type: "boolean", short: "v" },
95
+ "no-server": { type: "boolean" }, "no-open": { type: "boolean" }, foreground: { type: "boolean" }, worktrees: { type: "string" },
96
+ "no-daemon": { type: "boolean" }, note: { type: "string" }, resnapshot: { type: "boolean" }, "fresh-session": { type: "boolean" }, interrupt: { type: "boolean" },
97
+ base: { type: "string" }, harness: { type: "string" }, session: { type: "string" }, worktree: { type: "string" }, dir: { type: "string" }, port: { type: "string" },
98
+ cancel: { type: "boolean" }, retry: { type: "boolean" },
99
+ run: { type: "boolean" }, stdin: { type: "boolean" }, repo: { type: "string" }, revision: { type: "string" },
100
+ "request-id": { type: "string" }, context: { type: "string", multiple: true },
101
+ "completion-target": { type: "string" },
102
+ answer: { type: "string", multiple: true }, verify: { type: "string", multiple: true },
103
+ since: { type: "string" }, until: { type: "string" }, "dry-run": { type: "boolean" },
104
+ mode: { type: "string" }, status: { type: "string" }, from: { type: "string" }, reason: { type: "string" },
105
+ "key-path": { type: "string" }, "ssh-keygen": { type: "string" }, "learning-budget": { type: "string" },
106
+ };
107
+
108
+ async function dispatch(argv) {
109
+ const options = argv[0] === "learn" ? { ...OPTIONS, run: { type: "string" } } : OPTIONS;
110
+ const { values, positionals } = parseArgs({ args: argv, options, allowPositionals: true });
111
+ const [cmd, arg] = positionals;
112
+ if (values.version) { console.log(VERSION); return 0; }
113
+ if (values.help || !cmd || cmd === "help") { console.log(HELP); return 0; }
114
+ if (cmd === "init") {
115
+ if (arg) throw new Error("init takes no positional argument; run it from inside the target checkout");
116
+ const r = await init({ harness: values.harness, worktrees: values.worktrees });
117
+ const report = renderDoctor(r.doctor);
118
+ console.log(renderInit(r, report));
119
+ // Doctor's exit code, so a script or an agent reading only the status knows whether setup still has open items.
120
+ return report.exitCode;
121
+ }
122
+ if (values.foreground && cmd !== "serve") throw new Error("--foreground is available only with serve");
123
+ if (values.foreground && values["no-server"]) throw new Error("--foreground cannot be combined with --no-server");
124
+ // Validate before a PRD can dispatch paid work.
125
+ const serviceOptions = { port: ["start", "serve"].includes(cmd) ? trackerPort(values.port) : undefined,
126
+ noDaemon: values["no-daemon"], noServer: values["no-server"], noOpen: values["no-open"], foreground: values.foreground };
127
+ if (cmd === "learn") {
128
+ const ctx = makeContext({ needConfig: arg !== "setup" && (arg !== "doctor" || !!values.repo), readOnly: !!values["dry-run"] || ["list", "show", "report", "explain", "doctor"].includes(arg) });
129
+ const owner = !values["dry-run"] && !["list", "show", "report", "explain", "doctor", "setup"].includes(arg) ? acquireCliMutation(ctx.home) : null;
130
+ let result;
131
+ try { result = await learnCommand(ctx, { args: arg ? positionals.slice(1) : [], values }); }
132
+ finally { owner?.release(); }
133
+ process.stdout.write(safeDisplay(result) + "\n");
134
+ return result?.isolation?.qualified === false ? 1 : 0;
135
+ }
136
+
137
+ if (values.network && cmd !== "doctor") throw new Error("--network is available only with doctor");
138
+ if (cmd === "doctor" && values.network) {
139
+ const result = await diagnoseNetwork(values.network);
140
+ console.log(values.json ? JSON.stringify(result, null, 2) : renderNetworkDiagnostics(result));
141
+ return result.ok ? 0 : 1;
142
+ }
143
+ if (cmd === "doctor") { const r = renderDoctor(await doctor({ send: values.send, probe: values.probe })); console.log(r.text); return r.exitCode; }
144
+ if (cmd === "ready") {
145
+ if (!arg) throw new Error("ready needs a PRD path");
146
+ // A check only reads the brief and its sources. Recording readiness uses optional config; invalid config
147
+ // remains an error for that operation rather than silently selecting a local decision.
148
+ let readyCtx = {};
149
+ if (!values.check && hasConfig()) { const c = makeContext(); readyCtx = { config: c.config, apiKey: c.aportApiKey, log: c.log }; }
150
+ const r = await ready({ path: arg, checkOnly: values.check, ...readyCtx, onProgress: message => console.error(message) });
151
+ console.log(r.report);
152
+ return r.ok ? 0 : 1;
153
+ }
154
+
155
+ if (cmd === "task") {
156
+ try {
157
+ const ctx = makeContext();
158
+ const r = await taskCommand(ctx, { args: positionals.slice(1), values });
159
+ console.log(values.json ? JSON.stringify(r, null, 2) : renderTask(r));
160
+ return r.status === "blocked" || r.daemonError ? 1 : 0;
161
+ } catch (e) {
162
+ if (!values.json) throw e;
163
+ console.log(JSON.stringify({ version: TASK_VERSION, error: { code: e.code || "TASK_FAILED", message: e.message } }));
164
+ return 1;
165
+ }
166
+ }
167
+ const ctx = makeContext();
168
+ const mutating = ["start", "resume", "register", "remove", "recheck", "say", "cancel"].includes(cmd) || cmd === "chain" && (positionals.length > 2 || values.cancel || values.retry);
169
+ const owner = mutating ? acquireCliMutation(ctx.home) : null;
170
+ try {
171
+ if (cmd === "recheck" || cmd === "say" || cmd === "cancel") {
172
+ if (!arg) throw new Error(`${cmd} needs a handle`);
173
+ if (cmd === "say" && !values.note?.trim()) throw new Error("say needs --note with a message");
174
+ const r = requestControl(ctx, { handle: arg, action: cmd === "say" ? "message" : cmd, note: values.note, interrupt: values.interrupt });
175
+ console.log(`${r.entry}: ${r.action} queued; ${cmd === "cancel" ? "dispatch is fenced; the daemon will request and confirm cancellation" : r.interrupt ? "the daemon will stop the active run and resume its session with your message" : "it will wait for any active run to finish"}.`);
176
+ if (!values["no-daemon"]) {
177
+ try { await upInBackground(ctx); }
178
+ catch (e) { console.error(`Request saved, but the daemon could not start: ${e.message}\nAfter fixing the daemon, the saved request will be picked up; do not resend it.`); return 1; }
179
+ }
180
+ return 0;
181
+ }
182
+ switch (cmd) {
183
+ case "chain": {
184
+ const paths = positionals.slice(2);
185
+ const operation = paths.length > 0 || values.retry;
186
+ if (values.cancel && (values.retry || values.resnapshot || paths.length)) throw new Error("--cancel cannot be combined with a new plan or --retry");
187
+ if (values.resnapshot && !values.retry) throw new Error("chain --resnapshot requires --retry");
188
+ if (values.retry && paths.length) throw new Error("--retry uses the existing plan; do not pass PRD paths");
189
+ if ((values.cancel || values.retry) && !arg) throw new Error("a chain name is required");
190
+ if (values.base && !paths.length) throw new Error("--base is set when queuing a new chain");
191
+ if (paths.length) await queueChain(ctx, { name: arg, paths, base: values.base });
192
+ else if (values.cancel) cancelChain(ctx, arg);
193
+ else if (values.retry) await retryChain(ctx, { name: arg, resnapshot: values.resnapshot });
194
+ console.log(renderChains(ctx, arg));
195
+ if (operation && !values["no-daemon"]) {
196
+ try { await upInBackground(ctx); }
197
+ catch (e) { console.error(`Chain saved, but the daemon could not start: ${e.message}\nRun gallivant up after fixing it; do not queue the chain again.`); return 1; }
198
+ } else if (operation) console.log("Saved. Run gallivant up to dispatch the chain.");
199
+ return 0;
200
+ }
201
+ case "start":
202
+ case "serve": {
203
+ if (cmd === "serve" && arg) throw new Error("serve does not take a PRD; use gallivant start <prd.md>");
204
+ if (cmd === "start" && arg) {
205
+ const r = await start(ctx, { path: arg, base: values.base });
206
+ console.log(`${r.id}: started in ${r.worktree} on ${r.branch} -> ${r.base} (run ${r.run})`);
207
+ if (r.baseSource === "flag") console.log(`Base branch ${r.base} remembered for this repository; later starts here need no --base.`);
208
+ else if (r.baseSource === "config") console.log(`Base branch ${r.base} comes from repos in config.json.`);
209
+ }
210
+ const services = await startServices(ctx, serviceOptions);
211
+ for (const message of services.messages) console.log(message);
212
+ for (const error of services.errors) console.error(error);
213
+ return services.errors.length ? 1 : 0;
214
+ }
215
+ case "register": { if (!arg) throw new Error("register needs a PR URL"); const r = await register(ctx, { url: arg, harness: values.harness, session: values.session, worktree: values.worktree }); console.log(`${r.id}: watching PR #${r.pr} with ${r.harness}`); return 0; }
216
+ case "up": await up(ctx, { once: values.once }); return 0;
217
+ case "down": {
218
+ const tracker = stopTracker(ctx);
219
+ const r = down(ctx);
220
+ if (tracker.stopped) console.log(`Tracker ${tracker.pid} stopping.`);
221
+ if (!r.stopped && !tracker.stopped) { console.error(`${r.why}; ${tracker.why}`); return 1; }
222
+ if (r.stopped) console.log(`Daemon ${r.pid} stopping. Active runs keep going; gallivant start resumes monitoring.`);
223
+ if (tracker.unverified) { console.error(tracker.why); return 1; }
224
+ return 0;
225
+ }
226
+ case "status": { const r = status(ctx, { all: values.all, json: values.json }); console.log(r.text); return r.exitCode; }
227
+ case "new": {
228
+ if (!arg) throw new Error("new needs a name, for example: gallivant new widget-export");
229
+ const r = await newPrd(ctx, { name: arg, harness: values.harness, dir: values.dir });
230
+ console.log(`${r.rel}\n\nEdit it, then:\n gallivant ready ${r.rel} --check\n gallivant ready ${r.rel}\n gallivant start ${r.rel}`);
231
+ return 0;
232
+ }
233
+ case "why": { if (!arg) throw new Error("why needs a handle"); console.log(why(ctx, { handle: arg })); return 0; }
234
+ case "resume": {
235
+ if (!arg) throw new Error("resume needs a handle: the id, a PRD path, or <repo>#<pr>");
236
+ const r = await resume(ctx, { handle: arg, note: values.note, resnapshot: values.resnapshot, freshSession: values["fresh-session"] });
237
+ console.log(`${r.handle}: resumed in ${r.worktree} (run ${r.run})${r.attach ? `\nby hand: ${r.attach}` : "\nProvider progress is recorded by the coordinator."}`);
238
+ // Printed because replacing the brief the gate judges is not something to do quietly, and the operator asked for it
239
+ // one command ago: what they see back is what they actually changed.
240
+ if (r.resnapshotted?.length) console.log(`\nThe brief was resnapshotted and the session was told what moved:\n${r.resnapshotted.map(l => ` - ${l}`).join("\n")}`);
241
+ if (!readDaemon(ctx.home)?.alive) {
242
+ try { const d = await upInBackground(ctx); console.log(d ? `\nDaemon running in the background (pid ${d.pid}).` : ""); }
243
+ catch (e) { console.log(`\nThe run started, but the daemon did not: ${e.message}\n gallivant up`); }
244
+ }
245
+ return 0;
246
+ }
247
+ case "remove": { if (!arg) throw new Error("remove needs a handle"); const id = resolveHandle(ctx.state.entries(), arg); ctx.state.remove(id); console.log(`${id}: removed from the registry`); return 0; }
248
+ default: throw new Error(`unknown command "${cmd}"\n\n${HELP}`);
249
+ }
250
+ } finally { owner?.release(); }
251
+ }
252
+
253
+ async function main(argv) {
254
+ try { return await dispatch(argv); }
255
+ catch (e) {
256
+ if (argv[0] === "learn") e.message = safeDisplay({ error: { code: e.code || "LEARNING_FAILED", message: e.message } });
257
+ throw e;
258
+ }
259
+ }
260
+
261
+ main(process.argv.slice(2)).then(code => { process.exitCode = code; }, e => { console.error(`error: ${e.message}`); process.exitCode = 1; });
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+ import { parseArgs } from "node:util";
3
+ import { readFileSync } from "node:fs";
4
+ import { buildPluginPackage, validatePublicRelease } from "../lib/plugin_package.mjs";
5
+ try {
6
+ const { values } = parseArgs({ options: { config: { type: "string" }, out: { type: "string" }, "core-version": { type: "string" }, "source-commit": { type: "string" }, internal: { type: "boolean" }, "validate-public": { type: "boolean" } } });
7
+ if (!values.config) throw new Error("Pass --config with the APort endpoint, resource, publisher and asset inputs");
8
+ const config = JSON.parse(readFileSync(values.config, "utf8"));
9
+ if (values["validate-public"]) {
10
+ const result = validatePublicRelease(config); console.log(JSON.stringify(result, null, 2)); if (!result.ok) process.exitCode = 1;
11
+ } else {
12
+ if (!values.out) throw new Error("Pass --out with an empty artifact directory");
13
+ const manifest = buildPluginPackage({ config, out: values.out, coreVersion: values["core-version"], sourceCommit: values["source-commit"], internal: values.internal });
14
+ console.log(JSON.stringify({ classification: manifest.classification, coreSha256: manifest.coreSha256, hashes: manifest.hashes }, null, 2));
15
+ }
16
+ } catch (error) { console.error(`${error.code || "PACKAGE_FAILED"}: ${error.message}`); process.exitCode = 1; }
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env node
2
+ // Explicit opt-in entry point. Task execution stays in the existing G8 scripts.
3
+ import { parseArgs } from 'node:util';
4
+ import { spawnSync } from 'node:child_process';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { join } from 'node:path';
7
+ import { readReleaseJson, verifyReleasePayload, evaluateRelease } from '../lib/release_package.mjs';
8
+ try {
9
+ const { values } = parseArgs({ options: { out: { type: 'string' }, backend: { type: 'string' }, phase: { type: 'string', default: 'preflight' }, execute: { type: 'boolean' }, host: { type: 'boolean' } } });
10
+ if (!values.out) throw new Error('Supply --out for the exact installed candidate; no live action was taken');
11
+ const manifest = readReleaseJson(join(values.out, 'manifest.json'));
12
+ verifyReleasePayload(values.out, manifest);
13
+ const saved = readReleaseJson(join(values.out, 'qualification.json'));
14
+ const evidence = readReleaseJson(join(values.out, 'evidence.json'));
15
+ const result = evaluateRelease({ manifest, deployment: saved.deployment, evidence });
16
+ if (values.host) {
17
+ if (values.execute || values.backend) throw new Error('Host verification reads actual operator-captured observations; it does not simulate or dispatch a host session');
18
+ const ids = ['integration', 'deployment', 'host', 'recovery', 'reviewer', 'media'];
19
+ console.log(JSON.stringify({ kind: 'record-validation', actualHostExecution: false, checks: Object.fromEntries(ids.map(id => [id, result.checks[id]])), blockers: result.blockers.filter(item => ids.includes(item.id)) }, null, 2));
20
+ process.exitCode = ids.every(id => result.checks[id].status === 'passed') ? 0 : 1;
21
+ } else {
22
+ if (!['openai-agents', 'codex-cloud'].includes(values.backend) || !['preflight', 'implementation', 'correction'].includes(values.phase)) throw new Error('Select --backend openai-agents|codex-cloud and --phase preflight|implementation|correction');
23
+ if (values.execute && (!['implementation', 'correction'].includes(values.phase) || result.checks.integration.status !== 'passed' || result.checks.deployment.status !== 'passed')) throw new Error('Before paid phases, record qualified integration and human-approved deployment for this candidate; use preflight without --execute');
24
+ const script = fileURLToPath(new URL(`../test/${values.backend}-live.mjs`, import.meta.url));
25
+ const child = spawnSync(process.execPath, [script, `--phase=${values.phase}`, ...(values.execute ? ['--execute'] : [])], { stdio: 'inherit', env: process.env });
26
+ if (child.error) throw child.error;
27
+ process.exitCode = child.status ?? 1;
28
+ }
29
+ } catch (error) { console.error(error.message); process.exitCode = 1; }
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env node
2
+ import { parseArgs } from 'node:util';
3
+ import { join } from 'node:path';
4
+ import { readReleaseJson, releasePreflight, assembleRelease, verifyReleasePayload, evaluateRelease } from '../lib/release_package.mjs';
5
+ try {
6
+ const { values } = parseArgs({ options: { mode: { type: 'string', default: 'preflight' }, config: { type: 'string' }, setup: { type: 'string' }, evidence: { type: 'string' }, deployment: { type: 'string' }, out: { type: 'string' }, 'source-commit': { type: 'string' }, 'core-version': { type: 'string' }, internal: { type: 'boolean' } } });
7
+ let report;
8
+ if (values.mode === 'preflight') {
9
+ if (!values.config) throw new Error('Pass --config with the existing package configuration; keep private credentials outside it');
10
+ report = releasePreflight(readReleaseJson(values.config), values.setup ? readReleaseJson(values.setup) : {});
11
+ } else if (values.mode === 'build') {
12
+ if (!values.config || !values.out || !values.deployment) throw new Error('Build requires --config, --out and --deployment with the observed target revision and configuration digest');
13
+ report = assembleRelease({ config: readReleaseJson(values.config), out: values.out, deployment: readReleaseJson(values.deployment), evidence: values.evidence ? readReleaseJson(values.evidence) : undefined, coreVersion: values['core-version'], sourceCommit: values['source-commit'], internal: values.internal }).evaluation;
14
+ } else if (values.mode === 'validate') {
15
+ if (!values.out) throw new Error('Validate requires --out with the existing candidate directory');
16
+ const manifest = readReleaseJson(join(values.out, 'manifest.json'));
17
+ verifyReleasePayload(values.out, manifest);
18
+ const saved = readReleaseJson(join(values.out, 'qualification.json'));
19
+ report = evaluateRelease({ manifest, deployment: values.deployment ? readReleaseJson(values.deployment) : saved.deployment, evidence: readReleaseJson(values.evidence || join(values.out, 'evidence.json')) });
20
+ } else throw new Error('Use preflight, build or validate; this command never deploys, dispatches or publishes');
21
+ console.log(JSON.stringify(report, null, 2));
22
+ if (report.status !== 'submission-ready' && report.status !== 'prepared-unqualified') process.exitCode = 1;
23
+ } catch (error) { console.error(error.message); process.exitCode = 1; }
@@ -0,0 +1,13 @@
1
+ import { appendFileSync } from "node:fs";
2
+ import { sessionExports } from "../lib/runtime-environment.mjs";
3
+
4
+ if (!process.env.CLAUDE_ENV_FILE) {
5
+ process.stderr.write("Gallivant runtime setup: CLAUDE_ENV_FILE unavailable; Node tool runtime is unverified.\n");
6
+ process.exitCode = 1;
7
+ } else {
8
+ appendFileSync(process.env.CLAUDE_ENV_FILE, sessionExports(process.execPath, process.cwd()));
9
+ process.stdout.write(JSON.stringify({ hookSpecificOutput: {
10
+ hookEventName: "SessionStart",
11
+ additionalContext: `Selected tool runtime: ${process.execPath} (${process.platform}/${process.arch}, ${process.version}). The session environment prepends its directory to PATH for Bash commands. Before installing native dependencies, check node -p 'JSON.stringify({execPath:process.execPath,arch:process.arch,version:process.version})'. Native packages must match the Node process, not hardware architecture alone. If the selected runtime is inaccessible or differs, report the mismatch and blocked read; do not patch native packages, weaken the sandbox or change toolchains to conceal it.`,
12
+ } }));
13
+ }
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ import { parseArgs } from "node:util";
3
+ import { readFileSync } from "node:fs";
4
+ import { checkRuntimeState, migrateRuntimeState, backupRuntimeState, restoreRuntimeState } from "../lib/runtime_state.mjs";
5
+ try {
6
+ const { positionals, values } = parseArgs({ allowPositionals: true, options: { home: { type: "string" }, manifest: { type: "string" }, out: { type: "string" }, backup: { type: "string" } } });
7
+ if (!values.manifest || !values.home && positionals[0] !== "rollback") throw new Error("Pass --manifest runtime.json and --home the durable namespace");
8
+ const manifest = JSON.parse(readFileSync(values.manifest, "utf8"));
9
+ const fn = { check: checkRuntimeState, migrate: migrateRuntimeState, backup: backupRuntimeState, rollback: restoreRuntimeState }[positionals[0]];
10
+ if (!fn) throw new Error("Use check, migrate, backup or rollback (rollback needs --backup and fresh --out)");
11
+ console.log(JSON.stringify(fn({ ...values, manifest }), null, 2));
12
+ } catch (error) { console.error(`${error.code || "RUNTIME_STATE"}: ${error.message}`); process.exitCode = 1; }
package/docs/README.md ADDED
@@ -0,0 +1,72 @@
1
+ # Documentation
2
+
3
+ Start with [installation and your first run](install.md): inside the target
4
+ checkout, `npx --yes @uchibeke/gallivant init --harness codex` (or
5
+ `--harness claude`) writes the config, records the repository and prints the
6
+ setup report. Then choose how to describe the work. [Natural-language tasks](tasks.md) prepare a reviewable brief
7
+ from your request; [hand-authored PRDs](prd.md) let you write that brief directly.
8
+ Both use the same validation, execution, GitHub review and completion workflow.
9
+ Code, research and documentation can all be delivered as verifiable repository
10
+ changes. You do not need to write a PRD by hand, but every run needs clear
11
+ requirements and checks.
12
+
13
+ ## Choose a guide
14
+
15
+ | Need | Guide |
16
+ | --- | --- |
17
+ | Install, configure access and complete a first task | [Getting started](install.md) |
18
+ | Ask an agent to inspect, install and verify setup | [Agent setup](agent-setup.md) |
19
+ | Describe work, answer questions, inspect a draft and authorize execution | [Natural-language tasks](tasks.md) |
20
+ | Write a brief with acceptance criteria, source files and verification commands | [PRD contract](prd.md) |
21
+ | Find commands, monitor progress, message or resume a run | [Commands](commands.md) |
22
+ | Configure repositories, credentials, harnesses and execution limits | [Configuration](config.md) |
23
+ | Use the local dashboard and understand product boundaries | [Application](app.md) |
24
+ | Run coding work on an API-backed hosted environment | [OpenAI Agents API](openai-agents.md) |
25
+ | Understand the unavailable Codex Cloud dispatch and its required qualification | [Codex Cloud](codex-cloud.md) |
26
+ | Queue dependent tasks with a human merge between steps | [Chains](chains.md) |
27
+ | Understand permissions, credentials and completion checks | [Guardrails](guardrails.md) |
28
+ | Keep Node, Git and dependency execution consistent | [Runtime environment](runtime-environment.md) |
29
+ | Diagnose DNS/HTTPS failures and understand automatic recovery | [Network recovery](network-recovery.md) |
30
+ | Collect review feedback and understand why learned-rule activation is unavailable | [Learning](learning.md) |
31
+ | Integrate the shared services or package a task-delivery plugin | [Task delivery](task-delivery.md) |
32
+ | Qualify a specific task-delivery plugin release and its actual host evidence | [Release qualification](release-qualification.md) |
33
+ | Understand preparation isolation and provider capabilities | [Preparation](preparation.md) |
34
+ | Understand durable runs, continuation, cancellation and publication | [Execution](execution.md) |
35
+ | Understand module ownership and the overall design | [Architecture](architecture.md) |
36
+ | Prepare an announcement or publish a release | [Launch](launch.md), [releasing](releasing.md) |
37
+
38
+ ## Read support claims precisely
39
+
40
+ The local execution adapters are Claude Code, Codex, Gemini CLI, OpenCode and
41
+ Goose. The OpenAI Agents API adapter performs remote execution and restricted
42
+ task preparation. Codex Cloud is registered but refuses new dispatch because
43
+ the supported interface cannot verify the selected environment's repository.
44
+ Local Codex and Claude execution remain available. Preparation has its own
45
+ capabilities: an implementation adapter does not automatically support drafting.
46
+
47
+ Run `gallivant doctor` to check the configured installation. An adapter or a
48
+ passing fixture test does not establish access to your provider, model or paid
49
+ plan. Follow the selected provider's guide for actual prerequisites and recorded
50
+ qualification limits. When using hosted execution from the CLI, Gallivant's
51
+ coordinator still needs to remain running to advance the task and review loop.
52
+ A packaged plugin is also separate from a deployed, authenticated service.
53
+
54
+ ## Specifications and recorded evidence
55
+
56
+ The guides above describe this checkout. The following source-repository material
57
+ has a different purpose and is deliberately excluded from the npm package:
58
+
59
+ - [Implementation PRDs](https://github.com/uchibeke/gallivant/tree/main/docs/prds):
60
+ requirements used to build features, including work with incomplete gates.
61
+ - [Plans](https://github.com/uchibeke/gallivant/tree/main/docs/plans): architecture,
62
+ sequencing and release contracts, rather than a list of shipped capabilities.
63
+ - [Audits](https://github.com/uchibeke/gallivant/tree/main/docs/audit): dated results,
64
+ exact source revisions, limitations and remaining qualification work.
65
+ - [Original source-design notes](https://github.com/uchibeke/gallivant/blob/main/docs/design/sources-and-standards.md):
66
+ historical decisions and proposals with their original context.
67
+
68
+ For an older installed release, inspect the matching Git tag instead of treating
69
+ the current `main` branch as its feature set. Preserve queued PRDs and carried
70
+ plans: their bytes can be pinned to a running chain. Record new implementation
71
+ behavior in the operator guides and new evidence, rather than rewriting a
72
+ historical test result or silently changing an approved task.