@bridge_gpt/mcp-server 0.2.69 → 0.2.70
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 +140 -292
- package/build/agent-launchers/claude-executor-adapter.js +17 -4
- package/build/agents.generated.js +4 -4
- package/build/artifact-diff.js +110 -0
- package/build/bin-entrypoint.js +36 -0
- package/build/bridge-permissions.js +23 -4
- package/build/claude-review-workflow-drift.js +3 -3
- package/build/codex-bridge-profile.js +1 -1
- package/build/commands.generated.js +6 -6
- package/build/conductor/bridge-api-client.js +598 -19
- package/build/conductor/bring-up-facts.js +9 -9
- package/build/conductor/doctor.js +466 -18
- package/build/conductor/epic-child-continuation.js +54 -3
- package/build/conductor/epic-implementer-cli.js +132 -34
- package/build/conductor/epic-runtime.js +2 -2
- package/build/conductor/epic-state.js +1 -1
- package/build/conductor/hook-runtime-qualification.js +1 -1
- package/build/conductor/install-doctor.js +4 -2
- package/build/conductor/launch-need-probes.js +337 -0
- package/build/conductor/local-merge.js +241 -56
- package/build/conductor/merge-credential.js +172 -0
- package/build/conductor/merge-execution-eligibility.js +1 -1
- package/build/conductor/merged-child-check.js +95 -0
- package/build/conductor/plan-trim.js +30 -0
- package/build/conductor/pr-ci-producer.js +1 -1
- package/build/conductor/readiness-cli.js +11 -6
- package/build/conductor/readiness-prerequisites.js +41 -0
- package/build/conductor/readiness-sections.js +345 -55
- package/build/conductor/readiness.js +99 -87
- package/build/conductor/recovery-cli.js +606 -21
- package/build/conductor/recovery-operations.js +306 -7
- package/build/conductor/repair-contract.js +47 -2
- package/build/conductor/repository-workflow-remedy.js +8 -6
- package/build/conductor/stored-merge-credential.js +76 -0
- package/build/conductor/stored-merge-token-probe.js +28 -0
- package/build/conductor/temporary-plan.js +50 -0
- package/build/conductor/wait-run-cli.js +759 -0
- package/build/conductor/worker-database-doctor-inspector.js +49 -0
- package/build/conductor-bin.js +2 -2
- package/build/conductor-claude-hook-bin.js +2 -2
- package/build/conductor-claude-hook-removed-stub-bin.js +2 -5
- package/build/conductor-removed-stub-bin.js +2 -5
- package/build/credential-store.js +356 -13
- package/build/docs.generated.js +26 -13
- package/build/doctor.js +94 -32
- package/build/drive-epic-help.js +212 -0
- package/build/drive-epic.js +1840 -373
- package/build/epic-ci-workflow-drift.js +77 -0
- package/build/epic-ci-workflow-install.js +183 -0
- package/build/epic-ci-workflow.js +157 -0
- package/build/epic-contract-context.js +326 -0
- package/build/epic-implementer-bin.js +57 -41
- package/build/epic-implementer-claude-hook-bin.js +2 -2
- package/build/escalation-reachability.js +134 -0
- package/build/executor/cleanup-report.js +99 -0
- package/build/executor/cli.js +12 -1
- package/build/executor/completion-contract.js +66 -11
- package/build/executor/completion-supervision.js +99 -0
- package/build/executor/delivery-outcome.js +179 -0
- package/build/executor/deps.js +3 -0
- package/build/executor/env.js +71 -0
- package/build/executor/failure-reason-codes.js +126 -0
- package/build/executor/heartbeat.js +38 -0
- package/build/executor/http-client.js +72 -0
- package/build/executor/integration-job.js +298 -5
- package/build/executor/job-errors.js +36 -6
- package/build/executor/job-runner.js +720 -36
- package/build/executor/likely-tests.js +36 -2
- package/build/executor/merge-job.js +145 -2
- package/build/executor/observation.js +73 -4
- package/build/executor/permissions.js +193 -97
- package/build/executor/preflight.js +14 -3
- package/build/executor/process-containment.js +28 -2
- package/build/executor/prompt-spec.js +50 -5
- package/build/executor/rebase-finalization.js +38 -3
- package/build/executor/reconcile-overlap-job.js +35 -7
- package/build/executor/required-work-ledger.js +236 -81
- package/build/executor/resume-pre-spawn.js +38 -1
- package/build/executor/runner.js +91 -3
- package/build/executor/test-clock.js +8 -0
- package/build/executor/types.js +37 -2
- package/build/executor/worker-cli-launcher.js +259 -0
- package/build/executor/worker-command.js +41 -4
- package/build/executor/worker-database-target.js +91 -0
- package/build/executor/worker-database.js +908 -0
- package/build/executor/worker-finalization.js +106 -0
- package/build/executor/worker-guard-hook.js +203 -0
- package/build/executor/worker-resources.js +47 -0
- package/build/executor/worker-verification-guidance.js +15 -2
- package/build/executor/worktree.js +303 -15
- package/build/executor-launch.js +11 -1
- package/build/generated-assets-doctor.js +9 -1
- package/build/generated-assets-health.js +13 -4
- package/build/generated-assets-legacy.js +39 -0
- package/build/generated-assets-paths.js +12 -1
- package/build/generated-assets-repair.js +4 -1
- package/build/generated-assets-test-support.js +12 -0
- package/build/github-remote.js +33 -0
- package/build/github-sign-in.js +62 -0
- package/build/index.js +1067 -536
- package/build/init.js +39 -1
- package/build/install-bridge-conductor-gate.js +78 -0
- package/build/install-bridge-conductor-setup.js +413 -0
- package/build/install-bridge-conductor.js +1226 -267
- package/build/install-bridge.js +376 -60
- package/build/mcp-install-state.js +8 -2
- package/build/merge-pull-request.js +6 -0
- package/build/merge-workflow-install.js +763 -345
- package/build/phase-result-artifacts.js +75 -20
- package/build/pipeline-orchestrator.js +247 -4
- package/build/pipelines.generated.js +35 -35
- package/build/plan-discovery.js +15 -2
- package/build/plan-execution-ledger.js +306 -17
- package/build/plan-phase-routing.js +48 -13
- package/build/plane/cli.js +105 -51
- package/build/plane/defaults.js +3 -0
- package/build/plane/plane-freshness.js +294 -0
- package/build/plane/schema-compatibility.js +137 -0
- package/build/plane/shutdown-deps.js +38 -0
- package/build/plane/shutdown.js +83 -8
- package/build/plane/terminal-run-cleanup.js +303 -0
- package/build/printed-remedy-commands.js +26 -4
- package/build/readiness-check.js +6 -2
- package/build/readiness-prerequisites.generated.js +532 -0
- package/build/readme.generated.js +1 -1
- package/build/setup-epic.js +400 -30
- package/build/shared-resources.generated.js +5 -0
- package/build/start-tickets.js +28 -15
- package/build/test-command-detection.js +247 -0
- package/build/test-command-validation.js +73 -0
- package/build/testing-config-client.js +150 -0
- package/build/ticket-delivery-artifacts.js +258 -0
- package/build/ticket-delivery-enrollment.js +306 -0
- package/build/ticket-delivery.js +205 -0
- package/build/ticket-review-summary.js +14 -0
- package/build/upgrade-cli.js +13 -0
- package/build/v2-readiness-requirements.js +552 -0
- package/build/version.generated.js +3 -3
- package/build/worker-guard-hook-bin.js +2 -2
- package/build/worktree-core.js +22 -7
- package/docs/BRIDGE-DIRECTORY-README.md +48 -0
- package/docs/EPIC-IMPLEMENTER-INSTALL.md +12 -867
- package/docs/EPIC-IMPLEMENTER.md +104 -63
- package/docs/epic-implementer/getting-started.md +131 -0
- package/docs/epic-implementer/self-hosting.md +133 -0
- package/package.json +3 -3
- package/pipelines/idea-to-ticket.json +1 -1
- package/pipelines/implement-ticket.json +8 -8
- package/pipelines/review-ticket.json +2 -2
- package/design-assets/favicon/favicon-32x32.png +0 -0
- package/design-assets/just-logo-rough-draft.png +0 -0
|
@@ -77,9 +77,25 @@ export function canonicalJson(value) {
|
|
|
77
77
|
export function canonicalSha256(value) {
|
|
78
78
|
return createHash("sha256").update(canonicalJson(value), "utf8").digest("hex");
|
|
79
79
|
}
|
|
80
|
-
/**
|
|
80
|
+
/** BAPI-1514 — lowercase SHA-256 hex of a driver context's exact UTF-8 bytes. */
|
|
81
|
+
export function driverContextSha256(context) {
|
|
82
|
+
return createHash("sha256").update(context, "utf8").digest("hex");
|
|
83
|
+
}
|
|
84
|
+
/** The context hash a binding set carries, or `undefined` when it has no context. */
|
|
85
|
+
export function bindingsDriverContextSha256(b) {
|
|
86
|
+
if (b.driverContext !== undefined)
|
|
87
|
+
return driverContextSha256(b.driverContext);
|
|
88
|
+
return b.driverContextSha256;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* The fingerprint input document (identical to the Python builder). The
|
|
92
|
+
* top-level `driver_context_sha256` key is present ONLY when the request carries
|
|
93
|
+
* driver context, so a context-free document is byte-identical to before.
|
|
94
|
+
*/
|
|
81
95
|
export function continuationFingerprintInput(b) {
|
|
96
|
+
const contextSha = bindingsDriverContextSha256(b);
|
|
82
97
|
return {
|
|
98
|
+
...(contextSha !== undefined ? { driver_context_sha256: contextSha } : {}),
|
|
83
99
|
v: CONTINUATION_WIRE_VERSION,
|
|
84
100
|
operation: CONTINUATION_OPERATION,
|
|
85
101
|
target: {
|
|
@@ -222,6 +238,25 @@ export function parseContinuationStatusResult(value) {
|
|
|
222
238
|
receipt,
|
|
223
239
|
};
|
|
224
240
|
}
|
|
241
|
+
/**
|
|
242
|
+
* BAPI-1514 — retryable CI-waiting refusals. The server committed nothing and
|
|
243
|
+
* asks for a retry with the SAME request id, so the local recovery reference is
|
|
244
|
+
* KEPT (not cleared) and the next continue-child re-sends that same request.
|
|
245
|
+
*/
|
|
246
|
+
export const CONTINUATION_CI_WAITING_REASONS = new Set([
|
|
247
|
+
"ci_pending_at_bound_head",
|
|
248
|
+
"ci_provider_read_unavailable",
|
|
249
|
+
"ci_observation_stale",
|
|
250
|
+
]);
|
|
251
|
+
/**
|
|
252
|
+
* BAPI-1514 — whether a continuation result is a REAL dispatch (a committed
|
|
253
|
+
* receipt for this request). Only this may be counted against an intervention
|
|
254
|
+
* budget or continuation allowance; `ci_green_adopted` and every other refusal
|
|
255
|
+
* dispatched nothing and charged nothing.
|
|
256
|
+
*/
|
|
257
|
+
export function continuationDispatchedJob(result) {
|
|
258
|
+
return result.outcome === "committed" && result.receipt !== null;
|
|
259
|
+
}
|
|
225
260
|
/** The namespace (subdirectory) of the parked-child continuation references. */
|
|
226
261
|
export const CONTINUATION_REFERENCE_NAMESPACE = "epic-child-continuations";
|
|
227
262
|
/**
|
|
@@ -304,6 +339,11 @@ function parseReference(raw) {
|
|
|
304
339
|
expectedHeadSha: (b.expectedHeadSha ?? null),
|
|
305
340
|
sourceParkGeneration: b.sourceParkGeneration,
|
|
306
341
|
};
|
|
342
|
+
if (b.driverContextSha256 !== undefined) {
|
|
343
|
+
if (typeof b.driverContextSha256 !== "string" || !FINGERPRINT_RE.test(b.driverContextSha256))
|
|
344
|
+
return null;
|
|
345
|
+
bindings.driverContextSha256 = b.driverContextSha256;
|
|
346
|
+
}
|
|
307
347
|
if (!bindings.repoName || !bindings.epicRunId || !bindings.ticketKey || !bindings.parkedGate ||
|
|
308
348
|
!nonNegInt(bindings.expectedRowVersion) || !nonNegInt(bindings.sourceParkGeneration) ||
|
|
309
349
|
(bindings.expectedHeadSha !== null && !FULL_SHA_RE.test(bindings.expectedHeadSha))) {
|
|
@@ -332,11 +372,22 @@ export async function readContinuationReference(epicRunId, ticketKey, deps = {})
|
|
|
332
372
|
const raw = await readRecoveryReferenceFile(CONTINUATION_REFERENCE_NAMESPACE, { epicRunId, ticketKey }, deps);
|
|
333
373
|
return raw === null ? null : parseReference(raw);
|
|
334
374
|
}
|
|
375
|
+
/**
|
|
376
|
+
* BAPI-1514 — the bindings as stored: the raw context replaced by its hash. A
|
|
377
|
+
* context-free binding set is returned unchanged, so its file is byte-identical.
|
|
378
|
+
*/
|
|
379
|
+
function persistableBindings(b) {
|
|
380
|
+
const contextSha = bindingsDriverContextSha256(b);
|
|
381
|
+
const { driverContext: _omitContext, driverContextSha256: _omitSha, ...rest } = b;
|
|
382
|
+
return contextSha === undefined ? rest : { ...rest, driverContextSha256: contextSha };
|
|
383
|
+
}
|
|
335
384
|
/** Durably write a reference (0700 directory, 0600 file, write-then-rename). */
|
|
336
385
|
export async function writeContinuationReference(reference, deps = {}, key = reference.bindings) {
|
|
337
386
|
// ``key`` only names the file (the caller may address the run by its epic key);
|
|
338
|
-
// the stored bindings are always the exact, fingerprinted ones.
|
|
339
|
-
|
|
387
|
+
// the stored bindings are always the exact, fingerprinted ones. BAPI-1514: the
|
|
388
|
+
// raw driver context is never written — only its SHA-256, which is all a
|
|
389
|
+
// reload needs to recompute the same fingerprint.
|
|
390
|
+
await writeRecoveryReferenceFile(CONTINUATION_REFERENCE_NAMESPACE, key, { ...reference, bindings: persistableBindings(reference.bindings) }, deps);
|
|
340
391
|
}
|
|
341
392
|
/** Remove a child's reference once its outcome is authoritative. Never throws. */
|
|
342
393
|
export async function clearContinuationReference(epicRunId, ticketKey, deps = {}) {
|
|
@@ -8,8 +8,10 @@
|
|
|
8
8
|
* epic-implementer doctor [--json]
|
|
9
9
|
* epic-implementer purge [--json]
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
11
|
+
* The ledger commands talk only to the local SQLite ledger; the run-control,
|
|
12
|
+
* status, readiness, and read-only wait commands (for example `wait-run`) are
|
|
13
|
+
* authenticated Bridge API calls, each lazily imported so a local-only command
|
|
14
|
+
* never loads the HTTP graph. Errors are surfaced as sanitized messages (no stack traces, no secrets, no raw payloads) and
|
|
13
15
|
* {@link runEpicImplementerCli} returns a process exit code rather than calling
|
|
14
16
|
* `process.exit` itself (the bin wrapper owns that).
|
|
15
17
|
*
|
|
@@ -28,13 +30,18 @@ import { SEMANTIC_EVENT_TYPES } from "./taxonomy.js";
|
|
|
28
30
|
import { DRIVER_CONTEXT_MAX_BYTES, POLICY_PATCH_MAX_BYTES } from "./repair-contract.js";
|
|
29
31
|
import { CONDUCTOR_BIN_UNRESOLVED_ERROR, installConductorGitHooks, resolveConductorHookBin, } from "./git-hooks.js";
|
|
30
32
|
import { HOOK_RUNTIME_PROBE_ACTION, HOOK_RUNTIME_PROBE_MARKER } from "./hook-runtime-qualification.js";
|
|
31
|
-
import { runFileScopeGuardCli } from "./file-scope-guard.js";
|
|
33
|
+
import { DECLARED_TOUCHED_FILES_ENV, runFileScopeGuardCli } from "./file-scope-guard.js";
|
|
32
34
|
import { runPostCommitHookProducer, runReferenceTransactionHookProducer } from "./git-producer.js";
|
|
33
35
|
import { buildConductorDoctorReport, formatConductorDoctorReport } from "./doctor.js";
|
|
36
|
+
import { nodeGithubMergeCredentialReadDeps, readLocalGithubRemote } from "./stored-merge-credential.js";
|
|
34
37
|
import { createCommandDriftInspector } from "./command-drift-production.js";
|
|
38
|
+
import { createWorkerDatabaseDoctorInspector } from "./worker-database-doctor-inspector.js";
|
|
35
39
|
import { writeJsonDocument } from "./json-output.js";
|
|
36
40
|
import { resolveSupervisorConfig } from "./supervisor-config.js";
|
|
37
41
|
import { VERSION } from "../version.generated.js";
|
|
42
|
+
// BAPI-1496: the inspector factory moved to its own module; re-exported so
|
|
43
|
+
// existing importers of this module keep working.
|
|
44
|
+
export { createWorkerDatabaseDoctorInspector };
|
|
38
45
|
// `runSupervisor` is imported LAZILY inside runSuperviseCommand: it transitively
|
|
39
46
|
// pulls in the SQLite store, and a static import here would force every
|
|
40
47
|
// epic-implementer CLI invocation (and every test that mocks ./store.js for the
|
|
@@ -62,7 +69,7 @@ export function getConductorUsage() {
|
|
|
62
69
|
"Local append-only event ledger for multi-agent coordination, plus run-control",
|
|
63
70
|
"commands. The ledger commands talk ONLY to the local SQLite store",
|
|
64
71
|
"(~/.config/bridge/events.db); the run-control commands (stop-run through",
|
|
65
|
-
"
|
|
72
|
+
"resolve-handoff), epic-status, readiness, and the read-only wait-run call the Bridge API.",
|
|
66
73
|
"",
|
|
67
74
|
"Commands:",
|
|
68
75
|
" emit-event Append one semantic event to the ledger",
|
|
@@ -113,7 +120,7 @@ export function getConductorUsage() {
|
|
|
113
120
|
" resume-run --epic-run-id <id> [--clear-quarantine] [--json]",
|
|
114
121
|
" Resume a paused run (a quarantined one only with --clear-quarantine);",
|
|
115
122
|
" activation is re-checked server-side. Active/no-control is a no-op.",
|
|
116
|
-
" continue-child --epic-run-id <id> --ticket-key <key> [--reason <text> --worker-exit-proof exited|fenced] [--json]",
|
|
123
|
+
" continue-child --epic-run-id <id> --ticket-key <key> [--reason <text> --worker-exit-proof exited|fenced] [--context-file <path>] [--json]",
|
|
117
124
|
" grant-continuation --epic-run-id <id> --ticket-key <key> --reason <text> [--extra-continuations 1] [--extra-seconds <seconds>] [--json]",
|
|
118
125
|
" Continue a parked child's CURRENT episode WITHOUT a reset: same attempt,",
|
|
119
126
|
" retained work, only the allowance left. At most one dispatch per episode.",
|
|
@@ -127,6 +134,20 @@ export function getConductorUsage() {
|
|
|
127
134
|
" Ask CI to re-run a ticket's FAILED checks at its bound head (guided: copy",
|
|
128
135
|
" the command explain-run rendered). Charges NO allowance and never re-runs",
|
|
129
136
|
" claude-review. A re-run is not a verdict: after CI is green, use resume-ci.",
|
|
137
|
+
" regenerate-plan --epic-run-id <id> --ticket-key <key> --route test|implementation --reason <text> [--json]",
|
|
138
|
+
" Answer a child parked on a NAMED plan failure with ONE forced plan",
|
|
139
|
+
" regeneration on the chosen route. Success means the override and gate",
|
|
140
|
+
" reset committed, not that a usable plan exists. Prints \"server too old\"",
|
|
141
|
+
" and exits nonzero on a server without the route; no fallback is tried.",
|
|
142
|
+
" resolve-handoff --epic-run-id <id> --ticket-key <key> --steps 27,36 --kind verified|risk_accepted",
|
|
143
|
+
" --evidence <ref> --reason <text> [--json]",
|
|
144
|
+
" Record the OWNER's decision on verification hand-offs (a human decision,",
|
|
145
|
+
" never automatic). Also remove .bridge/epic-workarounds/<KEY>.md in a new",
|
|
146
|
+
" epic-head commit: the PR leaves draft only when both holds are clear.",
|
|
147
|
+
" wait-run --epic-run-id <id> [--ticket-key <key>] [--since <token>] [--timeout <seconds>] [--json]",
|
|
148
|
+
" Read-only: wait until the run's observable state changes, the run finishes,",
|
|
149
|
+
" or the window closes (default 110 s; values above 240 are clamped to 240).",
|
|
150
|
+
" Pass the previous result's token as --since. Changes nothing.",
|
|
130
151
|
"",
|
|
131
152
|
"supervise options:",
|
|
132
153
|
" --run-id <id> Run/session identifier to supervise (required)",
|
|
@@ -198,7 +219,7 @@ export function getConductorUsage() {
|
|
|
198
219
|
" explicit skipped state (enforcement UNVERIFIED, never enforced)",
|
|
199
220
|
"",
|
|
200
221
|
"Examples:",
|
|
201
|
-
" epic-implementer emit-event --type run.started --source git-hook --run-id
|
|
222
|
+
" epic-implementer emit-event --type run.started --source git-hook --run-id PROJ-393 \\",
|
|
202
223
|
" --data-json '{\"summary\":\"run started\"}'",
|
|
203
224
|
" epic-implementer emit-event --type git.commit_created --source git-hook \\",
|
|
204
225
|
" --raw-json '{\"branch\":\"feature/x\",\"sha\":\"abc123\"}'",
|
|
@@ -265,17 +286,25 @@ export function getConductorUsage() {
|
|
|
265
286
|
" # The run is done for good — close it out:",
|
|
266
287
|
" epic-implementer abandon-run --epic-run-id 3f9c2b7e-...",
|
|
267
288
|
" # A ticket parked itself (needs_human) and the cause is now fixed:",
|
|
268
|
-
" epic-implementer unpark --epic-run-id 3f9c2b7e-... --ticket-key
|
|
289
|
+
" epic-implementer unpark --epic-run-id 3f9c2b7e-... --ticket-key PROJ-852",
|
|
269
290
|
" # A human pushed directly to the PR and the ticket parked on head drift:",
|
|
270
|
-
" epic-implementer adopt-current-head-and-unpark --epic-run-id 3f9c2b7e-... --ticket-key
|
|
291
|
+
" epic-implementer adopt-current-head-and-unpark --epic-run-id 3f9c2b7e-... --ticket-key PROJ-852",
|
|
271
292
|
" # A child stopped mid-attempt and should carry on WITHOUT resetting its budget:",
|
|
272
|
-
" epic-implementer continue-child --epic-run-id 3f9c2b7e-... --ticket-key
|
|
293
|
+
" epic-implementer continue-child --epic-run-id 3f9c2b7e-... --ticket-key PROJ-852",
|
|
273
294
|
" # The continue-child response was lost — did it commit?",
|
|
274
|
-
" epic-implementer continuation-receipt --epic-run-id 3f9c2b7e-... --ticket-key
|
|
295
|
+
" epic-implementer continuation-receipt --epic-run-id 3f9c2b7e-... --ticket-key PROJ-852",
|
|
275
296
|
" # CI finished green after the child parked on an expired CI wait:",
|
|
276
|
-
" epic-implementer resume-ci --epic-run-id 3f9c2b7e-... --ticket-key
|
|
297
|
+
" epic-implementer resume-ci --epic-run-id 3f9c2b7e-... --ticket-key PROJ-852",
|
|
277
298
|
" # CI failed on infrastructure: re-run it (never claude-review); then resume-ci once green:",
|
|
278
|
-
" epic-implementer rerun-ci --epic-run-id 3f9c2b7e-... --ticket-key
|
|
299
|
+
" epic-implementer rerun-ci --epic-run-id 3f9c2b7e-... --ticket-key PROJ-852 --binding <token>",
|
|
300
|
+
" # A child parked on a named plan failure (see explain-run for the route and reason):",
|
|
301
|
+
" epic-implementer regenerate-plan --epic-run-id 3f9c2b7e-... --ticket-key PROJ-852 \\",
|
|
302
|
+
" --route implementation --reason \"The test plan named a target that is not indexed\"",
|
|
303
|
+
" # The owner accepted the risk of a step a worker could not verify (verification incomplete):",
|
|
304
|
+
" epic-implementer resolve-handoff --epic-run-id 3f9c2b7e-... --ticket-key PROJ-852 --steps 27 \\",
|
|
305
|
+
" --kind risk_accepted --evidence owner-review-2026-10-11 --reason \"Owner accepts the risk\"",
|
|
306
|
+
" # Wait for the next change before the next full pass (read-only):",
|
|
307
|
+
" epic-implementer wait-run --epic-run-id 3f9c2b7e-... --since v1:0123456789abcdef",
|
|
279
308
|
].join("\n");
|
|
280
309
|
}
|
|
281
310
|
/**
|
|
@@ -334,6 +363,12 @@ const VALID_COMMANDS = new Set([
|
|
|
334
363
|
"resume-ci",
|
|
335
364
|
// BAPI-1388: CLI-only guided provider re-run of failed CI workflow runs — never an MCP tool.
|
|
336
365
|
"rerun-ci",
|
|
366
|
+
// BAPI-1494: CLI-only owner decision on verification hand-offs — never an MCP tool.
|
|
367
|
+
"resolve-handoff",
|
|
368
|
+
// BAPI-1532: CLI-only answer to a named plan failure — never an MCP tool.
|
|
369
|
+
"regenerate-plan",
|
|
370
|
+
// BAPI-1469: CLI-only read-only bounded wait on a run's state — never an MCP tool.
|
|
371
|
+
"wait-run",
|
|
337
372
|
// Private, and deliberately ABSENT from the usage text: `__hook-bin` exists as
|
|
338
373
|
// the bundled-artifact regression guard for BAPI-772 (mirroring `plane
|
|
339
374
|
// __entrypoint`), not as a supported operator workflow.
|
|
@@ -741,11 +776,28 @@ export async function runDoctorCommand(argv, deps = {}) {
|
|
|
741
776
|
// injected inspector wins so tests stay hermetic.
|
|
742
777
|
const doctorDeps = {
|
|
743
778
|
inspectCommandDrift: createCommandDriftInspector({ env: deps.env ?? process.env, cwd: process.cwd() }),
|
|
779
|
+
// BAPI-1479: advisory, read-only worker-database rows; runs under
|
|
780
|
+
// --no-deny-probe too. A caller's injected inspector wins.
|
|
781
|
+
inspectWorkerDatabase: createWorkerDatabaseDoctorInspector({ env: deps.env ?? process.env, cwd: process.cwd() }),
|
|
782
|
+
// BAPI-1461: a merge token stored once for this repository counts as
|
|
783
|
+
// configured — after the bounded capability probe confirms GitHub accepts
|
|
784
|
+
// it. Doctor runs in the person's own session, so the stored rung applies.
|
|
785
|
+
storedMergeCredential: {
|
|
786
|
+
allowStoredEntry: true,
|
|
787
|
+
localRemote: await readLocalGithubRemote(process.cwd()),
|
|
788
|
+
storeDeps: nodeGithubMergeCredentialReadDeps(deps.env ?? process.env),
|
|
789
|
+
// Loaded only when a stored token exists: the probe brings the merge
|
|
790
|
+
// executor's module graph, which an ordinary doctor run never needs.
|
|
791
|
+
probe: async (args) => {
|
|
792
|
+
const { probeStoredMergeToken } = await import("./stored-merge-token-probe.js");
|
|
793
|
+
return probeStoredMergeToken({ ...args, env: deps.env ?? process.env });
|
|
794
|
+
},
|
|
795
|
+
},
|
|
744
796
|
...deps,
|
|
745
797
|
};
|
|
746
798
|
const report = await buildConductorDoctorReport(bools.has("--no-deny-probe") ? { ...doctorDeps, skipDenyProbe: true } : doctorDeps);
|
|
747
799
|
if (bools.has("--json")) {
|
|
748
|
-
const written = await writeJsonDocument(JSON.stringify({ ...report.ledger, git_hooks: report.git_hooks, mcp_profile: report.mcp_profile, native_ledger: report.native_ledger, deny_enforcement: report.deny_enforcement, ...(report.command_drift ? { command_drift: report.command_drift } : {}) }));
|
|
800
|
+
const written = await writeJsonDocument(JSON.stringify({ ...report.ledger, git_hooks: report.git_hooks, mcp_profile: report.mcp_profile, native_ledger: report.native_ledger, deny_enforcement: report.deny_enforcement, ...(report.command_drift ? { command_drift: report.command_drift } : {}), ...(report.worker_database ? { worker_database: report.worker_database } : {}) }));
|
|
749
801
|
return written ? 0 : 1;
|
|
750
802
|
}
|
|
751
803
|
console.log(formatConductorDoctorReport(report));
|
|
@@ -1235,10 +1287,15 @@ export async function runEpicStatusCommand(argv) {
|
|
|
1235
1287
|
const dispatchRef = t.dispatch_run_id ? `dispatch_run_id=${t.dispatch_run_id}` : "-";
|
|
1236
1288
|
lines.push(` ${t.ticket_key} [${t.status}] ${dispatchRef} remediation=${t.remediation_attempts}/${t.remediation_no_progress_attempts}`);
|
|
1237
1289
|
}
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1290
|
+
if (state.dispatches === null) {
|
|
1291
|
+
lines.push("\nDispatches: not applicable to this run");
|
|
1292
|
+
}
|
|
1293
|
+
else {
|
|
1294
|
+
lines.push(`\nDispatches (${state.dispatches.length}):`);
|
|
1295
|
+
for (const d of state.dispatches) {
|
|
1296
|
+
const runRef = d.run_id ? `run_id=${d.run_id}` : "-";
|
|
1297
|
+
lines.push(` ${d.dispatch_key} [${d.status}] ${runRef}`);
|
|
1298
|
+
}
|
|
1242
1299
|
}
|
|
1243
1300
|
console.log(lines.join("\n"));
|
|
1244
1301
|
return 0;
|
|
@@ -1356,31 +1413,49 @@ export async function runPurgeCommand(argv) {
|
|
|
1356
1413
|
].join("\n"));
|
|
1357
1414
|
return 0;
|
|
1358
1415
|
}
|
|
1416
|
+
const LIKELY_TESTS_FAILURE_NOTE = "Likely-tests discovery unavailable: internal discovery failure (not an empty result).\n";
|
|
1359
1417
|
export async function runLikelyTestsCommand(argv, deps = {}) {
|
|
1360
|
-
const { resolveLikelyTests, isFullCommitSha, validTestReference, DISCOVERY_NOTES } = await import("../executor/likely-tests.js");
|
|
1361
1418
|
const stdout = deps.stdout ?? ((text) => { process.stdout.write(text); });
|
|
1362
1419
|
const stderr = deps.stderr ?? ((text) => { process.stderr.write(text); });
|
|
1363
|
-
let base;
|
|
1364
|
-
if (argv.length === 2 && argv[0] === "--base")
|
|
1365
|
-
base = argv[1];
|
|
1366
|
-
if (argv.length === 1 && argv[0].startsWith("--base="))
|
|
1367
|
-
base = argv[0].slice(7);
|
|
1368
|
-
if (!base || !isFullCommitSha(base)) {
|
|
1369
|
-
stderr(DISCOVERY_NOTES.invalid_arguments + "\n");
|
|
1370
|
-
return 0;
|
|
1371
|
-
}
|
|
1372
1420
|
try {
|
|
1373
|
-
const
|
|
1374
|
-
|
|
1375
|
-
|
|
1421
|
+
const { resolveLikelyTests, isFullCommitSha, validTestReference, DISCOVERY_NOTES } = await import("../executor/likely-tests.js");
|
|
1422
|
+
let base;
|
|
1423
|
+
if (argv.length === 2 && argv[0] === "--base")
|
|
1424
|
+
base = argv[1];
|
|
1425
|
+
if (argv.length === 1 && argv[0].startsWith("--base="))
|
|
1426
|
+
base = argv[0].slice(7);
|
|
1427
|
+
if (!base || !isFullCommitSha(base)) {
|
|
1428
|
+
stderr(DISCOVERY_NOTES.invalid_arguments + "\n");
|
|
1429
|
+
return 1;
|
|
1430
|
+
}
|
|
1431
|
+
const declared = (deps.env ?? process.env)[DECLARED_TOUCHED_FILES_ENV];
|
|
1432
|
+
const result = await (deps.resolve ?? resolveLikelyTests)({
|
|
1433
|
+
cwd: deps.cwd ?? process.cwd(),
|
|
1434
|
+
comparisonSha: base,
|
|
1435
|
+
...(declared !== undefined && declared.trim().length > 0 ? { declaredTouchedFilesJson: declared } : {}),
|
|
1436
|
+
});
|
|
1437
|
+
let printed = 0;
|
|
1438
|
+
for (const reference of result.references) {
|
|
1439
|
+
if (validTestReference(reference)) {
|
|
1376
1440
|
stdout(reference + "\n");
|
|
1377
|
-
|
|
1378
|
-
|
|
1441
|
+
printed += 1;
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
const failed = result.diagnostics.some(d => d === "git_unavailable" || d === "invalid_arguments");
|
|
1445
|
+
for (const diagnostic of new Set(result.diagnostics)) {
|
|
1446
|
+
if (diagnostic !== "empty")
|
|
1447
|
+
stderr(DISCOVERY_NOTES[diagnostic] + "\n");
|
|
1448
|
+
}
|
|
1449
|
+
if (failed)
|
|
1450
|
+
return 1;
|
|
1451
|
+
if (printed === 0)
|
|
1452
|
+
stderr(DISCOVERY_NOTES.empty + "\n");
|
|
1453
|
+
return 0;
|
|
1379
1454
|
}
|
|
1380
1455
|
catch {
|
|
1381
|
-
stderr(
|
|
1456
|
+
stderr(LIKELY_TESTS_FAILURE_NOTE);
|
|
1457
|
+
return 1;
|
|
1382
1458
|
}
|
|
1383
|
-
return 0;
|
|
1384
1459
|
}
|
|
1385
1460
|
/**
|
|
1386
1461
|
* Dispatch the epic-implementer CLI. Returns (asynchronously) a process exit
|
|
@@ -1521,6 +1596,29 @@ export async function runEpicImplementerCli(argv, likelyTestsDeps) {
|
|
|
1521
1596
|
const { runRerunCiCommand } = await import("./recovery-cli.js");
|
|
1522
1597
|
return await runRerunCiCommand(parsed.argv);
|
|
1523
1598
|
}
|
|
1599
|
+
case "resolve-handoff": {
|
|
1600
|
+
// BAPI-1494: delegates only to the owner-decision recovery operation.
|
|
1601
|
+
const { runResolveHandoffCommand } = await import("./recovery-cli.js");
|
|
1602
|
+
return await runResolveHandoffCommand(parsed.argv);
|
|
1603
|
+
}
|
|
1604
|
+
case "regenerate-plan": {
|
|
1605
|
+
// BAPI-1532: delegates only to the pinned plan-regeneration operation.
|
|
1606
|
+
const { runRegeneratePlanCommand } = await import("./recovery-cli.js");
|
|
1607
|
+
return await runRegeneratePlanCommand(parsed.argv);
|
|
1608
|
+
}
|
|
1609
|
+
case "wait-run": {
|
|
1610
|
+
// BAPI-1469: read-only bounded wait. Lazily imported like the recovery
|
|
1611
|
+
// verbs; a module-load failure gets fixed wording, never exception text.
|
|
1612
|
+
let runWaitRunCommand;
|
|
1613
|
+
try {
|
|
1614
|
+
({ runWaitRunCommand } = await import("./wait-run-cli.js"));
|
|
1615
|
+
}
|
|
1616
|
+
catch {
|
|
1617
|
+
console.error("Error: wait-run could not be loaded; no run state was read.");
|
|
1618
|
+
return 1;
|
|
1619
|
+
}
|
|
1620
|
+
return await runWaitRunCommand(parsed.argv);
|
|
1621
|
+
}
|
|
1524
1622
|
default:
|
|
1525
1623
|
console.error('Error: Unknown command. Run "epic-implementer --help" for usage.');
|
|
1526
1624
|
return 1;
|
|
@@ -668,7 +668,7 @@ export async function runEpicTick(options, deps = {}) {
|
|
|
668
668
|
// on the machine; rebuildObservedState only folds signals whose run_id maps
|
|
669
669
|
// to one of these dispatches, so scoping the read here avoids loading the
|
|
670
670
|
// entire (up to 50K-row) ledger on every tick.
|
|
671
|
-
const dispatchedRunIds = epicRunState.dispatches
|
|
671
|
+
const dispatchedRunIds = (epicRunState.dispatches ?? [])
|
|
672
672
|
.map((d) => d.run_id)
|
|
673
673
|
.filter((rid) => typeof rid === "string" && rid.length > 0);
|
|
674
674
|
const localEvents = await fetchLocalEvents(epic_key, dispatchedRunIds);
|
|
@@ -1005,7 +1005,7 @@ export async function runEpicTick(options, deps = {}) {
|
|
|
1005
1005
|
const latestDispatchByTicket = new Map();
|
|
1006
1006
|
const reviewLatestDispatchByTicket = new Map();
|
|
1007
1007
|
const reviewAttemptCounts = new Map();
|
|
1008
|
-
for (const d of epicRunState.dispatches) {
|
|
1008
|
+
for (const d of epicRunState.dispatches ?? []) {
|
|
1009
1009
|
const isReview = d.dispatch_key.endsWith(":review");
|
|
1010
1010
|
if (isReview) {
|
|
1011
1011
|
reviewAttemptCounts.set(d.ticket_key, (reviewAttemptCounts.get(d.ticket_key) ?? 0) + 1);
|
|
@@ -423,7 +423,7 @@ export function rebuildObservedState(postgresState, events, _now) {
|
|
|
423
423
|
// complete → proceed) instead of mis-folding it as implementation completion
|
|
424
424
|
// ("ready_for_review", awaiting merge), which would skip implementation.
|
|
425
425
|
const runIdToTicketKey = new Map();
|
|
426
|
-
for (const dispatch of dispatches) {
|
|
426
|
+
for (const dispatch of dispatches ?? []) {
|
|
427
427
|
if (dispatch.run_id) {
|
|
428
428
|
runIdToTicketKey.set(dispatch.run_id, {
|
|
429
429
|
ticketKey: dispatch.ticket_key,
|
|
@@ -89,7 +89,7 @@ export const HOOK_RUNTIME_ACTIONS = {
|
|
|
89
89
|
"from a persistent install.",
|
|
90
90
|
"unqualified-runtime": "the hook runtime is not a qualified persistent installation (supported: a persistent npm " +
|
|
91
91
|
`install of ${MCP_PACKAGE_NAME} ${HOOK_RUNTIME_COMPATIBLE_RANGE} on macOS or Linux with an even ` +
|
|
92
|
-
`Node LTS line >= ${HOOK_RUNTIME_MIN_NODE_MAJOR}). See
|
|
92
|
+
`Node LTS line >= ${HOOK_RUNTIME_MIN_NODE_MAJOR}). See docs/EPIC-IMPLEMENTER.md in the ${MCP_PACKAGE_NAME} package.`,
|
|
93
93
|
"node-unavailable": "`node` could not be launched from PATH, so the hook cannot run. Put a qualified Node on PATH " +
|
|
94
94
|
"and re-run `epic-implementer doctor`.",
|
|
95
95
|
"native-unavailable": "better-sqlite3 does not load for the hook's Node, so no event can be recorded. Reinstall the " +
|
|
@@ -129,7 +129,9 @@ export async function runConductorInstallDoctor(deps) {
|
|
|
129
129
|
}
|
|
130
130
|
else {
|
|
131
131
|
try {
|
|
132
|
-
readiness =
|
|
132
|
+
readiness = deps.epicRunId
|
|
133
|
+
? await fetchConductorReadiness(deps.access, deps.fetch, { epicRunId: deps.epicRunId })
|
|
134
|
+
: await fetchConductorReadiness(deps.access, deps.fetch);
|
|
133
135
|
}
|
|
134
136
|
catch (err) {
|
|
135
137
|
const kind = err instanceof ConductorBridgeApiError ? err.kind : "network";
|
|
@@ -158,7 +160,7 @@ export async function runConductorInstallDoctor(deps) {
|
|
|
158
160
|
// BAPI-1405 — the three warn-only epic-branch CI rows, beside the gate
|
|
159
161
|
// profile they qualify. Never fatal, so they cannot change the exit code or
|
|
160
162
|
// the installer's pre-write abort. An older server renders them as
|
|
161
|
-
// "Unreported
|
|
163
|
+
// "Unreported by the server", never as "no warning".
|
|
162
164
|
sections.push(...epicBranchCiSections(readiness.epic_branch_ci));
|
|
163
165
|
sections.push(githubCredentialsSection(readiness));
|
|
164
166
|
sections.push(githubActionsSection(readiness));
|