@uchibeke/gallivant 0.2.1 → 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.
Files changed (129) hide show
  1. package/README.md +38 -16
  2. package/SECURITY.md +9 -0
  3. package/bin/gallivant.mjs +33 -10
  4. package/bin/promote-bitbucket.mjs +40 -0
  5. package/bin/task-pr.mjs +9 -0
  6. package/bin/task-review.mjs +15 -0
  7. package/docs/README.md +2 -0
  8. package/docs/architecture.md +11 -3
  9. package/docs/bitbucket-promotion.md +69 -0
  10. package/docs/bitbucket-review.md +330 -0
  11. package/docs/chains.md +30 -1
  12. package/docs/codex-cloud.md +7 -0
  13. package/docs/codex-review.md +50 -0
  14. package/docs/commands.md +65 -5
  15. package/docs/config.md +35 -10
  16. package/docs/first-party-review.md +266 -0
  17. package/docs/git-hosts.md +294 -0
  18. package/docs/guardrails.md +56 -0
  19. package/docs/install.md +18 -8
  20. package/docs/network-recovery.md +9 -0
  21. package/docs/openai-agents.md +2 -1
  22. package/docs/prd.md +23 -2
  23. package/docs/preparation.md +1 -1
  24. package/docs/release-qualification.md +1 -1
  25. package/docs/releasing.md +17 -2
  26. package/docs/task-delivery.md +4 -4
  27. package/docs/task-profiles.md +537 -0
  28. package/docs/tasks.md +59 -197
  29. package/lib/attribution.mjs +7 -3
  30. package/lib/block.mjs +10 -1
  31. package/lib/chains.mjs +101 -30
  32. package/lib/codex_cloud.mjs +2 -1
  33. package/lib/codex_cloud_input.mjs +15 -6
  34. package/lib/codex_cloud_result.mjs +1 -1
  35. package/lib/commands/doctor.mjs +22 -462
  36. package/lib/commands/init.mjs +72 -30
  37. package/lib/commands/register.mjs +25 -11
  38. package/lib/commands/resume.mjs +5 -1
  39. package/lib/commands/review.mjs +66 -0
  40. package/lib/commands/serve.mjs +8 -0
  41. package/lib/commands/start.mjs +30 -11
  42. package/lib/commands/status.mjs +18 -2
  43. package/lib/commands/task.mjs +9 -1
  44. package/lib/commands/up.mjs +19 -11
  45. package/lib/commands/why.mjs +7 -3
  46. package/lib/config.mjs +42 -9
  47. package/lib/context.mjs +23 -18
  48. package/lib/control.mjs +21 -6
  49. package/lib/credential.mjs +20 -14
  50. package/lib/deliverable.mjs +2 -0
  51. package/lib/events.mjs +96 -53
  52. package/lib/execution.mjs +48 -3
  53. package/lib/execution_preparation.mjs +16 -1
  54. package/lib/execution_result.mjs +26 -5
  55. package/lib/first_party_review.mjs +618 -0
  56. package/lib/gh.mjs +144 -56
  57. package/lib/git.mjs +133 -32
  58. package/lib/git_diff.mjs +121 -0
  59. package/lib/harness.mjs +14 -10
  60. package/lib/host_contract.mjs +71 -0
  61. package/lib/host_definitions.mjs +22 -0
  62. package/lib/hosts/bitbucket.mjs +553 -0
  63. package/lib/hosts/bitbucket_contract.mjs +57 -0
  64. package/lib/hosts/bitbucket_credential.mjs +62 -0
  65. package/lib/hosts/bitbucket_review.mjs +44 -0
  66. package/lib/hosts/bitbucket_review_producer.mjs +50 -0
  67. package/lib/hosts/github.mjs +57 -0
  68. package/lib/hosts/github_contract.mjs +27 -0
  69. package/lib/hosts/github_doctor.mjs +482 -0
  70. package/lib/hosts/github_review_summary.mjs +38 -0
  71. package/lib/hosts/transport_doctor.mjs +35 -0
  72. package/lib/hosts.mjs +130 -0
  73. package/lib/learning_qualification.mjs +1 -1
  74. package/lib/notify.mjs +1 -1
  75. package/lib/openai_agents_input.mjs +7 -2
  76. package/lib/openai_agents_sources.mjs +16 -8
  77. package/lib/plugin_package.mjs +13 -1
  78. package/lib/pr_operation.mjs +41 -0
  79. package/lib/prd.mjs +18 -3
  80. package/lib/preparation_codex.mjs +6 -1
  81. package/lib/probe.mjs +6 -4
  82. package/lib/process.mjs +24 -0
  83. package/lib/promotion.mjs +132 -0
  84. package/lib/promotion_git.mjs +63 -0
  85. package/lib/prompt.mjs +16 -13
  86. package/lib/provider_health.mjs +14 -3
  87. package/lib/repository.mjs +68 -0
  88. package/lib/repository_preflight.mjs +11 -0
  89. package/lib/review_evaluation.mjs +25 -0
  90. package/lib/review_evidence.mjs +500 -0
  91. package/lib/review_history.mjs +73 -0
  92. package/lib/review_image.mjs +145 -0
  93. package/lib/review_link.mjs +12 -0
  94. package/lib/review_policy.mjs +132 -0
  95. package/lib/review_policy_contract.mjs +18 -0
  96. package/lib/review_state.mjs +13 -0
  97. package/lib/review_visual.mjs +94 -0
  98. package/lib/runtime-environment.mjs +6 -0
  99. package/lib/runtime_node.mjs +3 -2
  100. package/lib/runtime_state.mjs +39 -5
  101. package/lib/sources/prd.mjs +6 -3
  102. package/lib/state.mjs +137 -24
  103. package/lib/task_completion.mjs +7 -3
  104. package/lib/task_contract.mjs +201 -27
  105. package/lib/task_delivery.mjs +9 -2
  106. package/lib/task_delivery_contract.mjs +13 -9
  107. package/lib/task_delivery_view.mjs +18 -2
  108. package/lib/task_environment.mjs +13 -1
  109. package/lib/task_limits.mjs +13 -0
  110. package/lib/task_model_config.mjs +99 -0
  111. package/lib/task_model_route.mjs +243 -0
  112. package/lib/task_prepare.mjs +21 -8
  113. package/lib/task_profile.mjs +1875 -0
  114. package/lib/task_repository_context.mjs +65 -0
  115. package/lib/tasks.mjs +252 -51
  116. package/lib/tick.mjs +353 -97
  117. package/lib/time.mjs +1 -0
  118. package/lib/tracker/pages.mjs +19 -5
  119. package/lib/tracker/review.mjs +21 -0
  120. package/lib/tracker_process.mjs +8 -1
  121. package/lib/verification_sandbox.mjs +2 -1
  122. package/lib/work_artifacts.mjs +79 -0
  123. package/npm-shrinkwrap.json +12 -3
  124. package/package.json +9 -3
  125. package/templates/plugin/task-delivery.SKILL.md +17 -10
  126. package/templates/plugin/task-intake-contract.json +18 -0
  127. package/templates/plugin/task-profile-requests.mjs +1448 -0
  128. package/templates/tracker/tracker.css +3 -0
  129. 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`;
@@ -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>since</th><th></th></tr></thead><tbody>
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 ?? "unknown"} / ${entry.execution.reconciledAt ?? "never"}`) : ""),
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>Log</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 = ms => (ms ? new Date(ms).toISOString().replace("T", " ").slice(0, 19) : "unknown");
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
+ }
@@ -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
- const record = readTracker(ctx.home);
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, MAX_DIFF_BYTES } from "./gh.mjs";
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
+ }
@@ -1,25 +1,28 @@
1
1
  {
2
2
  "name": "@uchibeke/gallivant",
3
- "version": "0.2.1",
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.2.1",
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.2.1",
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 completion capabilities and select the creation-only
20
- completion.target: delivery_ready for a qualified reviewable delivery, or
21
- integrated when the user wants monitoring through an external merge. An omitted
22
- target defaults to delivery_ready. This field owns the PR/review/merge lifecycle;
23
- do not duplicate coordinator publication as a provider implementation criterion.
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
- The core preserves exact source ownership for supported publication clauses.
30
- If TASK_DELIVERY_SCOPE is returned, ask the owner to clarify the whole request;
31
- preserve prerequisites and prohibitions, and use a new preparation identity after
32
- clarification. Do not treat that refusal as permission to remove a condition or
33
- silently move a changed publication instruction into an implementation answer.
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
+ }