@bridge_gpt/mcp-server 0.2.66 → 0.2.67
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 +252 -77
- package/build/agent-capabilities/probes.js +15 -5
- package/build/agent-launchers/claude-executor-adapter.js +43 -16
- package/build/agent-launchers/executor-adapter.js +7 -4
- package/build/agents.generated.js +1 -1
- package/build/bounded-wait.js +67 -10
- package/build/commands.generated.js +11 -11
- package/build/conductor/bridge-api-client.js +143 -1
- package/build/conductor/bring-up-facts.js +1 -1
- package/build/conductor/doctor.js +3 -3
- package/build/conductor/epic-implementer-cli.js +22 -6
- package/build/conductor/errors.js +4 -3
- package/build/conductor/guided-repair.js +6 -2
- package/build/conductor/hook-runtime-qualification.js +1 -1
- package/build/conductor/merge-execution-contract.js +27 -8
- package/build/conductor/merge-execution-eligibility.js +3 -2
- package/build/conductor/pr-ci-producer.js +1 -1
- package/build/conductor/readiness-cli.js +18 -2
- package/build/conductor/readiness-sections.js +41 -6
- package/build/conductor/readiness.js +181 -29
- package/build/conductor/recovery-cli.js +200 -8
- package/build/conductor/recovery-operations.js +69 -3
- package/build/conductor/repair-contract.js +54 -3
- package/build/conductor/repository-workflow-remedy.js +125 -0
- package/build/conductor/tools.js +2 -2
- package/build/conductor-bundle-artifacts.js +8 -0
- package/build/docs.generated.js +8 -3
- package/build/drive-epic-key.js +6 -5
- package/build/drive-epic.js +85 -54
- package/build/epic-coherence-input.js +2 -1
- package/build/epic-implementer-bin.js +22 -20
- package/build/epic-implementer-bundle-cli.js +2 -2
- package/build/executor/cli.js +3 -2
- package/build/executor/completion-contract.js +48 -5
- package/build/executor/completion-supervision.js +4 -0
- package/build/executor/env.js +19 -0
- package/build/executor/heartbeat.js +1 -1
- package/build/executor/http-client.js +20 -0
- package/build/executor/job-errors.js +25 -0
- package/build/executor/job-runner.js +225 -41
- package/build/executor/observation.js +6 -1
- package/build/executor/preflight.js +51 -14
- package/build/executor/required-work-ledger.js +6 -1
- package/build/executor/resume-pre-spawn.js +2 -0
- package/build/executor/runner.js +8 -2
- package/build/executor/service-unit.js +7 -1
- package/build/executor/worker-finalization.js +82 -29
- package/build/executor/worker-guard-hook.js +137 -33
- package/build/executor/worktree.js +82 -1
- package/build/index.js +403 -429
- package/build/install-bridge-conductor.js +8 -6
- package/build/install-bridge.js +3 -3
- package/build/phase-context-git-probe.js +68 -0
- package/build/phase-result-artifacts.js +173 -5
- package/build/pipelines.generated.js +18 -24
- package/build/plane/alembic-head.js +12 -3
- package/build/plane/base-worktree-guard.js +147 -0
- package/build/plane/cli.js +217 -17
- package/build/plane/code-snapshot.js +468 -0
- package/build/plane/defaults.js +48 -3
- package/build/plane/endpoint.js +41 -0
- package/build/plane/manifest.js +120 -1
- package/build/plane/member-roster.js +94 -19
- package/build/plane/preflight.js +30 -2
- package/build/plane/process-cwd.js +67 -0
- package/build/plane/restart.js +542 -0
- package/build/plane/shutdown.js +31 -0
- package/build/plane/status.js +143 -8
- package/build/plane/supervisor.js +586 -179
- package/build/plane/types.js +54 -2
- package/build/printed-remedy-commands.js +37 -0
- package/build/readiness-check.js +1 -1
- package/build/readme.generated.js +1 -1
- package/build/review-generation.js +22 -0
- package/build/setup-epic.js +44 -19
- package/build/sfcc/config.js +9 -0
- package/build/sfcc/credentials.js +100 -38
- package/build/sfcc/hostname.js +67 -0
- package/build/sfcc/permissions.js +17 -13
- package/build/sfcc/register.js +1 -1
- package/build/sfcc/tool-wrapper.js +39 -43
- package/build/sfcc/write-grants.js +17 -11
- package/build/sfcc/write-result.js +4 -3
- package/build/sfcc/writes-site-preference.js +20 -8
- package/build/start-tickets.js +179 -31
- package/build/tool-error-envelope.js +61 -0
- package/build/version.generated.js +3 -3
- package/build/visual-diff.js +22 -1
- package/build/worker-guard-hook-bin.js +3 -6
- package/build/worker-guard-hook-main.js +47 -0
- package/build/worktree-core.js +130 -4
- package/docs/EPIC-IMPLEMENTER-INSTALL.md +849 -0
- package/docs/EPIC-IMPLEMENTER.md +662 -0
- package/docs/install/mcp-tool-integrations.md +24 -15
- package/docs/install/sfcc-integration.md +12 -4
- package/package.json +1 -1
- package/pipelines/plan-epic.json +0 -5
- package/pipelines/review-ticket.json +1 -1
- package/docs/CONDUCTOR.md +0 -605
- package/pipelines/idea-to-pr.json +0 -49
|
@@ -18,12 +18,12 @@
|
|
|
18
18
|
* callers: `recovery-cli.ts` and `plane/shutdown.ts`.
|
|
19
19
|
*/
|
|
20
20
|
import { randomUUID } from "crypto";
|
|
21
|
-
import { adoptCurrentHeadAndUnparkTicket, completeMergeChild, ConductorBridgeApiError, dispatchRemediation, fetchEpicRunState, mergeChild, patchPolicy, resumeRun, safeDiagnosticMessage, scopeCatchUp, scopeReparse, stopEpicRun, unparkEpicTicket, updateEpicRunStatus, fetchChildContinuationStatus, postContinueParkedChild, resumeCiTicket, REVIEW_HEAD_REFUSAL_CODES, } from "./bridge-api-client.js";
|
|
22
|
-
import { clearGuidedReference, guardForBinding, GUIDED_PAYLOAD_CONFLICT_MESSAGE, GUIDED_REFERENCE_STORE_FAILED_MESSAGE, prepareGuidedRequest, } from "./guided-repair.js";
|
|
21
|
+
import { adoptCurrentHeadAndUnparkTicket, completeMergeChild, ConductorBridgeApiError, dispatchRemediation, fetchEpicRunState, mergeChild, patchPolicy, RERUN_CI_ROUTE_UNAVAILABLE_CODE, RERUN_CI_ROUTE_UNAVAILABLE_MESSAGE, rerunCi, resumeRun, safeDiagnosticMessage, scopeCatchUp, scopeReparse, stopEpicRun, unparkEpicTicket, updateEpicRunStatus, fetchChildContinuationStatus, postContinueParkedChild, resumeCiTicket, REVIEW_HEAD_REFUSAL_CODES, } from "./bridge-api-client.js";
|
|
22
|
+
import { clearGuidedReference, guardForBinding, GUIDED_PAYLOAD_CONFLICT_MESSAGE, GUIDED_REFERENCE_STORE_FAILED_MESSAGE, guidedPayloadDigest, prepareGuidedRequest, } from "./guided-repair.js";
|
|
23
23
|
import { DEFAULT_GATE_NAME } from "./git-ci-types.js";
|
|
24
24
|
import { canonicalSha256, clearContinuationReference, clearRecoveryReferenceFile, computeContinuationFingerprint, readRecoveryReferenceFile, writeRecoveryReferenceFile, isFullSha, newContinuationRequestId, readContinuationReference, writeContinuationReference, } from "./epic-child-continuation.js";
|
|
25
25
|
import { resolveLocalMergeCredential, resolveLocalMergeMethod, runApprovedLocalMerge, } from "./local-merge.js";
|
|
26
|
-
import { RESUME_RUN_UNCONFIRMED_MESSAGE } from "./repair-contract.js";
|
|
26
|
+
import { RERUN_CI_UNCONFIRMED_REASON, RESUME_RUN_UNCONFIRMED_MESSAGE } from "./repair-contract.js";
|
|
27
27
|
/**
|
|
28
28
|
* Bounded total attempts for a ticket-recovery CAS retry loop. Each attempt
|
|
29
29
|
* begins with a fresh, authoritative state read — so this bounds full
|
|
@@ -501,6 +501,72 @@ export async function guidedRepairOnce(options, deps = {}) {
|
|
|
501
501
|
? { kind: "committed", result, requestId, referenceReused }
|
|
502
502
|
: { kind: "no_op", result, requestId, referenceReused };
|
|
503
503
|
}
|
|
504
|
+
/** The bounded unconfirmed text for a `rerun-ci` whose release write was lost. */
|
|
505
|
+
export const RERUN_CI_COMPENSATION_UNCONFIRMED_MESSAGE = "the server could not confirm whether the workflow runs it did not enqueue are still " +
|
|
506
|
+
"recorded for this head. Re-read the run explanation before choosing another action";
|
|
507
|
+
/**
|
|
508
|
+
* Send ONE guided `rerun-ci`, pinned to its displayed binding, through the shared
|
|
509
|
+
* guided-repair reference (stored BEFORE the first POST; a lost response re-sends
|
|
510
|
+
* the SAME request). The server decides eligibility and every per-check status;
|
|
511
|
+
* this layer only classifies the answer.
|
|
512
|
+
*/
|
|
513
|
+
export async function rerunCiRepair(access, options, deps = {}) {
|
|
514
|
+
const call = deps.rerunCi ?? rerunCi;
|
|
515
|
+
const outcome = await guidedRepairOnce({
|
|
516
|
+
binding: options.binding,
|
|
517
|
+
payloadDigest: guidedPayloadDigest({ ticket_key: options.ticketKey }),
|
|
518
|
+
send: (guard) => call(access, { epicRunId: options.epicRunId, ticketKey: options.ticketKey, guard }),
|
|
519
|
+
unconfirmedMessage: "the outcome could not be confirmed: the server may or may not have requested the " +
|
|
520
|
+
"re-runs. Re-run the SAME command to re-send the same pinned request, or inspect " +
|
|
521
|
+
"the run read-only",
|
|
522
|
+
}, deps);
|
|
523
|
+
switch (outcome.kind) {
|
|
524
|
+
case "committed":
|
|
525
|
+
case "no_op":
|
|
526
|
+
return {
|
|
527
|
+
kind: "applied",
|
|
528
|
+
result: outcome.result,
|
|
529
|
+
requestId: outcome.requestId,
|
|
530
|
+
referenceReused: outcome.referenceReused,
|
|
531
|
+
};
|
|
532
|
+
case "stale":
|
|
533
|
+
return {
|
|
534
|
+
kind: "stale",
|
|
535
|
+
code: outcome.code,
|
|
536
|
+
message: outcome.message,
|
|
537
|
+
requestId: outcome.requestId,
|
|
538
|
+
};
|
|
539
|
+
case "refused":
|
|
540
|
+
if (outcome.code === RERUN_CI_ROUTE_UNAVAILABLE_CODE) {
|
|
541
|
+
return { kind: "unsupported", message: RERUN_CI_ROUTE_UNAVAILABLE_MESSAGE };
|
|
542
|
+
}
|
|
543
|
+
if (outcome.code === "ROW_VERSION_CONFLICT" &&
|
|
544
|
+
outcome.message.includes(RERUN_CI_UNCONFIRMED_REASON)) {
|
|
545
|
+
return {
|
|
546
|
+
kind: "unconfirmed",
|
|
547
|
+
reason: "compensation_conflict",
|
|
548
|
+
message: RERUN_CI_COMPENSATION_UNCONFIRMED_MESSAGE,
|
|
549
|
+
requestId: outcome.requestId,
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
return {
|
|
553
|
+
kind: "refused",
|
|
554
|
+
code: outcome.code,
|
|
555
|
+
status: outcome.status,
|
|
556
|
+
message: outcome.message,
|
|
557
|
+
requestId: outcome.requestId,
|
|
558
|
+
};
|
|
559
|
+
case "unconfirmed":
|
|
560
|
+
return {
|
|
561
|
+
kind: "unconfirmed",
|
|
562
|
+
reason: "transport",
|
|
563
|
+
message: outcome.message,
|
|
564
|
+
requestId: outcome.requestId,
|
|
565
|
+
};
|
|
566
|
+
default:
|
|
567
|
+
return { kind: "unavailable", message: outcome.message };
|
|
568
|
+
}
|
|
569
|
+
}
|
|
504
570
|
const AUTHORITATIVE_OUTCOMES = new Set([
|
|
505
571
|
"committed",
|
|
506
572
|
"already_continued",
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The shared contract for the
|
|
2
|
+
* The shared contract for the eight operator repair verbs: BAPI-1154's
|
|
3
3
|
* `scope-reparse`, `dispatch-remediation`, and `merge-child`, BAPI-1176's
|
|
4
|
-
* `scope-catch-up`, `patch-policy`, and `resume-run`,
|
|
5
|
-
* ticket-addressed `resume-ci`.
|
|
4
|
+
* `scope-catch-up`, `patch-policy`, and `resume-run`, BAPI-1326's
|
|
5
|
+
* ticket-addressed `resume-ci`, and BAPI-1388's ticket-addressed `rerun-ci`.
|
|
6
6
|
*
|
|
7
7
|
* CLI-only, exactly like the BAPI-872 recovery verbs: none of these names is ever
|
|
8
8
|
* registered as an MCP tool (`index-static.test.ts` pins that). This module is the
|
|
@@ -22,6 +22,7 @@ export const VERB_SCOPE_CATCH_UP = "scope-catch-up";
|
|
|
22
22
|
export const VERB_PATCH_POLICY = "patch-policy";
|
|
23
23
|
export const VERB_RESUME_RUN = "resume-run";
|
|
24
24
|
export const VERB_RESUME_CI = "resume-ci";
|
|
25
|
+
export const VERB_RERUN_CI = "rerun-ci";
|
|
25
26
|
/** Every repair verb, in the order an operator meets them. */
|
|
26
27
|
export const OPERATOR_REPAIR_VERBS = [
|
|
27
28
|
VERB_SCOPE_REPARSE,
|
|
@@ -31,6 +32,7 @@ export const OPERATOR_REPAIR_VERBS = [
|
|
|
31
32
|
VERB_PATCH_POLICY,
|
|
32
33
|
VERB_RESUME_RUN,
|
|
33
34
|
VERB_RESUME_CI,
|
|
35
|
+
VERB_RERUN_CI,
|
|
34
36
|
];
|
|
35
37
|
/**
|
|
36
38
|
* The ONE cap on driver context, in UTF-8 BYTES — the same number the server
|
|
@@ -156,3 +158,52 @@ export const CI_RESUME_RECEIPT_RETENTION = "latest_only";
|
|
|
156
158
|
export function isCiResumeRefusalCode(code) {
|
|
157
159
|
return (typeof code === "string" && CI_RESUME_REFUSAL_CODES.includes(code));
|
|
158
160
|
}
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
// BAPI-1388 — `rerun-ci`
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
/**
|
|
165
|
+
* The closed `rerun-ci` refusal subset — mirrors `RERUN_CI_REFUSAL_CODES` in
|
|
166
|
+
* `operator_repair_contract.py`. It mints no new code: every member already
|
|
167
|
+
* exists in {@link OPERATOR_REPAIR_REFUSAL_CODES}.
|
|
168
|
+
*/
|
|
169
|
+
export const RERUN_CI_REFUSAL_CODES = [
|
|
170
|
+
"TICKET_NOT_IN_RUN",
|
|
171
|
+
"GUIDED_REPAIR_DISABLED",
|
|
172
|
+
"REPAIR_SUBJECT_STALE",
|
|
173
|
+
"RUN_NOT_DISPATCHABLE",
|
|
174
|
+
"TICKET_NOT_REMEDIABLE",
|
|
175
|
+
"REMEDIATION_JOB_IN_FLIGHT",
|
|
176
|
+
"REMEDIATION_LANE_BUSY",
|
|
177
|
+
"REMEDIATION_HELD",
|
|
178
|
+
"REMEDIATION_NO_PULL_REQUEST",
|
|
179
|
+
"ROW_VERSION_CONFLICT",
|
|
180
|
+
"DISPATCH_ENQUEUE_FAILED",
|
|
181
|
+
];
|
|
182
|
+
/** True when `code` is one of the closed `rerun-ci` refusal codes. */
|
|
183
|
+
export function isRerunCiRefusalCode(code) {
|
|
184
|
+
return (typeof code === "string" && RERUN_CI_REFUSAL_CODES.includes(code));
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* The bounded reason a `rerun-ci` `ROW_VERSION_CONFLICT` carries when releasing a
|
|
188
|
+
* reservation lost its guarded write. Whether the unenqueued workflow runs are
|
|
189
|
+
* still recorded is UNCONFIRMED; the CLI exits 3.
|
|
190
|
+
*/
|
|
191
|
+
export const RERUN_CI_UNCONFIRMED_REASON = "rerun_ci_unconfirmed";
|
|
192
|
+
/**
|
|
193
|
+
* The closed per-check outcome vocabulary. None of them says a check is green: a
|
|
194
|
+
* provider re-run is an action, not a verdict.
|
|
195
|
+
*/
|
|
196
|
+
export const RERUN_CI_CHECK_STATUSES = [
|
|
197
|
+
"enqueued",
|
|
198
|
+
"deduplicated",
|
|
199
|
+
"deferred",
|
|
200
|
+
"not_re_runnable",
|
|
201
|
+
];
|
|
202
|
+
/** Why one failed check is not re-run: a closed token. */
|
|
203
|
+
export const RERUN_CI_NOT_RUNNABLE_REASONS = [
|
|
204
|
+
"not_github_actions",
|
|
205
|
+
"no_workflow_run_id",
|
|
206
|
+
"claude_review_excluded",
|
|
207
|
+
];
|
|
208
|
+
/** The closed overall outcomes of an applied `rerun-ci`. */
|
|
209
|
+
export const RERUN_CI_OUTCOMES = ["enqueued", "partial", "nothing_to_enqueue"];
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/** The two fixed workflow files unattended auto-merge reads. Named once here. */
|
|
2
|
+
export const REVIEW_WORKFLOW_PATH = ".github/workflows/claude-review.yml";
|
|
3
|
+
export const CI_WORKFLOW_PATH = ".github/workflows/epic-implementer-ci.yml";
|
|
4
|
+
/** The migration guard's stable job id and its exact checker invocation. */
|
|
5
|
+
export const MIGRATION_GUARD_JOB_ID = "migration-guard";
|
|
6
|
+
export const MIGRATION_GUARD_CHECKER_PATH = "scripts/check_alembic_migration_graph.py";
|
|
7
|
+
export const MIGRATION_GUARD_INVOCATION = `python ${MIGRATION_GUARD_CHECKER_PATH}`;
|
|
8
|
+
const RETRY = "Run the readiness check again, or pass --attended. " +
|
|
9
|
+
"If it keeps failing, contact Bridge operations.";
|
|
10
|
+
const TEMPLATES = {
|
|
11
|
+
review_workflow_file: `${REVIEW_WORKFLOW_PATH} is missing from the default branch. Add it, or pass --attended.`,
|
|
12
|
+
review_run_id_marker: `${REVIEW_WORKFLOW_PATH} does not emit the run-id: marker in its verdict comment. ` +
|
|
13
|
+
"Add it, or pass --attended.",
|
|
14
|
+
review_reviewed_sha_marker: `${REVIEW_WORKFLOW_PATH} does not emit the Reviewed-SHA: marker in its verdict comment. ` +
|
|
15
|
+
"Add it, or pass --attended.",
|
|
16
|
+
review_workflow_unverifiable: `Bridge could not read ${REVIEW_WORKFLOW_PATH} on the default branch. ${RETRY}`,
|
|
17
|
+
ci_workflow_file: `${CI_WORKFLOW_PATH} is missing from the default branch. Add it, or pass --attended.`,
|
|
18
|
+
migration_guard_job: `${CI_WORKFLOW_PATH} has no job with id ${MIGRATION_GUARD_JOB_ID} running ${MIGRATION_GUARD_INVOCATION}. ` +
|
|
19
|
+
"It is required because this repository has alembic.ini on its default branch, " +
|
|
20
|
+
`and an equivalent checker at ${MIGRATION_GUARD_CHECKER_PATH} is required too. ` +
|
|
21
|
+
"Add that job, or pass --attended.",
|
|
22
|
+
migration_chain_unverifiable: "Bridge could not confirm whether this repository has alembic.ini on its default branch, " +
|
|
23
|
+
`so it could not decide if the ${MIGRATION_GUARD_JOB_ID} job is required. ${RETRY}`,
|
|
24
|
+
ci_workflow_unverifiable: `Bridge could not read ${CI_WORKFLOW_PATH} on the default branch. ${RETRY}`,
|
|
25
|
+
// An older server never reported whether the guard is conditional, so its
|
|
26
|
+
// guard has always been required. This states that requirement and NOTHING
|
|
27
|
+
// about alembic.ini: an older server observed no such file.
|
|
28
|
+
migration_guard_job_legacy: `${CI_WORKFLOW_PATH} has no job with id ${MIGRATION_GUARD_JOB_ID} running ${MIGRATION_GUARD_INVOCATION}. ` +
|
|
29
|
+
"Add that job, or pass --attended.",
|
|
30
|
+
};
|
|
31
|
+
/** Shown when the report is missing, incomplete, or not in a shape we can name. */
|
|
32
|
+
const UNVERIFIABLE_FALLBACK = "Bridge could not verify the review and CI workflows on the default branch. " + RETRY;
|
|
33
|
+
function classifyReview(w) {
|
|
34
|
+
if (w === null || w === undefined || typeof w !== "object")
|
|
35
|
+
return "malformed";
|
|
36
|
+
if (w.probe_succeeded !== true)
|
|
37
|
+
return ["review_workflow_unverifiable"];
|
|
38
|
+
if (w.workflow_present === false)
|
|
39
|
+
return ["review_workflow_file"];
|
|
40
|
+
if (w.workflow_present !== true ||
|
|
41
|
+
typeof w.emits_run_id !== "boolean" ||
|
|
42
|
+
typeof w.emits_reviewed_sha !== "boolean") {
|
|
43
|
+
return "malformed";
|
|
44
|
+
}
|
|
45
|
+
const missing = [];
|
|
46
|
+
if (!w.emits_run_id)
|
|
47
|
+
missing.push("review_run_id_marker");
|
|
48
|
+
if (!w.emits_reviewed_sha)
|
|
49
|
+
missing.push("review_reviewed_sha_marker");
|
|
50
|
+
return missing;
|
|
51
|
+
}
|
|
52
|
+
function classifyCi(c) {
|
|
53
|
+
if (c === null || c === undefined || typeof c !== "object")
|
|
54
|
+
return "malformed";
|
|
55
|
+
if (c.probe_succeeded !== true)
|
|
56
|
+
return ["ci_workflow_unverifiable"];
|
|
57
|
+
if (c.workflow_present === false)
|
|
58
|
+
return ["ci_workflow_file"];
|
|
59
|
+
if (c.workflow_present !== true || typeof c.migration_guard_present !== "boolean") {
|
|
60
|
+
return "malformed";
|
|
61
|
+
}
|
|
62
|
+
if (c.migration_guard_present)
|
|
63
|
+
return [];
|
|
64
|
+
const required = c.migration_guard_required;
|
|
65
|
+
const chainProbed = c.migration_chain_probe_succeeded;
|
|
66
|
+
// Older server: neither field reported. The guard was always required there,
|
|
67
|
+
// and the absent fields must never read as "not required".
|
|
68
|
+
if (required === undefined && chainProbed === undefined) {
|
|
69
|
+
return ["migration_guard_job_legacy"];
|
|
70
|
+
}
|
|
71
|
+
if (typeof required !== "boolean" || typeof chainProbed !== "boolean")
|
|
72
|
+
return "malformed";
|
|
73
|
+
if (chainProbed && !required)
|
|
74
|
+
return []; // confirmed guard-free
|
|
75
|
+
if (chainProbed && required)
|
|
76
|
+
return ["migration_guard_job"];
|
|
77
|
+
return ["migration_chain_unverifiable"];
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Name the first failing workflow section of a parsed readiness report, its
|
|
81
|
+
* missing facts in stable order, and one fixed-template message.
|
|
82
|
+
*
|
|
83
|
+
* `section: null` with the fallback text means the report is missing or malformed
|
|
84
|
+
* — "could not verify", never "fine". An empty `missing` with `section: null`
|
|
85
|
+
* cannot occur: a report with nothing failing yields the fallback only when it is
|
|
86
|
+
* unreadable, and otherwise the empty result below.
|
|
87
|
+
*/
|
|
88
|
+
export function describeRepositoryWorkflowRemedy(readiness) {
|
|
89
|
+
const review = classifyReview(readiness?.review_workflow);
|
|
90
|
+
if (review === "malformed") {
|
|
91
|
+
return { section: null, missing: [], text: UNVERIFIABLE_FALLBACK };
|
|
92
|
+
}
|
|
93
|
+
if (review.length > 0)
|
|
94
|
+
return build("review_workflow", review);
|
|
95
|
+
const ci = classifyCi(readiness?.conductor_ci_workflow);
|
|
96
|
+
if (ci === "malformed") {
|
|
97
|
+
return { section: null, missing: [], text: UNVERIFIABLE_FALLBACK };
|
|
98
|
+
}
|
|
99
|
+
if (ci.length > 0)
|
|
100
|
+
return build("conductor_ci_workflow", ci);
|
|
101
|
+
return { section: null, missing: [], text: "" };
|
|
102
|
+
}
|
|
103
|
+
function build(section, missing) {
|
|
104
|
+
return { section, missing, text: missing.map((token) => TEMPLATES[token]).join(" ") };
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* The remedy sentence for a repository whose workflows are not confirmed. Never
|
|
108
|
+
* empty: a report with nothing to name (the server said unconfirmed but this
|
|
109
|
+
* client can name no item) gets the unverifiable fallback rather than silence.
|
|
110
|
+
*/
|
|
111
|
+
export function repositoryWorkflowRemedy(readiness) {
|
|
112
|
+
const described = describeRepositoryWorkflowRemedy(readiness);
|
|
113
|
+
return described.text === "" ? UNVERIFIABLE_FALLBACK : described.text;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* The remedy for the CI workflow section alone — what the readiness CI row shows
|
|
117
|
+
* when the migration guard is absent. Same templates, same words as
|
|
118
|
+
* {@link repositoryWorkflowRemedy}, so the row and the refusals cannot drift.
|
|
119
|
+
*/
|
|
120
|
+
export function ciWorkflowRemedy(c) {
|
|
121
|
+
const tokens = classifyCi(c);
|
|
122
|
+
if (tokens === "malformed" || tokens.length === 0)
|
|
123
|
+
return UNVERIFIABLE_FALLBACK;
|
|
124
|
+
return tokens.map((token) => TEMPLATES[token]).join(" ");
|
|
125
|
+
}
|
package/build/conductor/tools.js
CHANGED
|
@@ -82,9 +82,9 @@ function registerExplainRunTool(registerTool) {
|
|
|
82
82
|
// is the field list: a caller cannot discover the answer shape from a
|
|
83
83
|
// tool that only says "explain a run", and the whole point of the tool is
|
|
84
84
|
// that the answer is in one place.
|
|
85
|
-
description: "Explain why
|
|
85
|
+
description: "Explain why an Epic Implementer run, or one ticket in it, is stuck. One snapshot: " +
|
|
86
86
|
"current gate, latest observation and age, outstanding jobs with queue position, " +
|
|
87
|
-
"lease owner/expiry, blocker reasons, expected
|
|
87
|
+
"lease owner/expiry, blocker reasons, expected/observed head SHA, committed and " +
|
|
88
88
|
"pending disposition, usage ceiling. Pass ticket_key to narrow. Read-only; 404 " +
|
|
89
89
|
"returns status 'unknown'.",
|
|
90
90
|
inputSchema: {
|
|
@@ -27,6 +27,14 @@ import { validateTicketKey } from "./ticket-key-utils.js";
|
|
|
27
27
|
const EPIC_SLUG_RE = /^[A-Za-z][A-Za-z0-9_-]*$/;
|
|
28
28
|
/** Placeholder node identity written by `decompose-epic.md` before tickets exist. */
|
|
29
29
|
const PLACEHOLDER_RE = /TBD-\d+/;
|
|
30
|
+
/**
|
|
31
|
+
* A whole plan-node key that is still an authoring placeholder (`TBD-<digits>`),
|
|
32
|
+
* case-insensitive. Anchored on purpose and stateless (no `g` or `y` flag, so
|
|
33
|
+
* `.test()` carries no `lastIndex`). This is NOT {@link PLACEHOLDER_RE}: that one
|
|
34
|
+
* is an unanchored scan of a whole serialized plan, and
|
|
35
|
+
* {@link findResidualPlaceholder} keeps using it unchanged.
|
|
36
|
+
*/
|
|
37
|
+
export const PLACEHOLDER_NODE_KEY_RE = /^TBD-\d+$/i;
|
|
30
38
|
/**
|
|
31
39
|
* The conductor's hard spec bound, mirroring `_SPEC_REVIEW_TICKET_SPEC_MAX_CHARS`
|
|
32
40
|
* (64,000) in `api/library/epic_implementer/reconciler.py`. Since PR #848 the
|