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