@uchibeke/gallivant 0.2.0 → 0.3.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/README.md +120 -60
- package/SECURITY.md +9 -0
- package/bin/gallivant.mjs +41 -14
- package/bin/promote-bitbucket.mjs +40 -0
- package/bin/task-pr.mjs +9 -0
- package/bin/task-review.mjs +15 -0
- package/docs/README.md +6 -2
- package/docs/agent-setup.md +55 -41
- package/docs/architecture.md +11 -3
- package/docs/bitbucket-promotion.md +69 -0
- package/docs/bitbucket-review.md +330 -0
- package/docs/chains.md +30 -1
- package/docs/codex-cloud.md +7 -0
- package/docs/codex-review.md +50 -0
- package/docs/commands.md +103 -14
- package/docs/config.md +45 -13
- package/docs/execution.md +1 -1
- package/docs/first-party-review.md +266 -0
- package/docs/git-hosts.md +294 -0
- package/docs/guardrails.md +56 -0
- package/docs/install.md +137 -67
- package/docs/launch.md +7 -4
- package/docs/network-recovery.md +9 -0
- package/docs/openai-agents.md +3 -2
- package/docs/prd.md +23 -2
- package/docs/preparation.md +2 -2
- package/docs/release-qualification.md +1 -1
- package/docs/releasing.md +46 -22
- package/docs/task-delivery.md +5 -5
- package/docs/task-profiles.md +537 -0
- package/docs/tasks.md +59 -197
- package/lib/attribution.mjs +7 -3
- package/lib/block.mjs +10 -1
- package/lib/chains.mjs +101 -30
- package/lib/codex_cloud.mjs +2 -1
- package/lib/codex_cloud_input.mjs +15 -6
- package/lib/codex_cloud_result.mjs +1 -1
- package/lib/commands/doctor.mjs +22 -453
- package/lib/commands/init.mjs +281 -28
- package/lib/commands/register.mjs +81 -13
- package/lib/commands/resume.mjs +5 -1
- package/lib/commands/review.mjs +66 -0
- package/lib/commands/serve.mjs +8 -0
- package/lib/commands/start.mjs +30 -11
- package/lib/commands/status.mjs +18 -2
- package/lib/commands/task.mjs +9 -1
- package/lib/commands/up.mjs +19 -11
- package/lib/commands/why.mjs +7 -3
- package/lib/config.mjs +42 -9
- package/lib/context.mjs +28 -21
- package/lib/control.mjs +21 -6
- package/lib/credential.mjs +20 -14
- package/lib/deliverable.mjs +2 -0
- package/lib/events.mjs +96 -53
- package/lib/execution.mjs +132 -11
- package/lib/execution_preparation.mjs +16 -1
- package/lib/execution_result.mjs +26 -5
- package/lib/first_party_review.mjs +618 -0
- package/lib/fs.mjs +10 -3
- package/lib/gh.mjs +144 -56
- package/lib/git.mjs +133 -32
- package/lib/git_diff.mjs +121 -0
- package/lib/harness.mjs +14 -10
- package/lib/host_contract.mjs +71 -0
- package/lib/host_definitions.mjs +22 -0
- package/lib/hosts/bitbucket.mjs +553 -0
- package/lib/hosts/bitbucket_contract.mjs +57 -0
- package/lib/hosts/bitbucket_credential.mjs +62 -0
- package/lib/hosts/bitbucket_review.mjs +44 -0
- package/lib/hosts/bitbucket_review_producer.mjs +50 -0
- package/lib/hosts/github.mjs +57 -0
- package/lib/hosts/github_contract.mjs +27 -0
- package/lib/hosts/github_doctor.mjs +482 -0
- package/lib/hosts/github_review_summary.mjs +38 -0
- package/lib/hosts/transport_doctor.mjs +35 -0
- package/lib/hosts.mjs +130 -0
- package/lib/learning_qualification.mjs +1 -1
- package/lib/notify.mjs +1 -1
- package/lib/openai_agents_input.mjs +7 -2
- package/lib/openai_agents_sources.mjs +16 -8
- package/lib/plugin_package.mjs +13 -1
- package/lib/pr_operation.mjs +41 -0
- package/lib/prd.mjs +18 -3
- package/lib/preparation_codex.mjs +6 -1
- package/lib/probe.mjs +6 -4
- package/lib/process.mjs +24 -0
- package/lib/promotion.mjs +132 -0
- package/lib/promotion_git.mjs +63 -0
- package/lib/prompt.mjs +16 -13
- package/lib/provider_health.mjs +14 -3
- package/lib/repository.mjs +68 -0
- package/lib/repository_preflight.mjs +11 -0
- package/lib/review_evaluation.mjs +25 -0
- package/lib/review_evidence.mjs +500 -0
- package/lib/review_history.mjs +73 -0
- package/lib/review_image.mjs +145 -0
- package/lib/review_link.mjs +12 -0
- package/lib/review_policy.mjs +132 -0
- package/lib/review_policy_contract.mjs +18 -0
- package/lib/review_state.mjs +13 -0
- package/lib/review_visual.mjs +94 -0
- package/lib/runtime-environment.mjs +6 -0
- package/lib/runtime_node.mjs +3 -2
- package/lib/runtime_state.mjs +39 -5
- package/lib/sources/prd.mjs +6 -3
- package/lib/state.mjs +137 -24
- package/lib/task_completion.mjs +7 -3
- package/lib/task_contract.mjs +201 -27
- package/lib/task_delivery.mjs +9 -2
- package/lib/task_delivery_contract.mjs +13 -9
- package/lib/task_delivery_view.mjs +18 -2
- package/lib/task_environment.mjs +13 -1
- package/lib/task_limits.mjs +13 -0
- package/lib/task_model_config.mjs +99 -0
- package/lib/task_model_route.mjs +243 -0
- package/lib/task_prepare.mjs +21 -8
- package/lib/task_profile.mjs +1875 -0
- package/lib/task_repository_context.mjs +65 -0
- package/lib/tasks.mjs +252 -51
- package/lib/tick.mjs +373 -98
- package/lib/time.mjs +1 -0
- package/lib/tracker/pages.mjs +19 -5
- package/lib/tracker/review.mjs +21 -0
- package/lib/tracker_process.mjs +8 -1
- package/lib/verification_sandbox.mjs +2 -1
- package/lib/work_artifacts.mjs +79 -0
- package/npm-shrinkwrap.json +12 -3
- package/package.json +9 -3
- package/templates/plugin/task-delivery.SKILL.md +17 -10
- package/templates/plugin/task-intake-contract.json +18 -0
- package/templates/plugin/task-profile-requests.mjs +1448 -0
- package/templates/tracker/tracker.css +3 -0
- package/templates/tracker/tracker.js +38 -0
package/lib/time.mjs
CHANGED
|
@@ -27,6 +27,7 @@ export const earlier = (a, b) => (!a ? b : !b ? a : Date.parse(a) <= Date.parse(
|
|
|
27
27
|
export function ago(iso, clock = Date) {
|
|
28
28
|
if (!iso) return "never";
|
|
29
29
|
const ms = clock.now() - Date.parse(iso);
|
|
30
|
+
if (!Number.isFinite(ms)) return "unknown";
|
|
30
31
|
if (ms < 45_000) return "just now";
|
|
31
32
|
const m = Math.round(ms / 60_000);
|
|
32
33
|
if (m < 60) return `${m}m ago`;
|
package/lib/tracker/pages.mjs
CHANGED
|
@@ -8,6 +8,8 @@ import { contract } from "../fs.mjs";
|
|
|
8
8
|
import { controlPending, CONTROL_LABELS, MAX_NOTE, lastMessage } from "../control.mjs";
|
|
9
9
|
import { probeStatus } from "../probe.mjs";
|
|
10
10
|
import { needsAgentResume, networkRecoveryDetail } from "../network_recovery.mjs";
|
|
11
|
+
import { reviewProgressText } from "../review_policy.mjs";
|
|
12
|
+
import { reviewActivity } from "./review.mjs";
|
|
11
13
|
|
|
12
14
|
/** The five characters that matter in HTML text and in a double-quoted attribute. Nothing else is interpolated. */
|
|
13
15
|
export const esc = s => String(s ?? "")
|
|
@@ -125,10 +127,10 @@ export function dashboard(m, { clock = Date, by = "state", theme = null, query =
|
|
|
125
127
|
const ctxq = { theme: query.theme, by: by === "state" ? "" : by };
|
|
126
128
|
const groups = entryGroups(base, m, by, null, ctxq);
|
|
127
129
|
const table = (title, rows) => `<h2>${esc(title)}</h2>` + (rows.length ? `<table>
|
|
128
|
-
<thead><tr><th>entry</th><th>state</th><th>detail</th><th>round</th><th>
|
|
130
|
+
<thead><tr><th>entry</th><th>state</th><th>detail</th><th>round</th><th>last activity</th><th></th></tr></thead><tbody>
|
|
129
131
|
${rows.map(r => `<tr><td><a href="${esc(at(base, "/run", ctxq, { id: r.id }))}">${esc(r.handle)}</a></td>
|
|
130
132
|
<td><span class="tag${r.section === "needs_you" ? " bad" : r.run?.running ? " live" : ""}">${esc(r.label)}</span></td><td>${esc(r.detail)}</td><td class="dim">${esc(r.round)}</td>
|
|
131
|
-
<td class="dim">${esc(ago(r.since, clock))}</td><td>${r.url ? `<a href="${esc(r.url)}">PR</a>` : ""}</td></tr>`).join("\n")}
|
|
133
|
+
<td class="dim">${r.since ? `<time datetime="${esc(r.since)}" data-relative-time>${esc(ago(r.since, clock))}</time>` : "unknown"}</td><td>${r.url ? `<a href="${esc(r.url)}">PR</a>` : ""}</td></tr>`).join("\n")}
|
|
132
134
|
</tbody></table>` : `<p class="empty">(none)</p>`);
|
|
133
135
|
const d = m.daemon;
|
|
134
136
|
// A dead daemon is the first thing a reader needs, and "run gallivant up" on its own does not say how long it has
|
|
@@ -144,6 +146,7 @@ ${rows.map(r => `<tr><td><a href="${esc(at(base, "/run", ctxq, { id: r.id }))}">
|
|
|
144
146
|
<section class="main">
|
|
145
147
|
<p class="${d?.alive ? "dim" : "warn"}">${esc(state)}</p>
|
|
146
148
|
${m.latestError ? `<p class="warn mono">${esc(m.latestError)}</p>` : ""}
|
|
149
|
+
${(m.repositoryGuidance ?? []).map(r => `<p class="warn">${esc(r.repo)}: ${esc(r.detail)}</p>`).join("")}
|
|
147
150
|
${SECTIONS.map(([key, label]) => table(label, m[key] || [])).join("")}
|
|
148
151
|
</section></div>` });
|
|
149
152
|
}
|
|
@@ -195,12 +198,13 @@ export function runPage({ id, entry, handle, run, initialLaunch = false, maxIter
|
|
|
195
198
|
fact("network recovery", entry.network_retry ? esc(networkRecoveryDetail(entry.network_retry)) : ""),
|
|
196
199
|
fact("harness", esc(entry.harness)),
|
|
197
200
|
fact("round", esc(`round ${entry.iteration}/${maxIterations}`)),
|
|
201
|
+
fact("independent review", esc(reviewProgressText(entry))),
|
|
198
202
|
fact("PR", entry.url ? `<a href="${esc(entry.url)}">${esc(entry.url)}</a>` : entry.pr ? esc(`#${entry.pr}`) : ""),
|
|
199
203
|
fact("branch", `<code>${esc(entry.branch)}</code>`),
|
|
200
204
|
fact("worktree", `<code>${esc(contract(entry.worktree))}</code>`),
|
|
201
205
|
fact("session", `<code>${esc(entry.session)}</code>`),
|
|
202
206
|
fact("provider state", esc(entry.execution?.providerState)),
|
|
203
|
-
fact("observed / reconciled", entry.execution ? esc(`${entry.execution.observedAt
|
|
207
|
+
fact("observed / reconciled", entry.execution ? esc(`${when(entry.execution.observedAt)} / ${when(entry.execution.reconciledAt)}`) : ""),
|
|
204
208
|
fact("recovery action", esc(entry.execution?.recoveryAction)),
|
|
205
209
|
fact("run", run ? `<code>${esc(run.id)}</code> ${run.running ? `<span class="live">running</span>` : `exit ${esc(String(run.exit ?? "not recorded yet"))}`}` : ""),
|
|
206
210
|
fact("runs", `<a href="${esc(at(base, "/runs", ctxq, { entry: id }))}">every run for this entry</a>`),
|
|
@@ -219,6 +223,12 @@ export function runPage({ id, entry, handle, run, initialLaunch = false, maxIter
|
|
|
219
223
|
<section class="main">
|
|
220
224
|
${divergence ? briefMoved(divergence) : ""}
|
|
221
225
|
<dl class="facts">${facts}</dl>
|
|
226
|
+
<section aria-labelledby="review-activity-heading" id="review-activity" data-entry="${esc(id)}">
|
|
227
|
+
<h2 id="review-activity-heading">Reviewer activity</h2>
|
|
228
|
+
<p class="note">Live status updates from the independent reviewer. The agent log is shown separately below.</p>
|
|
229
|
+
<div id="review-activity-log" role="log" aria-live="polite" aria-relevant="additions text"><p>${esc(reviewActivity(entry).message)}</p></div>
|
|
230
|
+
<p id="review-activity-state" class="note">Connecting to review status…</p>
|
|
231
|
+
</section>
|
|
222
232
|
${controlToken && entry.state !== "done" ? `<section id="run-controls" class="run-controls" aria-labelledby="control-heading" data-entry="${esc(id)}" data-token="${esc(controlToken)}" data-pending="${controlPending(entry.control)}">
|
|
223
233
|
<h2 id="control-heading">Run actions</h2>
|
|
224
234
|
<div class="run-actions">
|
|
@@ -241,7 +251,7 @@ ${run?.running && entry.execution?.controls?.interrupt !== false ? `<label class
|
|
|
241
251
|
<a id="control-refresh" href="${esc(at(base, "/run", ctxq, { id }))}" hidden>View updated run</a>
|
|
242
252
|
<details id="last-message"${savedMessage ? "" : " hidden"}><summary>Last submitted message</summary><pre>${esc(savedMessage?.note || "")}</pre><button id="restore-message" type="button">Edit and resend</button></details>
|
|
243
253
|
</section>` : entry.state === "done" ? `<p class="note">This PR is closed or merged. Start a follow-up PRD for further changes.</p>` : ""}
|
|
244
|
-
<h2>
|
|
254
|
+
<h2>Agent log</h2>
|
|
245
255
|
${run ? logPane(`data-entry="${esc(id)}" data-run="${esc(run.id)}" data-harness="${esc(entry.harness || "")}"`) : `<p class="empty">No run has been dispatched for this entry yet.</p>`}
|
|
246
256
|
</section></div>` });
|
|
247
257
|
}
|
|
@@ -310,7 +320,11 @@ function runGroups(base, rows, by, entry, ctxq, open = null) {
|
|
|
310
320
|
.filter(g => g.rows.length);
|
|
311
321
|
}
|
|
312
322
|
|
|
313
|
-
const when =
|
|
323
|
+
const when = value => {
|
|
324
|
+
if (value == null || value === "") return "unknown";
|
|
325
|
+
const date = new Date(value);
|
|
326
|
+
return Number.isFinite(date.getTime()) ? `${date.toISOString().replace("T", " ").slice(0, 19)} UTC` : "unknown";
|
|
327
|
+
};
|
|
314
328
|
|
|
315
329
|
const duration = r => {
|
|
316
330
|
if (r.running) return "running";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { needsFirstParty, reviewProgressText } from "../review_policy.mjs";
|
|
2
|
+
import { redact } from "../execution_io.mjs";
|
|
3
|
+
|
|
4
|
+
// A read-only status projection. Review evidence and raw provider output can
|
|
5
|
+
// contain private source; neither belongs in a dashboard activity feed.
|
|
6
|
+
export function reviewActivity(entry) {
|
|
7
|
+
if (!needsFirstParty(entry.reviewPolicy)) return { status: "unavailable", message: "No independent review is configured for this task." };
|
|
8
|
+
const record = entry.firstPartyReview;
|
|
9
|
+
if (!record) return { status: "waiting", message: "Waiting for independent review." };
|
|
10
|
+
const pieces = Array.isArray(record.pieces) ? record.pieces : [];
|
|
11
|
+
const current = pieces.findIndex(piece => piece.status === "running");
|
|
12
|
+
const verdict = record.status === "completed" ? record.result?.verdict : null;
|
|
13
|
+
const count = verdict === "findings" ? record.result?.findingsCount ?? (Array.isArray(record.result?.findings) ? record.result.findings.length : 0) : 0;
|
|
14
|
+
const parts = [reviewProgressText(entry)];
|
|
15
|
+
if (current >= 0) parts.push(`reviewing job ${current + 1} of ${pieces.length}`);
|
|
16
|
+
if (verdict === "findings") parts.push(`${count} finding${count === 1 ? "" : "s"}`);
|
|
17
|
+
if (verdict === "clear") parts.push("review clear; other completion checks still apply");
|
|
18
|
+
if (record.disposition?.status === "dispatched") parts.push("findings sent to agent");
|
|
19
|
+
if (["failed", "incomplete"].includes(record.status) && record.error) parts.push(redact(String(record.error)).slice(0, 500));
|
|
20
|
+
return { status: record.status, message: parts.filter(Boolean).join("; ") };
|
|
21
|
+
}
|
package/lib/tracker_process.mjs
CHANGED
|
@@ -82,7 +82,14 @@ export async function trackerInBackground(ctx, { port = DEFAULT_PORT, waitMs = 1
|
|
|
82
82
|
const deadline = Date.now() + waitMs;
|
|
83
83
|
while (Date.now() < deadline) {
|
|
84
84
|
assertUnpinnedHome(ctx.home);
|
|
85
|
-
|
|
85
|
+
let record;
|
|
86
|
+
try { record = readTracker(ctx.home); }
|
|
87
|
+
catch (error) {
|
|
88
|
+
// Our child creates its lock before writing JSON. Wait within this
|
|
89
|
+
// startup deadline; malformed prior locks still refuse before spawning,
|
|
90
|
+
// and no incomplete record can establish readiness or signal authority.
|
|
91
|
+
if (!child || !error.message.startsWith(`${lockPath(ctx.home)} is not valid JSON:`)) throw error;
|
|
92
|
+
}
|
|
86
93
|
usable(record, port);
|
|
87
94
|
if (await ready(record, port)) return record;
|
|
88
95
|
if (spawnError || child?.exitCode != null && !record?.alive) throw new Error(`Tracker failed to start; see ${join(ctx.home, "tracker.log")}`);
|
|
@@ -5,7 +5,8 @@ import { homedir } from "node:os";
|
|
|
5
5
|
import { existsSync, realpathSync, statSync, mkdtempSync, writeFileSync, rmSync, mkdirSync, readlinkSync } from "node:fs";
|
|
6
6
|
import { createHash } from "node:crypto";
|
|
7
7
|
import { deflateSync } from "node:zlib";
|
|
8
|
-
import { execFileAsync
|
|
8
|
+
import { execFileAsync } from "./process.mjs";
|
|
9
|
+
import { MAX_DIFF_BYTES } from "./host_contract.mjs";
|
|
9
10
|
import { executionError } from "./execution_io.mjs";
|
|
10
11
|
|
|
11
12
|
const unavailable = () => executionError("EXECUTION_VERIFICATION_SANDBOX", "OS verification sandbox unavailable or enforcement probe failed; use macOS sandbox-exec with its installed dyld-support.sb and Command Line Tools, or Linux /usr/bin/bwrap with working user/PID/network namespaces; /usr/bin/git and a dedicated Node installation with npm are required. Preserve saved results and repair host isolation before explicitly resuming; verification never falls back to an unsandboxed shell");
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { parseSourcesMeta } from "./config.mjs";
|
|
2
|
+
|
|
3
|
+
// Bump when classification or path-proof semantics change to invalidate saved checks.
|
|
4
|
+
export const PUBLICATION_POLICY_VERSION = 1;
|
|
5
|
+
|
|
6
|
+
/** Exact output exceptions come only from the coordinator's approved brief snapshot. */
|
|
7
|
+
export function publicationPolicyOf(prd, { sources = [], base = null } = {}) {
|
|
8
|
+
const { paths, problems } = parseSourcesMeta(prd?.meta?.publish_files);
|
|
9
|
+
if (paths.length > 100) problems.push("publish_files is limited to 100 exact deliverable paths");
|
|
10
|
+
if (paths.some(path => path.split("/").includes("."))) problems.push("publish_files must use canonical paths without ./ components");
|
|
11
|
+
if (problems.length) throw new Error(`Invalid publish_files: ${problems.join("; ")}`);
|
|
12
|
+
return { publishFiles: paths, inputFiles: [...new Set([...parseSourcesMeta(prd?.meta?.sources).paths, ...sources])], ...(base ? { base } : {}) };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Self-contained so the identical classifier can be embedded in the Git hook.
|
|
16
|
+
// This is a path hygiene policy, not a content classifier or a sandbox boundary.
|
|
17
|
+
export function workArtifactReason(path, policy = {}) {
|
|
18
|
+
if (typeof path !== "string" || !path || path.startsWith("/") || /[\\\x00]/.test(path) || path.split("/").some(p => !p || p === "." || p === "..")) return "invalid publication path";
|
|
19
|
+
if (policy.publishFiles?.includes(path)) return null;
|
|
20
|
+
if (policy.inputFiles?.includes(path)) return "carried task input, not an output deliverable";
|
|
21
|
+
const name = path.normalize("NFC").toLowerCase();
|
|
22
|
+
if (/(^|\/)(?:\.gallivant|\.agent-work|\.task-work|session-logs|session_logs|work-logs|work_logs|scratch|scratchpad)(\/|$)/.test(name)) return "task scratch or session storage";
|
|
23
|
+
if (/(^|\/)(?:\.codex\/sessions|\.claude\/projects|docs\/(?:audit|audits|evidence|verification|work|notes|runs))(\/|$)/.test(name)) return "task audit, evidence or execution notes";
|
|
24
|
+
if (/(^|\/)(?:work|session|agent)[-_]?(?:log|notes|summary)(?:[-_][^/]*)?\.(?:md|txt|log|jsonl?)$/i.test(name)) return "task work log, session notes or summary";
|
|
25
|
+
if (/(^|\/)(?:handoff|audit|audit[-_]report|review|notes|scratch)(?:[-_][^/]*)?\.(?:md|txt|log|jsonl)$/i.test(name)) return "task review notes, audit or handoff";
|
|
26
|
+
if (/(^|\/)docs\/(?:release|reporting)\/[^/]+[-_]validation\.md$/.test(name)) return "execution validation log";
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function workArtifactViolations(paths, policy = {}) {
|
|
31
|
+
return [...new Set(paths)].flatMap(path => { const reason = workArtifactReason(path, policy); return reason ? [{ path, reason }] : []; });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function workArtifactError(violations) {
|
|
35
|
+
return [
|
|
36
|
+
"Task work artifacts must stay out of the commit/PR:",
|
|
37
|
+
...violations.slice(0, 30).map(v => ` ${JSON.stringify(v.path)}: ${v.reason}`),
|
|
38
|
+
...(violations.length > 30 ? [` ... and ${violations.length - 30} more`] : []),
|
|
39
|
+
"Unstage these files or remove them from the branch diff, preserving useful local copies. Keep carried inputs readable in the checkout; do not delete them.",
|
|
40
|
+
"Keep scratch files outside the repository (for example, in a directory created with mktemp -d). Put concise verification results in the PR receipt; retain durable project documentation and tests.",
|
|
41
|
+
"An explicitly requested report or evidence file may be listed by exact path in publish_files in the operator's approved brief. Do not edit the session copy to grant an exception, rename logs to evade this rule, or bypass hooks.",
|
|
42
|
+
].join("\n");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function assertPublicationPaths(paths, policy = {}) {
|
|
46
|
+
const violations = workArtifactViolations(paths, policy);
|
|
47
|
+
if (violations.length) throw new Error(workArtifactError(violations));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Inlined, dependency-free policy shared by every local harness's existing hook. */
|
|
51
|
+
export function workArtifactHook(policy = {}) {
|
|
52
|
+
return `
|
|
53
|
+
${workArtifactReason.toString()}
|
|
54
|
+
${workArtifactViolations.toString()}
|
|
55
|
+
${workArtifactError.toString()}
|
|
56
|
+
const { execFileSync } = await import("node:child_process");
|
|
57
|
+
try {
|
|
58
|
+
const policy = ${JSON.stringify(policy)};
|
|
59
|
+
const git = args => execFileSync("git", args, { encoding: "utf8", timeout: 10000, maxBuffer: 16777216 });
|
|
60
|
+
let mergeHead = null;
|
|
61
|
+
try { mergeHead = readFileSync(git(["rev-parse", "--git-path", "MERGE_HEAD"]).trim(), "utf8").trim(); }
|
|
62
|
+
catch (error) { if (error.code !== "ENOENT") throw error; }
|
|
63
|
+
// A trusted base refresh brings upstream-owned files into the index. Judge
|
|
64
|
+
// the candidate tree against that exact base parent, never skip a merge.
|
|
65
|
+
let comparison = [];
|
|
66
|
+
if (mergeHead && policy.base) {
|
|
67
|
+
const approvedBase = git(["rev-parse", "--verify", "refs/remotes/origin/" + policy.base + "^{commit}"]).trim();
|
|
68
|
+
if (mergeHead === approvedBase) comparison = [approvedBase];
|
|
69
|
+
}
|
|
70
|
+
const paths = git(["diff", "--cached", "--name-only", "--diff-filter=ACMRT", "-z", ...comparison]);
|
|
71
|
+
if (paths && !paths.endsWith("\\0")) throw new Error("incomplete staged path list");
|
|
72
|
+
const violations = workArtifactViolations(paths ? paths.slice(0, -1).split("\\0") : [], policy);
|
|
73
|
+
if (violations.length) throw new Error(workArtifactError(violations));
|
|
74
|
+
} catch (error) {
|
|
75
|
+
process.stderr.write("Publication check failed: " + error.message + "\\n");
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
`;
|
|
79
|
+
}
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,25 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uchibeke/gallivant",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@uchibeke/gallivant",
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.3.0",
|
|
10
10
|
"bundleDependencies": [
|
|
11
11
|
"smol-toml"
|
|
12
12
|
],
|
|
13
13
|
"license": "Apache-2.0",
|
|
14
14
|
"dependencies": {
|
|
15
15
|
"ajv": "8.20.0",
|
|
16
|
+
"jpeg-js": "0.4.4",
|
|
16
17
|
"pngjs": "7.0.0",
|
|
17
18
|
"smol-toml": "1.9.0"
|
|
18
19
|
},
|
|
19
20
|
"bin": {
|
|
20
21
|
"gallivant": "bin/gallivant.mjs",
|
|
21
22
|
"task-delivery-package": "bin/package-plugin.mjs",
|
|
22
|
-
"task-delivery-state": "bin/task-delivery-state.mjs"
|
|
23
|
+
"task-delivery-state": "bin/task-delivery-state.mjs",
|
|
24
|
+
"task-pr": "bin/task-pr.mjs",
|
|
25
|
+
"task-review": "bin/task-review.mjs"
|
|
23
26
|
},
|
|
24
27
|
"devDependencies": {
|
|
25
28
|
"@modelcontextprotocol/sdk": "1.32.1",
|
|
@@ -1228,6 +1231,12 @@
|
|
|
1228
1231
|
"url": "https://github.com/sponsors/panva"
|
|
1229
1232
|
}
|
|
1230
1233
|
},
|
|
1234
|
+
"node_modules/jpeg-js": {
|
|
1235
|
+
"version": "0.4.4",
|
|
1236
|
+
"resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz",
|
|
1237
|
+
"integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==",
|
|
1238
|
+
"license": "BSD-3-Clause"
|
|
1239
|
+
},
|
|
1231
1240
|
"node_modules/json-schema-traverse": {
|
|
1232
1241
|
"version": "1.0.0",
|
|
1233
1242
|
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
package/package.json
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uchibeke/gallivant",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Turn repository tasks into reviewed pull requests with local or hosted agents, acceptance checks and a persistent review loop.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"gallivant": "bin/gallivant.mjs",
|
|
8
|
+
"task-review": "bin/task-review.mjs",
|
|
9
|
+
"task-pr": "bin/task-pr.mjs",
|
|
8
10
|
"task-delivery-package": "bin/package-plugin.mjs",
|
|
9
11
|
"task-delivery-state": "bin/task-delivery-state.mjs"
|
|
10
12
|
},
|
|
@@ -49,7 +51,9 @@
|
|
|
49
51
|
"registry": "https://registry.npmjs.org"
|
|
50
52
|
},
|
|
51
53
|
"scripts": {
|
|
54
|
+
"promote:bitbucket": "node bin/promote-bitbucket.mjs",
|
|
52
55
|
"test": "node --test test/*.test.mjs",
|
|
56
|
+
"test:first-party-review:live": "node test/first-party-review-live.mjs",
|
|
53
57
|
"test:install": "node test/install-package.mjs",
|
|
54
58
|
"test:learning": "node --test test/learning.test.mjs test/learning-openssh.test.mjs test/learning-dispatch.test.mjs",
|
|
55
59
|
"test:learning:live": "node test/learning-live.mjs",
|
|
@@ -59,11 +63,12 @@
|
|
|
59
63
|
"test:openai-agents:preparation:live": "node test/openai-agents-preparation-live.mjs",
|
|
60
64
|
"test:codex-cloud": "node --test test/codex-cloud.test.mjs && node --test --test-name-pattern='codex-cloud|result publication validates|verified publication recovery|failed remote verification|deterministic publication rejection|interrupted verification' test/execution-backends.test.mjs",
|
|
61
65
|
"test:codex-cloud:live": "node test/codex-cloud-live.mjs",
|
|
62
|
-
"test:task-intake": "node --test test/task-intake.test.mjs",
|
|
66
|
+
"test:task-intake": "node --test test/task-intake.test.mjs test/task-profile.test.mjs",
|
|
67
|
+
"contract:intake": "node scripts/task-intake-contract.mjs",
|
|
63
68
|
"test:live-harnesses": "node test/live-harnesses.mjs",
|
|
64
69
|
"test:plugin-package": "node test/plugin-package.mjs",
|
|
65
70
|
"package:plugin": "node bin/package-plugin.mjs",
|
|
66
|
-
"lint": "node --check bin/release-package.mjs && node --check bin/release-live.mjs && node --check bin/gallivant.mjs && node --check bin/package-plugin.mjs && node --check bin/task-delivery-state.mjs && for f in lib/*.mjs lib/*/*.mjs; do node --check \"$f\" || exit 1; done",
|
|
71
|
+
"lint": "node --check bin/promote-bitbucket.mjs && node --check bin/task-pr.mjs && node --check bin/task-review.mjs && node --check bin/release-package.mjs && node --check bin/release-live.mjs && node --check bin/gallivant.mjs && node --check bin/package-plugin.mjs && node --check bin/task-delivery-state.mjs && for f in lib/*.mjs lib/*/*.mjs scripts/*.mjs; do node --check \"$f\" || exit 1; done && node scripts/task-intake-contract.mjs --check",
|
|
67
72
|
"test:release-package": "node --test test/release-package.test.mjs",
|
|
68
73
|
"release:preflight": "node bin/release-package.mjs --mode preflight",
|
|
69
74
|
"release:package": "node bin/release-package.mjs --mode build",
|
|
@@ -86,6 +91,7 @@
|
|
|
86
91
|
},
|
|
87
92
|
"dependencies": {
|
|
88
93
|
"ajv": "8.20.0",
|
|
94
|
+
"jpeg-js": "0.4.4",
|
|
89
95
|
"pngjs": "7.0.0",
|
|
90
96
|
"smol-toml": "1.9.0"
|
|
91
97
|
},
|
|
@@ -16,21 +16,28 @@ inference costs but does not authorize implementation. Ask the saved questions,
|
|
|
16
16
|
then call task_answer with their IDs, a new operationId and the saved revision.
|
|
17
17
|
Preserve explicit requirements and any readiness denial. Inspect stale revisions.
|
|
18
18
|
|
|
19
|
-
Read the repository's
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
Read the repository's delivery capabilities and select the creation-only typed
|
|
20
|
+
delivery profile with `delivery: { action, completion }`: action pull_request
|
|
21
|
+
(the only supported action) and completion delivery_ready for a qualified
|
|
22
|
+
reviewable delivery, or integrated when the user wants monitoring through an
|
|
23
|
+
external merge. Omitted values default to pull_request and delivery_ready. The
|
|
24
|
+
profile alone authorizes external actions; request text, answers and model
|
|
25
|
+
output cannot expand it, and merge or deploy actions are refused with a remedy.
|
|
26
|
+
Do not duplicate coordinator publication as a provider implementation criterion.
|
|
27
|
+
Select `route` only when the user asks: harness uses the connected harness,
|
|
28
|
+
model uses the operator's bounded text-only model route when enabled.
|
|
24
29
|
Organize the user's implementation requirements clearly yourself; do not require
|
|
25
30
|
the user to format a special prompt. Preserve every constraint and prohibition,
|
|
26
31
|
show the separated delivery goal with the prepared brief, and ask a focused
|
|
27
32
|
clarification if mixed prose cannot be separated without changing meaning.
|
|
28
33
|
Never rewrite an existing false attestation or drop an inconvenient requirement.
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
+
A clause that conflicts with the profile returns a q-delivery-* question in the
|
|
35
|
+
saved task; answer it with the implementation scope the clause keeps, or prepare
|
|
36
|
+
a new task with a different profile. Show the user the returned review object:
|
|
37
|
+
immutable request, clause map, open questions, assumptions, draft, profile,
|
|
38
|
+
harness, allowance, route and cost status. Historical tasks may still return
|
|
39
|
+
TASK_DELIVERY_SCOPE; then ask the owner to clarify the whole request and use a
|
|
40
|
+
new preparation identity. Never remove a condition to pass validation.
|
|
34
41
|
|
|
35
42
|
After the user authorizes implementation, call task_start with run true, a stable
|
|
36
43
|
operationId and the current revision. Return the task handle promptly. Subsequent
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"format": 1,
|
|
3
|
+
"profileVersion": 1,
|
|
4
|
+
"taskVersion": 1,
|
|
5
|
+
"fragmentVersion": 2,
|
|
6
|
+
"validator": "lib/task_profile.mjs",
|
|
7
|
+
"fixtures": "templates/plugin/task-profile-requests.mjs",
|
|
8
|
+
"files": {
|
|
9
|
+
"lib/task_profile.mjs": "18b51dbc87107295806d3125a306454f6ce22660b8758065ac559c2882511cf6",
|
|
10
|
+
"lib/host_definitions.mjs": "1179cddc073e2a05aa603b7117c178ffe4fc057d7270931832fbf8733d3a84d6",
|
|
11
|
+
"lib/review_policy_contract.mjs": "16cafa3b835a3fa8c3904b0cc577f46f6b7bb0c9a1697ad0438a32cac636f3f2",
|
|
12
|
+
"lib/hosts/github_contract.mjs": "f8910b07ddd0dca8c06780d78eb3b5a106da4604ba01cb25b7a205432e2fbe81",
|
|
13
|
+
"lib/hosts/bitbucket_contract.mjs": "89cbf40585fa81f7030adbe368c60f430af73b741e87d9518bbb8e3ffa03e02f",
|
|
14
|
+
"lib/repository.mjs": "f397883ca04bae6c3d15e730fbef2fe267fb5d616566a9f4b724aee8f4925f97",
|
|
15
|
+
"templates/plugin/task-profile-requests.mjs": "7cbc45263fce9025737b5b1709d463b21a853de5ad8d486a8ef8c96ff41e8da4"
|
|
16
|
+
},
|
|
17
|
+
"upgrade": "Vendor the listed files byte-for-byte. A profileVersion change requires the Agent Passport task-intake-vendor-sync PRD; saved tasks without a fragmentVersion pin retain line-based source references; older saved profiles keep their original interpretation and fail closed when unsupported."
|
|
18
|
+
}
|