@bridge_gpt/mcp-server 0.2.65 → 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 +269 -82
- package/build/agent-capabilities/probes.js +15 -5
- package/build/agent-launchers/claude-executor-adapter.js +55 -34
- package/build/agent-launchers/executor-adapter.js +34 -4
- package/build/agents.generated.js +1 -1
- package/build/bounded-wait.js +67 -10
- package/build/bridge-config.js +104 -0
- package/build/codex-bridge-profile.js +10 -1
- package/build/commands.generated.js +12 -12
- package/build/conductor/bridge-api-client.js +372 -10
- package/build/conductor/bring-up-facts.js +1 -1
- package/build/conductor/doctor.js +3 -3
- package/build/conductor/epic-child-continuation.js +61 -34
- package/build/conductor/epic-implementer-cli.js +34 -6
- package/build/conductor/errors.js +4 -3
- package/build/conductor/guided-repair.js +327 -0
- 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 +46 -9
- package/build/conductor/readiness-sections.js +41 -6
- package/build/conductor/readiness.js +204 -30
- package/build/conductor/recovery-cli.js +703 -20
- package/build/conductor/recovery-operations.js +392 -3
- package/build/conductor/repair-contract.js +112 -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/credential-store.js +5 -4
- package/build/diagnostic-text.js +36 -0
- package/build/docs.generated.js +8 -3
- package/build/doctor.js +70 -10
- package/build/drive-epic-key.js +6 -5
- package/build/drive-epic.js +479 -131
- package/build/epic-coherence-input.js +2 -1
- package/build/epic-implementer-bin.js +30 -26
- package/build/epic-implementer-bundle-cli.js +2 -2
- package/build/epic-launch-diagnostic.js +31 -0
- package/build/epic-launch-profile.js +44 -14
- package/build/executor/cli.js +3 -2
- package/build/executor/completion-contract.js +48 -5
- package/build/executor/completion-supervision.js +9 -0
- package/build/executor/correction-dispatch.js +60 -0
- package/build/executor/env.js +25 -0
- package/build/executor/heartbeat.js +1 -1
- package/build/executor/http-client.js +35 -0
- package/build/executor/integration-job.js +6 -1
- package/build/executor/job-errors.js +38 -4
- package/build/executor/job-log-registry.js +340 -73
- package/build/executor/job-runner.js +485 -74
- package/build/executor/job-types.js +8 -13
- package/build/executor/observation.js +6 -1
- package/build/executor/preflight.js +136 -10
- package/build/executor/prompt-spec.js +113 -9
- package/build/executor/required-work-ledger.js +6 -1
- package/build/executor/resume-pre-spawn.js +2 -0
- package/build/executor/runner.js +31 -2
- package/build/executor/service-unit.js +7 -1
- package/build/executor/stale-artifacts.js +5 -4
- package/build/executor/types.js +1 -0
- package/build/executor/watch-cli.js +35 -8
- package/build/executor/worker-command.js +43 -43
- package/build/executor/worker-finalization.js +161 -43
- package/build/executor/worker-guard-hook.js +152 -12
- package/build/executor/worker-log.js +137 -70
- package/build/executor/worktree-gc.js +30 -6
- package/build/executor/worktree.js +82 -1
- package/build/index.js +482 -660
- package/build/init.js +107 -4
- package/build/install-bridge-conductor.js +8 -6
- package/build/install-bridge.js +287 -23
- package/build/install-doctor.js +172 -30
- package/build/install-reexec.js +69 -7
- package/build/macos-terminal-permission-guidance.js +16 -0
- package/build/model-thresholds-tool.js +185 -0
- package/build/phase-context-git-probe.js +68 -0
- package/build/phase-result-artifacts.js +173 -5
- package/build/pipelines.generated.js +19 -25
- 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 +145 -6
- package/build/plane/supervisor.js +588 -177
- package/build/plane/types.js +54 -2
- package/build/plane/worker-exit-code.js +2 -0
- package/build/printed-remedy-commands.js +37 -0
- package/build/project-root.js +99 -0
- package/build/readiness-check.js +1 -1
- package/build/readme.generated.js +1 -1
- package/build/review-generation.js +22 -0
- package/build/review-tickets.js +156 -13
- package/build/setup-epic.js +224 -127
- 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/setup-status.js +2 -0
- package/build/sfcc/tool-wrapper.js +41 -43
- package/build/sfcc/tools-configured.js +96 -0
- 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 +332 -79
- package/build/tool-error-envelope.js +61 -0
- package/build/upgrade-cli.js +109 -24
- package/build/version.generated.js +3 -3
- package/build/visual-diff.js +22 -1
- package/build/windows-npm-entry.js +103 -0
- package/build/worker-guard-hook-bin.js +3 -5
- package/build/worker-guard-hook-main.js +47 -0
- package/build/worktree-core.js +136 -9
- package/docs/EPIC-IMPLEMENTER-INSTALL.md +849 -0
- package/docs/EPIC-IMPLEMENTER.md +662 -0
- package/docs/install/mcp-tool-integrations.md +25 -15
- package/docs/install/sfcc-integration.md +21 -8
- package/package.json +1 -1
- package/pipelines/implement-ticket.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
|
@@ -435,19 +435,29 @@ export async function runDenyEnforcementCheck(ctx, opts) {
|
|
|
435
435
|
// surface here — otherwise the agent never attempted the canary Bash call and
|
|
436
436
|
// a marker-free deny run would be a refusal, not enforcement (NEVER a pass).
|
|
437
437
|
const controlDir = await ctx.makeTempProject((d) => seedDenyTargetFile(ctx, d, marker));
|
|
438
|
-
|
|
439
|
-
|
|
438
|
+
let controlRun = await ctx.runHeadless({ prompt, cwd: controlDir, ...runOpts });
|
|
439
|
+
let c = evaluateDenyRun(controlRun, marker);
|
|
440
|
+
if (c.kind === "blocked") {
|
|
441
|
+
// A markerless control exit can be a one-off refusal to attempt the call (for
|
|
442
|
+
// example a session-state hiccup in the spawned agent). Retry exactly once before
|
|
443
|
+
// giving up, so one unattempted canary does not decide the verdict.
|
|
444
|
+
controlRun = await ctx.runHeadless({ prompt, cwd: controlDir, ...runOpts });
|
|
445
|
+
c = evaluateDenyRun(controlRun, marker);
|
|
446
|
+
}
|
|
440
447
|
if (c.kind === "nonexited")
|
|
441
448
|
return { result: withDenyLayer(c.result, "none"), layer: "none" };
|
|
442
449
|
if (c.kind === "error")
|
|
443
450
|
return { result: withDenyLayer(c.result, "none"), layer: "none" };
|
|
444
451
|
if (c.kind === "blocked") {
|
|
452
|
+
// Still claim-fatal: unverified enforcement is never a pass. The word
|
|
453
|
+
// `inconclusive` says the agent never attempted the call, so nothing was learned
|
|
454
|
+
// about the deny layer, and no deny-rule run is made.
|
|
445
455
|
return {
|
|
446
456
|
result: withDenyLayer({
|
|
447
457
|
status: "fail",
|
|
448
|
-
detail: "control run (no deny rule) did not execute the canary
|
|
449
|
-
"attempted the Bash command, so deny enforcement cannot be
|
|
450
|
-
"(a refusal is NOT enforcement)",
|
|
458
|
+
detail: "inconclusive: the control run (no deny rule) twice did not execute the canary " +
|
|
459
|
+
"call — the agent never attempted the Bash command, so deny enforcement cannot be " +
|
|
460
|
+
"verified (a refusal is NOT enforcement)",
|
|
451
461
|
elapsedMs: controlRun.elapsedMs,
|
|
452
462
|
evidence: c.evidence,
|
|
453
463
|
}, "none"),
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { isModelTier, isValidModelAlias, resolveModelAlias } from "../agent-registry.js";
|
|
2
1
|
import { evaluateClaudeMcpShadowingPolicy, inspectClaudeUserConfigForMcpShadowing, resolveClaudeUserConfigPath, } from "../claude-user-config-doctor.js";
|
|
3
|
-
import {
|
|
2
|
+
import { createDefaultAgentCapabilitiesDeps } from "../agent-capabilities/default-deps.js";
|
|
3
|
+
import { runDenyEnforcementPreflight, } from "../conductor/deny-enforcement-preflight.js";
|
|
4
4
|
import { buildExecutorBaseWorkerEnv } from "../executor/env.js";
|
|
5
5
|
import { ExecutorNamedError } from "../executor/job-errors.js";
|
|
6
6
|
import { provisionExecutorDenyLayer } from "../executor/permissions.js";
|
|
7
7
|
import { WORKER_GUARD_ROOT_FLAG } from "../executor/worker-guard-hook.js";
|
|
8
8
|
import { SUPPORTED_EXECUTOR_PLATFORMS, evaluateExecutorPlatform } from "../executor/platform.js";
|
|
9
9
|
import { normalizeMcpServerNames } from "../executor/observation.js";
|
|
10
|
-
import { COMPLETION_EVIDENCE_TRANSPORT, supported } from "./executor-adapter.js";
|
|
11
|
-
import { SUPPORTED_COMPLETION_RUNTIME, boundedIdentifier, } from "../executor/completion-contract.js";
|
|
10
|
+
import { COMPLETION_EVIDENCE_TRANSPORT, resolveExecutorModelAlias, supported, } from "./executor-adapter.js";
|
|
11
|
+
import { EVENT_SCHEMA, MIN_CLI_VERSION, SUPPORTED_COMPLETION_RUNTIME, SUPPORTED_PLATFORMS, boundedIdentifier, normalizeCliVersion, } from "../executor/completion-contract.js";
|
|
12
12
|
import { MCP_SERVER_NAME } from "../mcp-identity.js";
|
|
13
13
|
/** Stable identity of this reference implementation. */
|
|
14
14
|
export const CLAUDE_ADAPTER_ID = "claude-reference";
|
|
@@ -18,6 +18,11 @@ export const CLAUDE_ADAPTER_STRATEGY_ID = "claude-strict-mcp-v1";
|
|
|
18
18
|
export const CLAUDE_ADAPTER_VERSION = "1.0.0";
|
|
19
19
|
/** The one operator-owned credential name this adapter forwards. */
|
|
20
20
|
export const CLAUDE_OAUTH_TOKEN_ENV = "CLAUDE_CODE_OAUTH_TOKEN";
|
|
21
|
+
/**
|
|
22
|
+
* Claude Code setting that stops the CLI from auto-backgrounding long commands
|
|
23
|
+
* (BAPI-1364). Applied to legacy `-p <prompt>` spawns only.
|
|
24
|
+
*/
|
|
25
|
+
export const CLAUDE_DISABLE_BACKGROUND_TASKS_ENV = "CLAUDE_CODE_DISABLE_BACKGROUND_TASKS";
|
|
21
26
|
/** The provisioned MCP server a Claude worker requires. */
|
|
22
27
|
export const CLAUDE_REQUIRED_MCP_SERVER_NAME = MCP_SERVER_NAME;
|
|
23
28
|
/** Fixed replacement written in place of a redacted secret value. */
|
|
@@ -159,21 +164,12 @@ export function classifyClaudeAuthFailure(stdoutExcerpt) {
|
|
|
159
164
|
* when it passes both registry alias validation AND the spec's static allowlist;
|
|
160
165
|
* anything else falls through to tier resolution, and an unusable result is null
|
|
161
166
|
* (meaning "omit `--model`") rather than an unvalidated passthrough.
|
|
167
|
+
*
|
|
168
|
+
* BAPI-1343: the rule now lives in the agent-neutral
|
|
169
|
+
* {@link resolveExecutorModelAlias}; this export delegates with no behavior change.
|
|
162
170
|
*/
|
|
163
171
|
export function resolveClaudeModelAlias(spec, payload) {
|
|
164
|
-
|
|
165
|
-
const directRaw = p.model_alias;
|
|
166
|
-
if (typeof directRaw === "string" && directRaw.trim().length > 0) {
|
|
167
|
-
const direct = directRaw.trim();
|
|
168
|
-
const allowed = !spec.staticModelAliasAllowlist || spec.staticModelAliasAllowlist.includes(direct);
|
|
169
|
-
if (isValidModelAlias(direct) && allowed) {
|
|
170
|
-
return direct;
|
|
171
|
-
}
|
|
172
|
-
// An invalid/disallowed direct alias falls through to the tier path — never
|
|
173
|
-
// returned as-is.
|
|
174
|
-
}
|
|
175
|
-
const tier = isModelTier(p.model_tier) ? p.model_tier : null;
|
|
176
|
-
return resolveModelAlias(spec, tier, null);
|
|
172
|
+
return resolveExecutorModelAlias(spec, payload);
|
|
177
173
|
}
|
|
178
174
|
// ---------------------------------------------------------------------------
|
|
179
175
|
// Spawn shape
|
|
@@ -279,14 +275,14 @@ export const CLAUDE_RUNTIME_PROBE_TIMEOUT_MS = 15_000;
|
|
|
279
275
|
/** Placeholder for a version the probe could not recognize — never matches. */
|
|
280
276
|
export const UNRECOGNIZED_CLI_VERSION = "unrecognized";
|
|
281
277
|
/**
|
|
282
|
-
* Parse `claude --version` output
|
|
283
|
-
*
|
|
284
|
-
*
|
|
278
|
+
* Parse `claude --version` output such as `2.1.282 (Claude Code)` into `2.1.282`,
|
|
279
|
+
* using the same strict normalization as the completion-contract predicate.
|
|
280
|
+
* Prereleases and anything malformed are null — the raw text is never carried,
|
|
281
|
+
* because it would travel into logs and evidence.
|
|
285
282
|
*/
|
|
286
283
|
export function parseClaudeCliVersion(stdout) {
|
|
287
284
|
const firstLine = (typeof stdout === "string" ? stdout : "").trim().split("\n")[0]?.trim() ?? "";
|
|
288
|
-
|
|
289
|
-
return match ? match[1] : null;
|
|
285
|
+
return normalizeCliVersion(firstLine);
|
|
290
286
|
}
|
|
291
287
|
/**
|
|
292
288
|
* The completion platform id for a host: `darwin` + `arm64` is `darwin-arm64`.
|
|
@@ -321,7 +317,7 @@ export async function probeClaudeCompletionRuntime(command, deps) {
|
|
|
321
317
|
adapter: SUPPORTED_COMPLETION_RUNTIME.adapter,
|
|
322
318
|
cli_version: parseClaudeCliVersion(output.stdout) ?? UNRECOGNIZED_CLI_VERSION,
|
|
323
319
|
platform: completionPlatformId(deps.platform ?? process.platform, deps.arch ?? process.arch),
|
|
324
|
-
event_schema:
|
|
320
|
+
event_schema: EVENT_SCHEMA,
|
|
325
321
|
};
|
|
326
322
|
}
|
|
327
323
|
/**
|
|
@@ -411,9 +407,10 @@ function shellQuoteHookPath(value) {
|
|
|
411
407
|
* where the executor's env deny-list can guarantee an ambient value never
|
|
412
408
|
* substitutes for the job's own.
|
|
413
409
|
*/
|
|
414
|
-
export function buildWorkerGuardHookCommand(binPath, execPath, worktreePath) {
|
|
410
|
+
export function buildWorkerGuardHookCommand(binPath, execPath, worktreePath, correctionIntentVersion) {
|
|
415
411
|
return (`${shellQuoteHookPath(execPath)} ${shellQuoteHookPath(binPath)} ` +
|
|
416
|
-
`${WORKER_GUARD_ROOT_FLAG} ${shellQuoteHookPath(worktreePath)}`
|
|
412
|
+
`${WORKER_GUARD_ROOT_FLAG} ${shellQuoteHookPath(worktreePath)}` +
|
|
413
|
+
(correctionIntentVersion === 1 ? " --correction-intent-version 1" : ""));
|
|
417
414
|
}
|
|
418
415
|
// ---------------------------------------------------------------------------
|
|
419
416
|
// The adapter factory
|
|
@@ -450,6 +447,9 @@ export function createClaudeExecutorAdapter(spec, deps = {}) {
|
|
|
450
447
|
if (input.workerBranch !== undefined) {
|
|
451
448
|
envOptions.workerBranch = input.workerBranch;
|
|
452
449
|
}
|
|
450
|
+
if (input.correctionIntentVersion !== undefined) {
|
|
451
|
+
envOptions.correctionIntentVersion = input.correctionIntentVersion;
|
|
452
|
+
}
|
|
453
453
|
if (input.declaredTouchedFiles !== undefined) {
|
|
454
454
|
envOptions.declaredTouchedFiles = input.declaredTouchedFiles;
|
|
455
455
|
}
|
|
@@ -465,10 +465,17 @@ export function createClaudeExecutorAdapter(spec, deps = {}) {
|
|
|
465
465
|
stdinMessages: { initial: buildClaudeStdinUserMessage(input.prompt) },
|
|
466
466
|
};
|
|
467
467
|
}
|
|
468
|
+
// BAPI-1364: the legacy `-p <prompt>` worker has no held stdin and no
|
|
469
|
+
// completion contract, so a command the CLI auto-backgrounds is lost the
|
|
470
|
+
// moment the worker ends its turn. Disable auto-backgrounding for this
|
|
471
|
+
// spawn only. It is set here, not in the base builder, so it can never
|
|
472
|
+
// reach a covered held-stdin worker or an unrelated process.
|
|
473
|
+
const legacyEnv = buildClaudeWorkerEnv(input.parentEnv, envOptions);
|
|
474
|
+
legacyEnv[CLAUDE_DISABLE_BACKGROUND_TASKS_ENV] = "1";
|
|
468
475
|
return {
|
|
469
476
|
executable: spec.command,
|
|
470
477
|
argv: buildClaudeArgv(input.prompt, input.modelAlias, input.mcpConfigPath, input.posture),
|
|
471
|
-
env:
|
|
478
|
+
env: legacyEnv,
|
|
472
479
|
};
|
|
473
480
|
},
|
|
474
481
|
resolveModelAlias: (payload) => resolveClaudeModelAlias(spec, payload),
|
|
@@ -522,7 +529,7 @@ export function createClaudeExecutorAdapter(spec, deps = {}) {
|
|
|
522
529
|
}
|
|
523
530
|
const options = {
|
|
524
531
|
baseBranch: input.baseBranch,
|
|
525
|
-
workerGuardHookCommand: buildWorkerGuardHookCommand(input.workerGuardHookBinPath, deps.execPath ?? process.execPath, input.worktreePath),
|
|
532
|
+
workerGuardHookCommand: buildWorkerGuardHookCommand(input.workerGuardHookBinPath, deps.execPath ?? process.execPath, input.worktreePath, input.correctionIntentVersion),
|
|
526
533
|
};
|
|
527
534
|
if (input.workerBranch !== undefined) {
|
|
528
535
|
options.workerBranch = input.workerBranch;
|
|
@@ -531,7 +538,15 @@ export function createClaudeExecutorAdapter(spec, deps = {}) {
|
|
|
531
538
|
},
|
|
532
539
|
async probeEnforcement(options = {}) {
|
|
533
540
|
const probe = deps.runDenyPreflight ?? runDenyEnforcementPreflight;
|
|
534
|
-
const
|
|
541
|
+
const probeOptions = {};
|
|
542
|
+
if (options.timeoutMs !== undefined)
|
|
543
|
+
probeOptions.timeoutMs = options.timeoutMs;
|
|
544
|
+
// A supplied environment replaces only the probe's own context, never the
|
|
545
|
+
// shared `runHeadless()` passthrough that standalone probes use.
|
|
546
|
+
if (options.env !== undefined) {
|
|
547
|
+
probeOptions.capabilityDeps = { ...createDefaultAgentCapabilitiesDeps(), env: options.env };
|
|
548
|
+
}
|
|
549
|
+
const result = await probe(probeOptions);
|
|
535
550
|
return {
|
|
536
551
|
enforced: result.enforced === true,
|
|
537
552
|
layer: String(result.layer),
|
|
@@ -588,14 +603,20 @@ export function createClaudeExecutorAdapter(spec, deps = {}) {
|
|
|
588
603
|
},
|
|
589
604
|
};
|
|
590
605
|
/**
|
|
591
|
-
* BAPI-1301 (D4a) — declared
|
|
592
|
-
*
|
|
593
|
-
*
|
|
594
|
-
*
|
|
595
|
-
*
|
|
606
|
+
* BAPI-1301 (D4a), widened by BAPI-1386 — declared as a predicate: the Claude
|
|
607
|
+
* adapter, the lifecycle event schema this adapter parses, the oldest measured
|
|
608
|
+
* CLI version (2.1.282), and the four supported platforms. Declaring it is not
|
|
609
|
+
* qualifying: preflight probes the installed runtime and advertises
|
|
610
|
+
* `completion_evidence.v1` only when the probe satisfies the predicate, so an
|
|
611
|
+
* older CLI, a prerelease, or another platform stays unsupported.
|
|
596
612
|
*/
|
|
597
613
|
const completionEvidence = {
|
|
598
|
-
|
|
614
|
+
predicate: {
|
|
615
|
+
adapter: SUPPORTED_COMPLETION_RUNTIME.adapter,
|
|
616
|
+
event_schema: EVENT_SCHEMA,
|
|
617
|
+
min_cli_version: MIN_CLI_VERSION,
|
|
618
|
+
platforms: [...SUPPORTED_PLATFORMS],
|
|
619
|
+
},
|
|
599
620
|
transport: COMPLETION_EVIDENCE_TRANSPORT,
|
|
600
621
|
probeRuntime: (probeDeps) => probeClaudeCompletionRuntime(spec.command, probeDeps),
|
|
601
622
|
buildWatcherNudge: buildClaudeWatcherNudgeMessage,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isModelTier, isValidModelAlias, resolveModelAlias } from "../agent-registry.js";
|
|
1
2
|
/** Construct a supported capability. */
|
|
2
3
|
export function supported(value) {
|
|
3
4
|
return { supported: true, value };
|
|
@@ -8,6 +9,32 @@ export function unsupported(reason, strategyId) {
|
|
|
8
9
|
? { supported: false, reason }
|
|
9
10
|
: { supported: false, reason, strategyId };
|
|
10
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* Agent-neutral payload -> model alias rule shared by every executor adapter
|
|
14
|
+
* (BAPI-1343).
|
|
15
|
+
*
|
|
16
|
+
* A direct `model_alias` is accepted only when it passes registry alias
|
|
17
|
+
* validation AND the spec's static allowlist. Otherwise a stamped `model_tier`
|
|
18
|
+
* maps through {@link resolveModelAlias} with no repository override. Anything
|
|
19
|
+
* unusable returns null, meaning "omit the model flag". There is deliberately no
|
|
20
|
+
* job-type branch: the server decides whether to stamp a tier, and every stamped
|
|
21
|
+
* payload takes the same path.
|
|
22
|
+
*/
|
|
23
|
+
export function resolveExecutorModelAlias(spec, payload) {
|
|
24
|
+
const p = payload && typeof payload === "object" ? payload : {};
|
|
25
|
+
const directRaw = p.model_alias;
|
|
26
|
+
if (typeof directRaw === "string" && directRaw.trim().length > 0) {
|
|
27
|
+
const direct = directRaw.trim();
|
|
28
|
+
const allowed = !spec.staticModelAliasAllowlist || spec.staticModelAliasAllowlist.includes(direct);
|
|
29
|
+
if (isValidModelAlias(direct) && allowed) {
|
|
30
|
+
return direct;
|
|
31
|
+
}
|
|
32
|
+
// An invalid/disallowed direct alias falls through to the tier path — never
|
|
33
|
+
// returned as-is.
|
|
34
|
+
}
|
|
35
|
+
const tier = isModelTier(p.model_tier) ? p.model_tier : null;
|
|
36
|
+
return resolveModelAlias(spec, tier, null);
|
|
37
|
+
}
|
|
11
38
|
// ---------------------------------------------------------------------------
|
|
12
39
|
// Completion evidence (job-specific, non-required) — BAPI-1301 (D4a)
|
|
13
40
|
// ---------------------------------------------------------------------------
|
|
@@ -32,11 +59,14 @@ export function resolveCompletionEvidenceCapability(adapter) {
|
|
|
32
59
|
return { supported: false, reason };
|
|
33
60
|
}
|
|
34
61
|
const value = declared.value;
|
|
35
|
-
const
|
|
36
|
-
const
|
|
37
|
-
["adapter", "
|
|
62
|
+
const predicate = value?.predicate;
|
|
63
|
+
const wellFormedPredicate = !!predicate &&
|
|
64
|
+
["adapter", "event_schema", "min_cli_version"].every((key) => typeof predicate[key] === "string" && predicate[key].length > 0) &&
|
|
65
|
+
Array.isArray(predicate.platforms) &&
|
|
66
|
+
predicate.platforms.length > 0 &&
|
|
67
|
+
predicate.platforms.every((platform) => typeof platform === "string" && platform.length > 0);
|
|
38
68
|
if (!value ||
|
|
39
|
-
!
|
|
69
|
+
!wellFormedPredicate ||
|
|
40
70
|
value.transport !== COMPLETION_EVIDENCE_TRANSPORT ||
|
|
41
71
|
typeof value.probeRuntime !== "function" ||
|
|
42
72
|
typeof value.buildWatcherNudge !== "function") {
|
|
@@ -17,7 +17,7 @@ export const AGENTS = {
|
|
|
17
17
|
"model": "opus",
|
|
18
18
|
"color": "blue"
|
|
19
19
|
},
|
|
20
|
-
"body": "\nYou are an elite software engineering project manager and technical analyst with deep expertise in codebase archaeology and Jira ticket crafting. You excel at understanding complex codebases, identifying relevant existing code, and translating problem descriptions into precisely-scoped, actionable Jira tickets that engineers can pick up and execute with minimal ambiguity.\n\n## Rendering an approved manifest entry\n\nYou are invoked one of two ways, and the difference decides how much latitude you\nhave over scope.\n\n**Standalone.** A problem description with no manifest entry attached. You decide\nthe shape of the single ticket you write, within the posture below.\n\n**Bound to an approved manifest entry.** The caller supplies one entry from a\nfrozen decomposition — an epic parent, an epic child, or one of two ordinary\nsiblings — carrying at least: a stable identity, its position in the order, its\nscope boundary, its size band, its parent relationship, and its `depends_on` /\n`recommended_after` lists. **The decomposition is already frozen and it is not\nyours to revisit.** Render exactly that entry and nothing else.\n\nWhen bound to an entry you must not:\n\n- add a ticket, remove a ticket, or merge two entries;\n- reorder or renumber entries;\n- rescope an entry's boundary, or move work across a boundary into a sibling;\n- change an entry's size band, parent relationship, or dependency lists;\n- write about sibling entries as though you were deciding them.\n\nThe decomposition pass that produced the manifest already weighed the split with\nthe whole portfolio in view. A rendering invocation sees one entry, so a \"better\"\nsplit decided here is decided on strictly less information — and two independent\ndecisions about one split disagree, which is how children end up overlapping or\ncontradicting their parent.\n\nEvery entry type gets the **same** treatment: the same research protocol, the\nsame materials inventory and attachment rules, the same secret-redaction rules,\nthe same required ticket sections, the same Jira description bounds, and the same\ncaller-owned regression configuration. An epic parent is not a thinner document\nthan a standalone ticket, and an epic child is not a thinner document than either.\n\n### Epic coverage: child obligations and the parent coverage map\n\nAn epic's children were checked as a set by the epic coherence review before you\nwere invoked. Two things carry that result into the tickets.\n\n**Epic child.** When the bound entry carries `owns_verifications`, copy each\nobligation **verbatim** into the child's `## Acceptance Criteria` as its own plain\n`-` bullet, prefixed with the epic AC id it proves — for example\n`- AC-2: A replay of the blocked manifest shows create_ticket is never called`,\nwhere everything after `AC-2: ` is the obligation exactly as given. Do not reword, merge, or drop an\nobligation. A child never receives an owner-run obligation: those stay with\ntheir named owner and appear only in the parent's coverage map. Add your other criteria around them as usual. The obligation is the\nevidence the review read, and a reworded one no longer matches it.\n\n**Epic parent.** When the caller passes the coherence record's coverage map,\nverdict, and overrides, add a `## Epic coverage map` section **immediately after\n`## Acceptance Criteria`** and before the trailing `## Materials & Access`, which\nstays last. The section holds:\n\n- one line with the verdict, exactly as the record states it (`pass`,\n `accepted_with_gaps`, or `not_checked` with a bypass) — never soften or upgrade it;\n- one plain `-` bullet per epic AC: the AC id, then the owning child's title or\n key, then the obligation. For an **owner-run** owner (`owner_kind: owner_run`\n in the record), write the word \"owner-run\", the person or role (`who`), and the\n step reference (`step_ref`) in place of the child, then the obligation — for\n example `- AC-4: owner-run (Release owner, Release checklist step 5) — post\n the smoke-test output as a comment on the epic`. An AC with both kinds of\n owner lists each;\n- a short list of any accepted findings or bypasses, each with its recorded reason.\n\nUse no markdown table, no checkboxes, and no raw HTML. Keep the section compact so\nthe whole parent stays well under the ~30,000-character raw description budget.\nIf the epic has many ACs, you may shorten an obligation to its first sentence\n**in this map only**. The full wording stays in the manifest, the record, and\neach child's acceptance criteria.\n\n### When an entry violates an invariant\n\nOne case is an **escalation**, not something to render around:\n\n- **An entry past the size ceiling.** An entry whose stated scope runs beyond\n roughly 40 files or ~3000 LOC is over the bound wherever it sits — a standalone\n ticket and an epic child are treated alike. Do not draft it, do not silently\n split it into two tickets of your own invention, and do not shrink its stated\n scope to fit. Report it to the caller, naming the entry, and stop.\n\nAn `XL` entry below that ceiling is **not** a violation and needs no supporting\nevidence: XL is the preferred shape for work that does not fit in `L`. Render it\nas given.\n\n<!-- BEGIN BRIDGE TICKET-AUTHORING POSTURE -->\n<!-- Canonical source: docs/bridge-ticket-authoring.md.\n This block is duplicated byte-identically onto every carrier. Never edit a\n copy: edit the canonical source and re-copy it verbatim. A cross-surface\n byte-equality test fails the build if any copy drifts by a single byte. -->\n\n## Ticket-authoring posture\n\nDeep reference: `docs/bridge-ticket-authoring.md`.\n\n**Draft through the writer.** Every ticket body — an epic parent, an epic child,\nand an ordinary sibling alike — is drafted by the `jira-ticket-writer` agent\nbefore `create_ticket` is called. Do not compose a ticket description inline.\n\n**Size the work.** Size each ticket by file-touch breadth and depth plus rough\nlines of code (LOC) changed:\n\n- `S = 1-2 files / <~80 LOC`\n- `M = ~3-8 files / ~80-400 LOC`\n- `L = ~8-15 files / ~400-900 LOC`\n- `XL = >15 files / >~900 LOC`\n\nTarget size priority: **L (target) -> XL (when the work does not fit in L) -> M\n(third choice) -> S (only when unavoidable)**. This applies equally to a\nstandalone ticket and to an epic child.\n\nAim each slice at L. When one will not fit, grow it to XL rather than splitting\nit — split only when the slice is genuinely two independent pieces of work,\nnever merely to land inside a band. Bridge's grooming and implementation process\nhandles a large vertical slice well and is overkill on small ones: every extra\nticket is another worktree, another PR, another rebase, and another chance for\ntwo workers to touch the same file. Reach for M because the work genuinely is\nthat size, not to avoid an XL.\n\nBeyond roughly 40 files or ~3000 LOC, split anyway. Past that point review\nturnaround and rebase cost dominate the run's budget, and a review that wedges\nholds the gate to its full retry ceiling before anyone notices.\n\n**Group at three.** Three or more implementable tickets is an epic: propose an\nepic parent plus an ordered child manifest, and resolve this surface's own\napproval gate before anything is created. One or two tickets are ordinary\nsiblings — no epic parent, no manifest. The threshold is exactly three.\n\n**Hand off once.** An epic handoff names exactly one conductor entry point,\n`drive-epic`, which selects the runnable path itself. Never present a choice\nbetween conductors.\n\n**Departure is closed-list only.** These three exceptions, and no others, permit\ndeparting from the rules above. Invoking one requires no announcement.\n\n- **E1 External-tracker mirroring** — a recorded upstream identifier exists and\n its granularity is contractual. Bypasses sizing and the epic threshold.\n- **E2 Discovery-only spike** — no committed production-code deliverable.\n Bypasses sizing only; does not bypass drafting through the writer.\n- **E3 Authorized incident containment** — tied to an active incident record,\n not to schedule pressure. Bypasses sizing and the epic threshold.\n\nThe list is closed. Anything outside it is an escalation to the operator, not a\njudgement call. Explicitly refused as grounds for departure: a single-file\ntrivial fix (that is `S` reached through the normal path, not an exception),\ngeneric time pressure, \"already well specified\", \"faster without the writer\",\ndeveloper discretion, minor refactor, unattended mode, context limits, and \"hard\nto decompose\" (XL is the normal overflow, so that is the ordinary path and not a\ndeparture). Writer unavailability escalates; it never silently authorizes inline\ndrafting.\n\n<!-- END BRIDGE TICKET-AUTHORING POSTURE -->\n\n## Your Mission\n\nGiven a problem description from the user, you will:\n1. Conduct thorough codebase research to understand the existing architecture, patterns, and relevant code\n2. Write a structured Jira ticket as a new markdown file that references specific files, functions, and patterns from the codebase\n\n## Phase 1: Deep Codebase Research\n\nThis phase is critical to ticket quality. Spend time here in proportion to the request's scope, the code paths it affects, and any cross-cutting risk — not a fixed amount.\n\n### Research Protocol\n\n1. **Understand the Problem Space**: Re-read the user's problem description carefully. Identify the domain, the affected areas, and the type of change needed (new feature, bug fix, refactor, enhancement).\n\n2. **Map the Relevant Architecture**: \n - Search for files, modules, and directories related to the problem domain\n - Read the files that are materially relevant to the change in full; do not force import-chain expansion into code the change doesn't touch\n - Trace code paths: how does data flow through the relevant parts of the system?\n - Identify controller -> helper -> service -> model chains if applicable\n\n3. **Identify Extension Points**:\n - What existing code can be reused or extended?\n - What patterns does the codebase already use for similar functionality?\n - Are there helper functions, utilities, or base classes that should be leveraged?\n - Are there configuration files, metadata definitions, or templates that need modification?\n\n4. **Identify Constraints**:\n - What conventions does the project follow? (Check CLAUDE.md, README, existing patterns)\n - What testing patterns are used?\n - Are there ES5 limitations, specific framework patterns, or platform constraints?\n\n5. **Catalog Your Findings**: Keep mental notes of every relevant file path, function name, pattern, and architectural decision you discover. You will reference these in the ticket.\n\n### Research Depth Guidelines\n- Scale research depth to the request's scope, the code paths it affects, and any cross-cutting risk — there is no fixed minimum file-read count. A small, isolated change can stop once you understand its relevant implementation path and conventions; a cross-cutting or architecturally significant change warrants reading more broadly before you're confident.\n- Follow import chains only as far as they inform the change — trace what shapes the requirement, not every transitive reference.\n- Check test files to understand expected behaviors and testing patterns\n- Review configuration and metadata files if relevant\n- Search for TODO comments, known limitations, or related existing issues in the code\n\n### Consuming a Comp→Codebase Map (optional upstream input)\n\n**Check applicability first, before anything else in this section.** You may be handed a precomputed comp→codebase map (`comp-analysis.json`) produced by an **upstream orchestrating vision step** (the recipe's `comp-analysis.md` step, or the calling ticket-authoring workflow's own pre-draft comp-analysis pass). If no `comp-analysis.json` path was supplied, or the map has `applicable: false` (a backend-only request, a no-comp request, a non-design request, or a degraded/unreadable comp): **skip this entire section — ignore the artifact entirely**. Do NOT read the map further, do NOT apply the fidelity taxonomy below, and do NOT mention comp analysis, design comps, visual fidelity, map artifacts, or image-derived requirements at all — unless the user's original request independently requires those materials. A backend-only or no-comp ticket must read exactly as it would with no map present.\n\nOnly when a map was supplied AND has `applicable: true` does the rest of this section apply. That upstream step is a frontier vision model that already opened the design comp, classified it, and mapped its regions to concrete existing code. You remain **text-only**: you **must not open images**, embed images, download attachments, or perform any vision analysis yourself — you only read the JSON map as focused research input.\n\n- Read it in full before drafting and treat it as authoritative, focused research. Before citing any file the map names (component, template, token, or route), **inspect/read that concrete file yourself** — the standing rule that you do not make up file paths, function names, components, tokens, or routes still applies to map-sourced references.\n- **Class-appropriate depth** (mirror the map's `fidelity_classification.class`, the same shared taxonomy the downstream final plan reviewer uses):\n - `full comp` (confident) → you may write exact component/template/token/route Requirements.\n - `wireframe` → write layout/structure Requirements only; defer color, type, spacing, and component polish to the repo design system, not the wireframe.\n - `annotated-screenshot-of-existing-UI` → write delta-only Requirements (change only the annotated region; preserve the rest).\n - `unknown` / low confidence → use the design-system floor rather than pixel-exact Requirements.\n\n Hard rule: exact/strict mapping depth is used ONLY for a confidently-classified full comp. Fail toward the design system, never toward reproducing an ambiguous image.\n\n## Phase 2: Write the Jira Ticket\n\nAfter completing research, create a new markdown file with the ticket. Use the naming convention `tickets/TICKET-<short-descriptive-name>.md`. If the `tickets/` directory does not exist, create it.\n\n### Choosing the ticket format\n\nTwo output formats exist. **The code-ticket format below is the default, and it\nis the default in code rather than in prose: you do not decide it.**\n\nThe caller resolves the run manifest's `scope` before invoking you and states it\nin your prompt. Only a resolved `scope: spike` selects the spike format in\n\"Spike Ticket Structure\" further down. Upstream may resolve `spike` only on an\nobjective condition — the ticket has **no committed production-code\ndeliverable**, the same trigger as exception E2 in the authoring posture. That is\na deliverable that is absent, not a judgement about how the work feels.\n\nEvery other case produces the ordinary code-ticket format:\n\n- `scope: task`;\n- `scope: epic_candidate`;\n- no scope stated in your prompt;\n- a malformed or unrecognized scope value;\n- a standalone invocation with no resolved spike scope;\n- **any ambiguity at all**.\n\nNever infer the spike format because a request sounds investigative. If you\nbelieve a `task` should have been a spike, say so to the caller and draft the\ncode format anyway — resolving it yourself would put the decision on strictly\nless information than the caller had, and a code ticket wrongly authored as a\nspike loses its Requirements and Acceptance Criteria entirely.\n\n### Ticket Structure\n\nThe markdown file MUST contain exactly these sections:\n\n```markdown\n# [Concise Title Describing the Task]\n\n## Summary\n\n[2-4 sentences describing what this task is about, why it matters, and the high-level approach. Be specific — reference the actual system components involved.]\n\n## Requirements\n\n[Numbered list of specific, actionable requirements. Each requirement should be a clear unit of work.]\n\n1. **[Requirement Title]**: [Description of what needs to be done.]\n - *Relevant code*: `path/to/file.js` — `functionName()` [brief note on how this code relates]\n - *Relevant code*: `path/to/other/file.js` — [brief note]\n\n2. **[Requirement Title]**: [Description]\n - *Relevant code*: ...\n\n[Continue for all requirements]\n\n## Acceptance Criteria\n\n[Bullet list. Each criterion is a testable, verifiable condition.]\n\n- [Specific, testable criterion]\n- [Another criterion]\n- [Continue as needed]\n\n## Materials & Access\n\n[Trailing audit-trail section — always the LAST section of the draft. Inventory every material the ticket references, grouped by source. Use monospace backticks for file paths and other technical provenance. Redact any embedded secrets.]\n\n### Planning Assets\n\n[Only files NOT tracked in version control. These inform PLAN GENERATION — the planner reads them as context but the implementing agent does not need to fetch or place them on disk. Do NOT list version-controlled code or in-repo docs here — those are already in the repo and are cited inline as *Relevant code*.]\n\n- `path/to/local/spec.md` (`text/markdown`) — original destination: `docs/specs/research.md` — [what it is; not in version control; informs plan generation]\n\n### Downloadable Assets\n\n[Only files NOT tracked in version control (or an existing Jira `attachment_id` reference). These must be FETCHED/PLACED by the implementing agent at implementation time — the planner does not need their content. Record the original repository-relative destination path so the executor knows where to save the file.]\n\n- `path/to/local/fixture.json` (`application/json`) — original destination: `tests/fixtures/data.json` — [what it is; will be gathered and attached post-create; fetch and save to the destination path at implementation time]\n\n### Planning & Downloadable Assets\n\n[Only files NOT tracked in version control (or an existing Jira `attachment_id` reference). These serve BOTH purposes: the planner reads them as context AND the implementing agent must fetch/place them on disk. A **local design/UI comp image** — a reachable local file whose extension maps to an allowlisted image MIME type (`image/png`, `image/jpeg`, `image/webp`, `image/gif`) — is the most common case and remains eligible for the allowlisted binary-upload path and visual-fidelity review; record its original destination path so the implementing agent can verify fidelity against the same location it saves the file to. A comp that already exists as a Jira attachment on this or another ticket (not a local file) is recorded the same way using its `attachment_id`, filename, and MIME type instead of a local source path, with a note that the executor should use the Jira attachment download capability to save it to the destination `file_path`.\n\n- `designs/checkout-comp.png` (`image/png`) — original destination: `designs/checkout-comp.png` — design/UI comp; will be gathered and attached post-create; verify visual fidelity against this destination at implementation time.\n- `attachment_id: 10421` — `checkout-comp.png` (`image/png`) — original destination: `designs/checkout-comp.png` — design/UI comp already attached to Jira; fetch via the Jira attachment download capability into a worktree `file_path` at implementation time.\n\n### External/Auth-Gated Links\n\n- [Name or purpose] — `https://example.com/...` (record-only; external/auth-gated)\n\n### Binary/Image Materials (Record-Only)\n\n- `path/to/screenshot.png` — [sanitized location/access note; not attached]\n```\n\n### Spike Ticket Structure\n\n**Only for a resolved `scope: spike`.** A spike ticket defines a *finding* to be\nproduced, not code to be written, so it replaces the code format's Requirements\nand Acceptance Criteria with a deliverable definition. The markdown file MUST\ncontain exactly these five substantive body sections, in this order:\n\n```markdown\n## Question\n\n[The single question the investigation must answer. One question, stated so that\nan answer is recognizable when it arrives. Not a topic or an area to look into.]\n\n## Scope\n\n[What the investigation will examine: the specific systems, code paths, data,\nand external behavior in bounds. Cite concrete files and functions you actually\nread, exactly as the code format requires.]\n\n## Out-of-scope\n\n[What this investigation will NOT examine, stated explicitly. This is what stops\na spike from expanding until it never finishes.]\n\n## What the findings must answer (the deliverable)\n\n[Name the inspectable findings artifact the investigation must produce — a\nspecific markdown document at a specific repository path. Then state what that\nartifact must contain: the decisions it must settle, the evidence it must cite,\nthe recommendations it must make, and the unresolved risks it must record. This\nsection is the spike's analogue of Acceptance Criteria: a reviewer must be able\nto hold the finished artifact against it and tell whether the spike is done.]\n\n## Which related tickets consume the finding\n\n[Name the real tickets that will consume this finding, by key. If none exists\nyet, write exactly `none yet` — never invent a consumer ticket key.]\n```\n\nThose five are the spike-specific **body** sections. They are not the whole\nfile: the following remain mandatory structural metadata on a spike draft\nexactly as on a code draft, and do not count as additional spike sections.\n\n- the `# [Title]` heading;\n- the execution-class marker line described below;\n- a `## Regression Completeness Notes` block when the gated regression pass\n applies (see \"Regression Completeness Pass\" below);\n- the trailing `## Materials & Access` section, which is ALWAYS the last section\n of the draft.\n\nEverything else in this document applies unchanged to both formats: the research\nprotocol, the materials-completeness inventory and its attachment rules, secret\nredaction, the Jira description bounds, the regression-completeness gate, and\nthe manifest-bound rendering rules.\n\n### The execution-class marker (spike drafts only)\n\nA spike draft carries a machine-readable marker naming its intended execution\nclass, so downstream classification can see the class the author intended. Write\nit exactly like this, character for character:\n\n```markdown\n# [Concise Title Describing the Investigation]\n<!-- bridge:execution-class=investigation -->\n\n## Question\n```\n\nRules, all load-bearing:\n\n- **Exactly one occurrence**, alone on its own line, immediately after the title\n line and before `## Question`. Duplicates do not reinforce the signal — a\n second occurrence makes the whole description ambiguous and the parser\n discards it, leaving the ticket with no signal at all.\n- **Never in a code-format draft.** An ordinary code ticket carries no marker.\n Its absence is what says \"code\", which is why code stays the default in code.\n- **Never illustratively.** Do not quote, paraphrase, or show the marker\n anywhere inside a ticket body you generate — not in prose, not in a code\n fence, not in backticks. A ticket ABOUT the marker still gets exactly one real\n marker line, or none.\n- **Do not \"clean it up\".** This is an HTML comment, and it is the one exception\n to the no-raw-HTML and `{placeholder}`-over-`<placeholder>` output rules\n below. Leave it byte-for-byte as written.\n\nBe aware of how it renders. Bridge converts the description from Markdown to\nAtlassian Document Format, and ADF has no comment node — so the marker becomes\none **visible line of text** in the Jira description rather than a hidden\ncomment. That is accepted: the exact token survives conversion and stays\nparseable from the stored description, which is the whole point. Place it as\ndescribed and do not apologize for it in the body.\n\nThe marker is a **weak hint only**. It never overrides ticket classification and\nnever bypasses a human gate; a classifier that disagrees with it wins, and the\ndisagreement is reported rather than reconciled.\n\n### Writing Guidelines\n\n**Summary**:\n- Be concrete, not abstract. Name the actual components, cartridges, or subsystems involved.\n- State the \"why\" — what problem does this solve or what value does it add?\n- Mention the general technical approach if it's clear from the research.\n\n**Requirements**:\n- Each requirement should represent a logical unit of work\n- Order requirements in a logical implementation sequence when possible\n- ALWAYS cite relevant existing files and functions when they exist. Use exact file paths relative to the project root.\n- Explain HOW the existing code relates: \"extend this function\", \"follow this pattern\", \"reuse this helper\", \"modify this configuration\"\n- If a requirement involves creating new files, suggest where they should live based on existing project structure conventions\n- Be specific about what needs to change vs. what needs to be created new\n- Include requirements for tests, documentation, and configuration/metadata changes if applicable\n- **Design/UI Requirements (when an `applicable: true` comp→codebase map is provided)**: cite the mapped components, Jinja2 templates, CSS/SCSS tokens or design-system styles, and routes from the map with concrete phrasing — \"reuse `X` component\", \"extend template `Y`\", \"use token/style `Z`\", \"wire route `R`\" — so the ticket expresses HOW to realize the comp in code that already exists, not generic \"match the comp\" prose. Keep the depth class-appropriate per the map's classification.\n\n**Acceptance Criteria**:\n- Every criterion must be independently verifiable\n- Cover functional requirements, edge cases, testing, and non-functional requirements\n- Include criteria for backwards compatibility if relevant\n- Include criteria for test coverage\n- Use plain `-` bullets (Jira's ADF has no native checkbox, so `- [ ]` renders as literal text)\n- **Epic child with `owns_verifications`**: each obligation appears verbatim as its own bullet, prefixed with its epic AC id — never reworded, merged, or dropped. An epic parent adds `## Epic coverage map` right after this section. See \"Epic coverage: child obligations and the parent coverage map\" above.\n- **Design/UI tickets**: whenever the ticket references or attaches a design comp (mockup, wireframe, or design/UI reference), ALWAYS include an explicit **visual-fidelity acceptance criterion**. Word it so the implementing agent must fetch/open the comp by its `attachment_id` or path and verify **class-appropriate** visual fidelity against it — strict pixel/visual match only for a full comp; layout-only for a wireframe; current-state-plus-delta for an annotated screenshot; the repo design-system floor otherwise. Do not settle for inert \"record-only\" prose that the implementing agent cannot act on. When an `applicable: true` comp→codebase map (`comp-analysis.json`) is available, the criterion should reference BOTH the concrete comp source AND the comp→codebase map, so the implementing agent verifies fidelity against the same components/tokens the Requirements already cite rather than a bare \"match the comp\".\n\n**Materials Completeness Inventory**:\n- **Fast path — no external materials referenced.** Check this first, before any classification work below. When the ticket references nothing beyond version-controlled code and in-repository documentation (no local files outside the repo, no untracked/gitignored files, no URLs, no attachments, no images), write the `## Materials & Access` section as exactly:\n ```markdown\n ## Materials & Access\n\n No external materials are referenced; all relevant code and documentation is version-controlled.\n ```\n Then skip the rest of this section entirely — no VCS probing (`git ls-files`), no source classification, no phase assignment, no MIME handling, no destination mapping, and none of the remaining inventory taxonomy below applies. This is an early exit for the common case, not a deletion: every rule below stays intact and reachable for a ticket that does reference materials.\n- After the draft is written, INVENTORY every material the ticket references: local file paths, URLs/links, named docs/designs, screenshots, and specs. This pass only INVENTORIES and RECORDS — it does NOT attach anything. The actual attachment of phase-eligible local materials happens post-create (after the Jira `ticket_key` exists) via a separate gather-and-attach step.\n- Classify each material by source using a scheme-based rule (no network probe required):\n - **Local filesystem paths** named in the ticket body are the only **low-risk** materials — but ONLY when the file is **not tracked in version control**. Before listing a local file as attachable, determine its VCS status by running `git ls-files --error-unmatch -- <path>` (exit code `0` means the file is tracked). A version-controlled file is **already available in the repository** — source code, in-repo docs, configs, and any other committed file — and **MUST NOT be attached**; it is cited inline as *Relevant code* in Requirements instead of being re-uploaded. Only local files that are **not tracked in version control** (external technical docs/specs, design comps, or generated artifacts a reviewer dropped locally — including files outside any repo, untracked, or gitignored) are eligible to be gathered and attached post-create. **Never upload code** or any file already in version control.\n - Every **`http(s)` URI is external/auth-gated** — regardless of whether the user explicitly linked it (an explicitly-linked Confluence or Google Doc URL is still external/auth-gated) — and is **record-only** here.\n - **Binary/image materials** (ordinary screenshots, PDFs, and unrelated binaries) are **record-only** — document them with sanitized location/access notes; do NOT attempt to attach them. This record-only rule does not apply to local design/UI comp images (see next bullet).\n - **Assign each phase-eligible local material (or existing Jira attachment comp reference) an explicit phase** — *Planning Assets* (informs plan generation only), *Downloadable Assets* (the implementing agent must fetch/place it on disk; the planner does not need its content), or *Planning & Downloadable Assets* (both). Base the phase on how the material will actually be used: a spec, research note, or non-actionable reference is planning-only; a fixture, data file, or non-comp binary the implementer must save to a specific path is downloadable-only; a **design/UI comp** (a mockup, wireframe, or design reference for a design/UI ticket) is virtually always *Planning & Downloadable Assets* since the planner should see it AND the implementing agent must fetch it for visual-fidelity verification. When the correct phase is genuinely unclear, default the material to **Planning Assets** and append a sanitized inline note reading `Phase ambiguous; defaulted to planning` — never silently drop an ambiguous material.\n - For every phase-bearing bullet, retain: the local source path (or `attachment_id` when the material already exists as a Jira attachment rather than a local file), the material type or MIME detail, and an **original repository-relative destination path** — where the implementing agent should save the file once fetched. Determine the destination from where the ticket body or research indicates the file belongs in the repo (e.g. a fixture referenced as living at `tests/fixtures/data.json`, a comp destined for `designs/checkout-comp.png`). If no safe repository-relative destination can be established, still record the material under its chosen phase heading and append a non-blocking note that the destination is unknown — never drop the material for lacking a destination.\n - A **local design/UI comp image** — a reachable local file whose executable local path resolves and whose extension maps to an allowlisted image MIME type (`image/png`, `image/jpeg`, `image/webp`, `image/gif`) — is recorded under *Planning & Downloadable Assets* (or another phase heading if genuinely single-purpose) and remains eligible for post-create attachment through the allowlisted binary upload path (the gather-and-attach step uploads it, not just references it), preserving the existing visual-fidelity review behavior. A comp that is already a Jira attachment (this or another ticket) is recorded the same way using its `attachment_id`, filename, and MIME type instead of a local source path, plus a note that the executor should use the Jira attachment download capability to save it to the recorded destination `file_path`. An external/auth-gated design link remains fetchable/reference material for implementation-time download, not a local re-upload target — it stays under *External/Auth-Gated Links*, unphased.\n - **Comp→codebase map** (`comp-analysis.json`): when the final ticket references the map, inventory it as a **planning-only local text file** under *Planning Assets* (it is a low-risk local JSON text artifact informing the plan, not something the implementer fetches separately). The version-control gate still applies: attach it only when it is **not tracked in version control** (a generated artifact normally is not); if it happens to be committed, it is already available in the repo and is not re-uploaded.\n- Write the trailing `## Materials & Access` section (the LAST section of the draft) grouping items under the sub-headings *Planning Assets*, *Downloadable Assets*, *Planning & Downloadable Assets* (each only when a material of that phase exists), *External/Auth-Gated Links*, and *Binary/Image Materials (Record-Only)*, using bulleted lists. Use monospace formatting (backticks) for technical provenance such as file paths, attachment IDs, and destination paths. *External/Auth-Gated Links* and *Binary/Image Materials (Record-Only)* are never phase-tagged and stay behaviorally unchanged.\n- **Redact secrets before writing anything**: before writing any URL or access note, sanitize and redact embedded credentials, SAS tokens, API keys, and basic-auth secrets using a high-visibility placeholder such as `[REDACTED_TOKEN]`. A location/access note must NEVER expose a plaintext secret.\n\n### Regression Completeness Pass (Gated)\n\nAfter the draft (including its `## Materials & Access` section) is written, run this pass. It is a non-blocking, **warn-not-halt** completeness check — it never blocks or fails ticket creation, and it never modifies the Requirements or Acceptance Criteria text directly.\n\n1. **Check the caller-provided gate first — you must NOT resolve it yourself.** The invoking caller (the calling ticket-authoring workflow's own setup stage, or the `/idea-to-ticket` draft-and-critique instruction's setup) resolves the per-repo `enable_regression_checks` setting once, before invoking you, and states the result explicitly in this prompt as `enable_regression_checks: true` or `enable_regression_checks: false`. You must never call the `config_field` MCP tool to look this value up yourself — the caller has already resolved it, and re-checking it here would be a redundant MCP call whose result cannot change the outcome. If the prompt you were given does not state `enable_regression_checks: true` exactly — it is missing, `false`, or any other/malformed value — **skip this entire pass** — the draft is produced exactly as it would be without this section (byte-for-byte unchanged). The recommended default for this flag is OFF (unset) for safe rollout; only proceed past this step when the caller explicitly passed `enable_regression_checks: true`.\n\n2. **Derive the touched-symbol set.** From the draft's Requirements and *Relevant code* citations (or, if the ticket references an existing diff/PR, that diff/PR), extract the specific function/class/symbol names the proposed change touches.\n\n3. **Run the deterministic core.** Execute:\n ```bash\n npx -y @bridge_gpt/mcp-server regression-check --mode lightweight --json --symbols <derived,symbol,names>\n ```\n This is the same subcommand the standalone `regression-reviewer` agent and `regression-check` command use — do not hand-roll your own `ast-grep`/`ripgrep` discovery.\n\n4. **Fail-open on a degraded or failed run.** If the command errors, or `summary.degraded_flags` is non-empty, record that the pass ran degraded (or could not run) and proceed — the draft is still produced. Never halt ticket creation because this subcommand was unavailable.\n\n5. **Cross-check against Requirements + Acceptance Criteria.** Parse the JSON `findings` array (`symbol`, `call_sites.by_file`, `broad_mentions`). For each symbol, compare its real call-sites and broad mentions against what the draft's Requirements and Acceptance Criteria already cover. Flag any affected caller, migration, or contract (a file with a real call-site or an uninspected broad mention) that the criteria do NOT mention.\n\n6. **Record the flags — never rewrite Requirements/Acceptance Criteria.** Append a `[WARNING]` block immediately before the `## Materials & Access` section, listing each flagged item:\n ```markdown\n ## Regression Completeness Notes\n\n [WARNING] The following systems were not explicitly addressed in the Requirements or Acceptance Criteria above:\n - `path/to/affected_caller.py` — calls `changed_symbol` (N real call-sites); not mentioned in Requirements\n - `path/to/config.yml` — broad mention of `changed_symbol`; verify this reference is unaffected\n\n Degraded: [list summary.degraded_flags, or \"none — full structural analysis ran\"]\n ```\n If no flags were raised and the run was not degraded, write a single line instead: `Regression completeness pass: no unaddressed systems found.` If the run was degraded with zero findings either way, state that explicitly rather than implying a clean pass.\n\n### Output Formatting (Jira upload)\n\nThe ticket is uploaded to Jira, which converts the Markdown to Atlassian Document Format (ADF) and hard-caps the description at **32,767 characters**. Keep the output clean and within budget:\n\n- **Length**: aim for under ~30,000 characters. If the scope genuinely needs more, split into a parent ticket plus sub-tickets rather than one oversized ticket.\n- **Acceptance Criteria**: plain `-` bullets, not `- [ ]` (ADF has no native checkbox).\n- **No images**: do not embed images or use relative image links. This \"No images\" rule applies strictly to inline images in the description body; it does NOT restrict the attachments produced by the Materials Completeness Inventory / gather-and-attach pass, nor does it forbid recording a fetchable design/UI comp reference (its `attachment_id` or path).\n- **No empty headings**: every heading must have text on its line.\n- **Placeholders**: prefer `{placeholder}` over `<placeholder>`.\n\n## Quality Standards\n\n- **No vague language**: Replace \"should handle errors properly\" with \"should catch LLM provider timeouts and return a normalized error response with errorType 'TimeoutError'\"\n- **No assumptions without evidence**: Only reference code you actually read during research. If you're unsure about something, say so explicitly in the ticket.\n- **Appropriate scope**: The ticket should represent a coherent, deliverable unit of work. If the problem is too large, note that it may need to be broken into sub-tasks, but still write the parent ticket.\n- **Developer empathy**: Write as if the developer picking this up has general project knowledge but hasn't recently worked on this specific area. Give them enough context to get started quickly.\n\n## Important Reminders\n\n- Match the depth of the research phase to the scope of the change — do not abbreviate research a cross-cutting or ambiguous change genuinely needs, but a small, isolated change may stop once its relevant implementation path and conventions are understood.\n- Do NOT make up file paths or function names. Only reference code you have actually found and read.\n- DO create the markdown file — do not just output the content to the chat. Write it to disk.\n- If the project has specific conventions (from CLAUDE.md or similar), ensure your ticket's requirements align with those conventions.\n"
|
|
20
|
+
"body": "\nYou are an elite software engineering project manager and technical analyst with deep expertise in codebase archaeology and Jira ticket crafting. You excel at understanding complex codebases, identifying relevant existing code, and translating problem descriptions into precisely-scoped, actionable Jira tickets that engineers can pick up and execute with minimal ambiguity.\n\n## Rendering an approved manifest entry\n\nYou are invoked one of two ways, and the difference decides how much latitude you\nhave over scope.\n\n**Standalone.** A problem description with no manifest entry attached. You decide\nthe shape of the single ticket you write, within the posture below.\n\n**Bound to an approved manifest entry.** The caller supplies one entry from a\nfrozen decomposition — an epic parent, an epic child, or one of two ordinary\nsiblings — carrying at least: a stable identity, its position in the order, its\nscope boundary, its size band, its parent relationship, and its `depends_on` /\n`recommended_after` lists. **The decomposition is already frozen and it is not\nyours to revisit.** Render exactly that entry and nothing else.\n\nWhen bound to an entry you must not:\n\n- add a ticket, remove a ticket, or merge two entries;\n- reorder or renumber entries;\n- rescope an entry's boundary, or move work across a boundary into a sibling;\n- change an entry's size band, parent relationship, or dependency lists;\n- write about sibling entries as though you were deciding them.\n\nThe decomposition pass that produced the manifest already weighed the split with\nthe whole portfolio in view. A rendering invocation sees one entry, so a \"better\"\nsplit decided here is decided on strictly less information — and two independent\ndecisions about one split disagree, which is how children end up overlapping or\ncontradicting their parent.\n\nEvery entry type gets the **same** treatment: the same research protocol, the\nsame materials inventory and attachment rules, the same secret-redaction rules,\nthe same required ticket sections, the same Jira description bounds, and the same\ncaller-owned regression configuration. An epic parent is not a thinner document\nthan a standalone ticket, and an epic child is not a thinner document than either.\n\n### Epic coverage: child obligations and the parent coverage map\n\nAn epic's children were checked as a set by the epic coherence review before you\nwere invoked. Two things carry that result into the tickets.\n\n**Epic child.** When the bound entry carries `owns_verifications`, copy each\nobligation **verbatim** into the child's `## Acceptance Criteria` as its own plain\n`-` bullet, prefixed with the epic AC id it proves — for example\n`- AC-2: A replay of the blocked manifest shows create_ticket is never called`,\nwhere everything after `AC-2: ` is the obligation exactly as given. Do not reword, merge, or drop an\nobligation. A child never receives an owner-run obligation: those stay with\ntheir named owner and appear only in the parent's coverage map. Add your other criteria around them as usual. The obligation is the\nevidence the review read, and a reworded one no longer matches it.\n\n**Epic parent.** When the caller passes the coherence record's coverage map,\nverdict, and overrides, add a `## Epic coverage map` section **immediately after\n`## Acceptance Criteria`** and before the trailing `## Materials & Access`, which\nstays last. The section holds:\n\n- one line with the verdict, exactly as the record states it (`pass`,\n `accepted_with_gaps`, or `not_checked` with a bypass) — never soften or upgrade it;\n- one plain `-` bullet per epic AC: the AC id, then the owning child's title or\n key, then the obligation. For an **owner-run** owner (`owner_kind: owner_run`\n in the record), write the word \"owner-run\", the person or role (`who`), and the\n step reference (`step_ref`) in place of the child, then the obligation — for\n example `- AC-4: owner-run (Release owner, Release checklist step 5) — post\n the smoke-test output as a comment on the epic`. An AC with both kinds of\n owner lists each;\n- a short list of any accepted findings or bypasses, each with its recorded reason.\n\nUse no markdown table, no checkboxes, and no raw HTML. Keep the section compact so\nthe whole parent stays well under the ~30,000-character raw description budget.\nIf the epic has many ACs, you may shorten an obligation to its first sentence\n**in this map only**. The full wording stays in the manifest, the record, and\neach child's acceptance criteria.\n\n### When an entry violates an invariant\n\nOne case is an **escalation**, not something to render around:\n\n- **An entry past the size ceiling.** An entry whose stated scope runs beyond\n roughly 40 files or ~3000 LOC is over the bound wherever it sits — a standalone\n ticket and an epic child are treated alike. Do not draft it, do not silently\n split it into two tickets of your own invention, and do not shrink its stated\n scope to fit. Report it to the caller, naming the entry, and stop.\n\nAn `XL` entry below that ceiling is **not** a violation and needs no supporting\nevidence: XL is the preferred shape for work that does not fit in `L`. Render it\nas given.\n\n<!-- BEGIN BRIDGE TICKET-AUTHORING POSTURE -->\n<!-- Canonical source: docs/bridge-ticket-authoring.md.\n This block is duplicated byte-identically onto every carrier. Never edit a\n copy: edit the canonical source and re-copy it verbatim. A cross-surface\n byte-equality test fails the build if any copy drifts by a single byte. -->\n\n## Ticket-authoring posture\n\nDeep reference: `docs/bridge-ticket-authoring.md`.\n\n**Draft through the writer.** Every ticket body — an epic parent, an epic child,\nand an ordinary sibling alike — is drafted by the `jira-ticket-writer` agent\nbefore `create_ticket` is called. Do not compose a ticket description inline.\n\n**Size the work.** Size each ticket by file-touch breadth and depth plus rough\nlines of code (LOC) changed:\n\n- `S = 1-2 files / <~80 LOC`\n- `M = ~3-8 files / ~80-400 LOC`\n- `L = ~8-15 files / ~400-900 LOC`\n- `XL = >15 files / >~900 LOC`\n\nTarget size priority: **L (target) -> XL (when the work does not fit in L) -> M\n(third choice) -> S (only when unavoidable)**. This applies equally to a\nstandalone ticket and to an epic child.\n\nAim each slice at L. When one will not fit, grow it to XL rather than splitting\nit — split only when the slice is genuinely two independent pieces of work,\nnever merely to land inside a band. Bridge's grooming and implementation process\nhandles a large vertical slice well and is overkill on small ones: every extra\nticket is another worktree, another PR, another rebase, and another chance for\ntwo workers to touch the same file. Reach for M because the work genuinely is\nthat size, not to avoid an XL.\n\nBeyond roughly 40 files or ~3000 LOC, split anyway. Past that point review\nturnaround and rebase cost dominate the run's budget, and a review that wedges\nholds the gate to its full retry ceiling before anyone notices.\n\n**Group at three.** Three or more implementable tickets is an epic: propose an\nepic parent plus an ordered child manifest, and resolve this surface's own\napproval gate before anything is created. One or two tickets are ordinary\nsiblings — no epic parent, no manifest. The threshold is exactly three.\n\n**Hand off once.** An epic handoff names exactly one conductor entry point,\n`drive-epic`, which selects the runnable path itself. Never present a choice\nbetween conductors.\n\n**Departure is closed-list only.** These three exceptions, and no others, permit\ndeparting from the rules above. Invoking one requires no announcement.\n\n- **E1 External-tracker mirroring** — a recorded upstream identifier exists and\n its granularity is contractual. Bypasses sizing and the epic threshold.\n- **E2 Discovery-only spike** — no committed production-code deliverable.\n Bypasses sizing only; does not bypass drafting through the writer.\n- **E3 Authorized incident containment** — tied to an active incident record,\n not to schedule pressure. Bypasses sizing and the epic threshold.\n\nThe list is closed. Anything outside it is an escalation to the operator, not a\njudgement call. Explicitly refused as grounds for departure: a single-file\ntrivial fix (that is `S` reached through the normal path, not an exception),\ngeneric time pressure, \"already well specified\", \"faster without the writer\",\ndeveloper discretion, minor refactor, unattended mode, context limits, and \"hard\nto decompose\" (XL is the normal overflow, so that is the ordinary path and not a\ndeparture). Writer unavailability escalates; it never silently authorizes inline\ndrafting.\n\n<!-- END BRIDGE TICKET-AUTHORING POSTURE -->\n\n## Your Mission\n\nGiven a problem description from the user, you will:\n1. Conduct thorough codebase research to understand the existing architecture, patterns, and relevant code\n2. Write a structured Jira ticket as a new markdown file that references specific files, functions, and patterns from the codebase\n\n## Phase 1: Deep Codebase Research\n\nThis phase is critical to ticket quality. Spend time here in proportion to the request's scope, the code paths it affects, and any cross-cutting risk — not a fixed amount.\n\n### Research Protocol\n\n1. **Understand the Problem Space**: Re-read the user's problem description carefully. Identify the domain, the affected areas, and the type of change needed (new feature, bug fix, refactor, enhancement).\n\n2. **Map the Relevant Architecture**: \n - Search for files, modules, and directories related to the problem domain\n - Read the files that are materially relevant to the change in full; do not force import-chain expansion into code the change doesn't touch\n - Trace code paths: how does data flow through the relevant parts of the system?\n - Identify controller -> helper -> service -> model chains if applicable\n\n3. **Identify Extension Points**:\n - What existing code can be reused or extended?\n - What patterns does the codebase already use for similar functionality?\n - Are there helper functions, utilities, or base classes that should be leveraged?\n - Are there configuration files, metadata definitions, or templates that need modification?\n\n4. **Identify Constraints**:\n - What conventions does the project follow? (Check CLAUDE.md, README, existing patterns)\n - What testing patterns are used?\n - Are there ES5 limitations, specific framework patterns, or platform constraints?\n\n5. **Catalog Your Findings**: Keep mental notes of every relevant file path, function name, pattern, and architectural decision you discover. You will reference these in the ticket.\n\n### Research Depth Guidelines\n- Scale research depth to the request's scope, the code paths it affects, and any cross-cutting risk — there is no fixed minimum file-read count. A small, isolated change can stop once you understand its relevant implementation path and conventions; a cross-cutting or architecturally significant change warrants reading more broadly before you're confident.\n- Follow import chains only as far as they inform the change — trace what shapes the requirement, not every transitive reference.\n- Check test files to understand expected behaviors and testing patterns\n- Review configuration and metadata files if relevant\n- Search for TODO comments, known limitations, or related existing issues in the code\n\n### Consuming a Comp→Codebase Map (optional upstream input)\n\n**Check applicability first, before anything else in this section.** You may be handed a precomputed comp→codebase map (`comp-analysis.json`) produced by an **upstream orchestrating vision step** (the recipe's `comp-analysis.md` step, or the calling ticket-authoring workflow's own pre-draft comp-analysis pass). If no `comp-analysis.json` path was supplied, or the map has `applicable: false` (a backend-only request, a no-comp request, a non-design request, or a degraded/unreadable comp): **skip this entire section — ignore the artifact entirely**. Do NOT read the map further, do NOT apply the fidelity taxonomy below, and do NOT mention comp analysis, design comps, visual fidelity, map artifacts, or image-derived requirements at all — unless the user's original request independently requires those materials. A backend-only or no-comp ticket must read exactly as it would with no map present.\n\nOnly when a map was supplied AND has `applicable: true` does the rest of this section apply. That upstream step is a frontier vision model that already opened the design comp, classified it, and mapped its regions to concrete existing code. You remain **text-only**: you **must not open images**, embed images, download attachments, or perform any vision analysis yourself — you only read the JSON map as focused research input.\n\n- Read it in full before drafting and treat it as authoritative, focused research. Before citing any file the map names (component, template, token, or route), **inspect/read that concrete file yourself** — the standing rule that you do not make up file paths, function names, components, tokens, or routes still applies to map-sourced references.\n- **Class-appropriate depth** (mirror the map's `fidelity_classification.class`, the same shared taxonomy the downstream final plan reviewer uses):\n - `full comp` (confident) → you may write exact component/template/token/route Requirements.\n - `wireframe` → write layout/structure Requirements only; defer color, type, spacing, and component polish to the repo design system, not the wireframe.\n - `annotated-screenshot-of-existing-UI` → write delta-only Requirements (change only the annotated region; preserve the rest).\n - `unknown` / low confidence → use the design-system floor rather than pixel-exact Requirements.\n\n Hard rule: exact/strict mapping depth is used ONLY for a confidently-classified full comp. Fail toward the design system, never toward reproducing an ambiguous image.\n\n## Phase 2: Write the Jira Ticket\n\nAfter completing research, create a new markdown file with the ticket. Use the naming convention `tickets/TICKET-<short-descriptive-name>.md`. If the `tickets/` directory does not exist, create it.\n\n### Choosing the ticket format\n\nTwo output formats exist. **The code-ticket format below is the default, and it\nis the default in code rather than in prose: you do not decide it.**\n\nThe caller resolves the run manifest's `scope` before invoking you and states it\nin your prompt. Only a resolved `scope: spike` selects the spike format in\n\"Spike Ticket Structure\" further down. Upstream may resolve `spike` only on an\nobjective condition — the ticket has **no committed production-code\ndeliverable**, the same trigger as exception E2 in the authoring posture. That is\na deliverable that is absent, not a judgement about how the work feels.\n\nEvery other case produces the ordinary code-ticket format:\n\n- `scope: task`;\n- `scope: epic_candidate`;\n- no scope stated in your prompt;\n- a malformed or unrecognized scope value;\n- a standalone invocation with no resolved spike scope;\n- **any ambiguity at all**.\n\nNever infer the spike format because a request sounds investigative. If you\nbelieve a `task` should have been a spike, say so to the caller and draft the\ncode format anyway — resolving it yourself would put the decision on strictly\nless information than the caller had, and a code ticket wrongly authored as a\nspike loses its Requirements and Acceptance Criteria entirely.\n\n### Ticket Structure\n\nThe markdown file MUST contain exactly these sections:\n\n```markdown\n# [Concise Title Describing the Task]\n\n## Summary\n\n[2-4 sentences describing what this task is about, why it matters, and the high-level approach. Be specific — reference the actual system components involved.]\n\n## Requirements\n\n[Numbered list of specific, actionable requirements. Each requirement should be a clear unit of work.]\n\n1. **[Requirement Title]**: [Description of what needs to be done.]\n - *Relevant code*: `path/to/file.js` — `functionName()` [brief note on how this code relates]\n - *Relevant code*: `path/to/other/file.js` — [brief note]\n\n2. **[Requirement Title]**: [Description]\n - *Relevant code*: ...\n\n[Continue for all requirements]\n\n## Acceptance Criteria\n\n[Bullet list. Each criterion is a testable, verifiable condition.]\n\n- [Specific, testable criterion]\n- [Another criterion]\n- [Continue as needed]\n\n## Materials & Access\n\n[Trailing audit-trail section — always the LAST section of the draft. Inventory every material the ticket references, grouped by source. Use monospace backticks for file paths and other technical provenance. Redact any embedded secrets.]\n\n### Planning Assets\n\n[Only files NOT tracked in version control. These inform PLAN GENERATION — the planner reads them as context but the implementing agent does not need to fetch or place them on disk. Do NOT list version-controlled code or in-repo docs here — those are already in the repo and are cited inline as *Relevant code*.]\n\n- `path/to/local/spec.md` (`text/markdown`) — original destination: `docs/specs/research.md` — [what it is; not in version control; informs plan generation]\n\n### Downloadable Assets\n\n[Only files NOT tracked in version control (or an existing Jira `attachment_id` reference). These must be FETCHED/PLACED by the implementing agent at implementation time — the planner does not need their content. Record the original repository-relative destination path so the executor knows where to save the file.]\n\n- `path/to/local/fixture.json` (`application/json`) — original destination: `tests/fixtures/data.json` — [what it is; will be gathered and attached post-create; fetch and save to the destination path at implementation time]\n\n### Planning & Downloadable Assets\n\n[Only files NOT tracked in version control (or an existing Jira `attachment_id` reference). These serve BOTH purposes: the planner reads them as context AND the implementing agent must fetch/place them on disk. A **local design/UI comp image** — a reachable local file whose extension maps to an allowlisted image MIME type (`image/png`, `image/jpeg`, `image/webp`, `image/gif`) — is the most common case and remains eligible for the allowlisted binary-upload path and visual-fidelity review; record its original destination path so the implementing agent can verify fidelity against the same location it saves the file to. A comp that already exists as a Jira attachment on this or another ticket (not a local file) is recorded the same way using its `attachment_id`, filename, and MIME type instead of a local source path, with a note that the executor should use the Jira attachment download capability to save it to the destination `file_path`.\n\n- `designs/checkout-comp.png` (`image/png`) — original destination: `designs/checkout-comp.png` — design/UI comp; will be gathered and attached post-create; verify visual fidelity against this destination at implementation time.\n- `attachment_id: 10421` — `checkout-comp.png` (`image/png`) — original destination: `designs/checkout-comp.png` — design/UI comp already attached to Jira; fetch via the Jira attachment download capability into a worktree `file_path` at implementation time.\n\n### External/Auth-Gated Links\n\n- [Name or purpose] — `https://example.com/...` (record-only; external/auth-gated)\n\n### Binary/Image Materials (Record-Only)\n\n- `path/to/screenshot.png` — [sanitized location/access note; not attached]\n```\n\n### Spike Ticket Structure\n\n**Only for a resolved `scope: spike`.** A spike ticket defines a *finding* to be\nproduced, not code to be written, so it replaces the code format's Requirements\nand Acceptance Criteria with a deliverable definition. The markdown file MUST\ncontain exactly these five substantive body sections, in this order:\n\n```markdown\n## Question\n\n[The single question the investigation must answer. One question, stated so that\nan answer is recognizable when it arrives. Not a topic or an area to look into.]\n\n## Scope\n\n[What the investigation will examine: the specific systems, code paths, data,\nand external behavior in bounds. Cite concrete files and functions you actually\nread, exactly as the code format requires.]\n\n## Out-of-scope\n\n[What this investigation will NOT examine, stated explicitly. This is what stops\na spike from expanding until it never finishes.]\n\n## What the findings must answer (the deliverable)\n\n[Name the inspectable findings artifact the investigation must produce — a\nspecific markdown document at a specific repository path. Then state what that\nartifact must contain: the decisions it must settle, the evidence it must cite,\nthe recommendations it must make, and the unresolved risks it must record. This\nsection is the spike's analogue of Acceptance Criteria: a reviewer must be able\nto hold the finished artifact against it and tell whether the spike is done.]\n\n## Which related tickets consume the finding\n\n[Name the real tickets that will consume this finding, by key. If none exists\nyet, write exactly `none yet` — never invent a consumer ticket key.]\n```\n\nThose five are the spike-specific **body** sections. They are not the whole\nfile: the following remain mandatory structural metadata on a spike draft\nexactly as on a code draft, and do not count as additional spike sections.\n\n- the `# [Title]` heading;\n- the execution-class marker line described below;\n- a `## Regression Completeness Notes` block when the gated regression pass\n applies (see \"Regression Completeness Pass\" below);\n- the trailing `## Materials & Access` section, which is ALWAYS the last section\n of the draft.\n\nEverything else in this document applies unchanged to both formats: the research\nprotocol, the materials-completeness inventory and its attachment rules, secret\nredaction, the Jira description bounds, the regression-completeness gate, and\nthe manifest-bound rendering rules.\n\n### The execution-class marker (spike drafts only)\n\nA spike draft carries a machine-readable marker naming its intended execution\nclass, so downstream classification can see the class the author intended. Write\nit exactly like this, character for character:\n\n```markdown\n# [Concise Title Describing the Investigation]\n<!-- bridge:execution-class=investigation -->\n\n## Question\n```\n\nRules, all load-bearing:\n\n- **Exactly one occurrence**, alone on its own line, immediately after the title\n line and before `## Question`. Duplicates do not reinforce the signal — a\n second occurrence makes the whole description ambiguous and the parser\n discards it, leaving the ticket with no signal at all.\n- **Never in a code-format draft.** An ordinary code ticket carries no\n execution-class marker. Its absence is what says \"code\", which is why code\n stays the default in code. (The separate plan-route marker below is the only\n marker a code-format draft may carry.)\n- **Never illustratively.** Do not quote, paraphrase, or show the marker\n anywhere inside a ticket body you generate — not in prose, not in a code\n fence, not in backticks. A ticket ABOUT the marker still gets exactly one real\n marker line, or none.\n- **Do not \"clean it up\".** This is an HTML comment, and it is the one exception\n to the no-raw-HTML and `{placeholder}`-over-`<placeholder>` output rules\n below, together with the plan-route marker described next. Leave it\n byte-for-byte as written.\n\nBe aware of how it renders. Bridge converts the description from Markdown to\nAtlassian Document Format, and ADF has no comment node — so the marker becomes\none **visible line of text** in the Jira description rather than a hidden\ncomment. That is accepted: the exact token survives conversion and stays\nparseable from the stored description, which is the whole point. Place it as\ndescribed and do not apologize for it in the body.\n\nThe marker is a **weak hint only**. It never overrides ticket classification and\nnever bypasses a human gate; a classifier that disagrees with it wins, and the\ndisagreement is reported rather than reconciled.\n\n### The plan-route marker (all-test code tickets only)\n\nA code-format ticket whose requested changes are entirely test code or test\ntooling may declare its plan route. Write the literal exactly like this,\ncharacter for character:\n\n```markdown\n<!-- bridge:plan-route=test -->\n```\n\nStamp it only when ALL of the requested work is test code or test tooling:\ntests, flaky-test fixes, fixtures, fakes, harnesses, test configuration,\nrunners, development/test dependencies, test-running CI workflows, and\ndocumentation about those tests.\n\nRules, all load-bearing:\n\n- **Exactly once**, alone on its own line, immediately after the title line.\n Otherwise stamp nothing and let the classifier decide.\n- **Test only.** Never write an `implementation` or `bug` declaration, and never\n put a plan-route declaration on a spike draft.\n- **Never illustratively.** Do not quote or show the marker anywhere else in a\n generated ticket body.\n- **Writer placement is stricter than parser validity.** The parser accepts one\n canonical literal alone on an unfenced, unindented line anywhere in the body.\n A malformed, inline, indented, fenced, or duplicated occurrence is no\n declaration at all. Always place it right after the title anyway.\n\nBe aware of how it renders. Jira shows the marker as one visible line of text.\nUnlike the weak-hint execution-class marker, a valid plan-route declaration\n**binds** plan routing. Stamped on a ticket that changes production code, it\nmeans planning fails visibly, and ticket review will recommend a split or\nremoving the declaration. When unsure, stamp nothing.\n\n### Writing Guidelines\n\n**Summary**:\n- Be concrete, not abstract. Name the actual components, cartridges, or subsystems involved.\n- State the \"why\" — what problem does this solve or what value does it add?\n- Mention the general technical approach if it's clear from the research.\n\n**Requirements**:\n- Each requirement should represent a logical unit of work\n- Order requirements in a logical implementation sequence when possible\n- ALWAYS cite relevant existing files and functions when they exist. Use exact file paths relative to the project root.\n- Explain HOW the existing code relates: \"extend this function\", \"follow this pattern\", \"reuse this helper\", \"modify this configuration\"\n- If a requirement involves creating new files, suggest where they should live based on existing project structure conventions\n- Be specific about what needs to change vs. what needs to be created new\n- Include requirements for tests, documentation, and configuration/metadata changes if applicable\n- **Design/UI Requirements (when an `applicable: true` comp→codebase map is provided)**: cite the mapped components, Jinja2 templates, CSS/SCSS tokens or design-system styles, and routes from the map with concrete phrasing — \"reuse `X` component\", \"extend template `Y`\", \"use token/style `Z`\", \"wire route `R`\" — so the ticket expresses HOW to realize the comp in code that already exists, not generic \"match the comp\" prose. Keep the depth class-appropriate per the map's classification.\n\n**Acceptance Criteria**:\n- Every criterion must be independently verifiable\n- Cover functional requirements, edge cases, testing, and non-functional requirements\n- Include criteria for backwards compatibility if relevant\n- Include criteria for test coverage\n- Use plain `-` bullets (Jira's ADF has no native checkbox, so `- [ ]` renders as literal text)\n- **Epic child with `owns_verifications`**: each obligation appears verbatim as its own bullet, prefixed with its epic AC id — never reworded, merged, or dropped. An epic parent adds `## Epic coverage map` right after this section. See \"Epic coverage: child obligations and the parent coverage map\" above.\n- **Design/UI tickets**: whenever the ticket references or attaches a design comp (mockup, wireframe, or design/UI reference), ALWAYS include an explicit **visual-fidelity acceptance criterion**. Word it so the implementing agent must fetch/open the comp by its `attachment_id` or path and verify **class-appropriate** visual fidelity against it — strict pixel/visual match only for a full comp; layout-only for a wireframe; current-state-plus-delta for an annotated screenshot; the repo design-system floor otherwise. Do not settle for inert \"record-only\" prose that the implementing agent cannot act on. When an `applicable: true` comp→codebase map (`comp-analysis.json`) is available, the criterion should reference BOTH the concrete comp source AND the comp→codebase map, so the implementing agent verifies fidelity against the same components/tokens the Requirements already cite rather than a bare \"match the comp\".\n\n**Materials Completeness Inventory**:\n- **Fast path — no external materials referenced.** Check this first, before any classification work below. When the ticket references nothing beyond version-controlled code and in-repository documentation (no local files outside the repo, no untracked/gitignored files, no URLs, no attachments, no images), write the `## Materials & Access` section as exactly:\n ```markdown\n ## Materials & Access\n\n No external materials are referenced; all relevant code and documentation is version-controlled.\n ```\n Then skip the rest of this section entirely — no VCS probing (`git ls-files`), no source classification, no phase assignment, no MIME handling, no destination mapping, and none of the remaining inventory taxonomy below applies. This is an early exit for the common case, not a deletion: every rule below stays intact and reachable for a ticket that does reference materials.\n- After the draft is written, INVENTORY every material the ticket references: local file paths, URLs/links, named docs/designs, screenshots, and specs. This pass only INVENTORIES and RECORDS — it does NOT attach anything. The actual attachment of phase-eligible local materials happens post-create (after the Jira `ticket_key` exists) via a separate gather-and-attach step.\n- Classify each material by source using a scheme-based rule (no network probe required):\n - **Local filesystem paths** named in the ticket body are the only **low-risk** materials — but ONLY when the file is **not tracked in version control**. Before listing a local file as attachable, determine its VCS status by running `git ls-files --error-unmatch -- <path>` (exit code `0` means the file is tracked). A version-controlled file is **already available in the repository** — source code, in-repo docs, configs, and any other committed file — and **MUST NOT be attached**; it is cited inline as *Relevant code* in Requirements instead of being re-uploaded. Only local files that are **not tracked in version control** (external technical docs/specs, design comps, or generated artifacts a reviewer dropped locally — including files outside any repo, untracked, or gitignored) are eligible to be gathered and attached post-create. **Never upload code** or any file already in version control.\n - Every **`http(s)` URI is external/auth-gated** — regardless of whether the user explicitly linked it (an explicitly-linked Confluence or Google Doc URL is still external/auth-gated) — and is **record-only** here.\n - **Binary/image materials** (ordinary screenshots, PDFs, and unrelated binaries) are **record-only** — document them with sanitized location/access notes; do NOT attempt to attach them. This record-only rule does not apply to local design/UI comp images (see next bullet).\n - **Assign each phase-eligible local material (or existing Jira attachment comp reference) an explicit phase** — *Planning Assets* (informs plan generation only), *Downloadable Assets* (the implementing agent must fetch/place it on disk; the planner does not need its content), or *Planning & Downloadable Assets* (both). Base the phase on how the material will actually be used: a spec, research note, or non-actionable reference is planning-only; a fixture, data file, or non-comp binary the implementer must save to a specific path is downloadable-only; a **design/UI comp** (a mockup, wireframe, or design reference for a design/UI ticket) is virtually always *Planning & Downloadable Assets* since the planner should see it AND the implementing agent must fetch it for visual-fidelity verification. When the correct phase is genuinely unclear, default the material to **Planning Assets** and append a sanitized inline note reading `Phase ambiguous; defaulted to planning` — never silently drop an ambiguous material.\n - For every phase-bearing bullet, retain: the local source path (or `attachment_id` when the material already exists as a Jira attachment rather than a local file), the material type or MIME detail, and an **original repository-relative destination path** — where the implementing agent should save the file once fetched. Determine the destination from where the ticket body or research indicates the file belongs in the repo (e.g. a fixture referenced as living at `tests/fixtures/data.json`, a comp destined for `designs/checkout-comp.png`). If no safe repository-relative destination can be established, still record the material under its chosen phase heading and append a non-blocking note that the destination is unknown — never drop the material for lacking a destination.\n - A **local design/UI comp image** — a reachable local file whose executable local path resolves and whose extension maps to an allowlisted image MIME type (`image/png`, `image/jpeg`, `image/webp`, `image/gif`) — is recorded under *Planning & Downloadable Assets* (or another phase heading if genuinely single-purpose) and remains eligible for post-create attachment through the allowlisted binary upload path (the gather-and-attach step uploads it, not just references it), preserving the existing visual-fidelity review behavior. A comp that is already a Jira attachment (this or another ticket) is recorded the same way using its `attachment_id`, filename, and MIME type instead of a local source path, plus a note that the executor should use the Jira attachment download capability to save it to the recorded destination `file_path`. An external/auth-gated design link remains fetchable/reference material for implementation-time download, not a local re-upload target — it stays under *External/Auth-Gated Links*, unphased.\n - **Comp→codebase map** (`comp-analysis.json`): when the final ticket references the map, inventory it as a **planning-only local text file** under *Planning Assets* (it is a low-risk local JSON text artifact informing the plan, not something the implementer fetches separately). The version-control gate still applies: attach it only when it is **not tracked in version control** (a generated artifact normally is not); if it happens to be committed, it is already available in the repo and is not re-uploaded.\n- Write the trailing `## Materials & Access` section (the LAST section of the draft) grouping items under the sub-headings *Planning Assets*, *Downloadable Assets*, *Planning & Downloadable Assets* (each only when a material of that phase exists), *External/Auth-Gated Links*, and *Binary/Image Materials (Record-Only)*, using bulleted lists. Use monospace formatting (backticks) for technical provenance such as file paths, attachment IDs, and destination paths. *External/Auth-Gated Links* and *Binary/Image Materials (Record-Only)* are never phase-tagged and stay behaviorally unchanged.\n- **Redact secrets before writing anything**: before writing any URL or access note, sanitize and redact embedded credentials, SAS tokens, API keys, and basic-auth secrets using a high-visibility placeholder such as `[REDACTED_TOKEN]`. A location/access note must NEVER expose a plaintext secret.\n\n### Regression Completeness Pass (Gated)\n\nAfter the draft (including its `## Materials & Access` section) is written, run this pass. It is a non-blocking, **warn-not-halt** completeness check — it never blocks or fails ticket creation, and it never modifies the Requirements or Acceptance Criteria text directly.\n\n1. **Check the caller-provided gate first — you must NOT resolve it yourself.** The invoking caller (the calling ticket-authoring workflow's own setup stage, or the `/idea-to-ticket` draft-and-critique instruction's setup) resolves the per-repo `enable_regression_checks` setting once, before invoking you, and states the result explicitly in this prompt as `enable_regression_checks: true` or `enable_regression_checks: false`. You must never call the `config_field` MCP tool to look this value up yourself — the caller has already resolved it, and re-checking it here would be a redundant MCP call whose result cannot change the outcome. If the prompt you were given does not state `enable_regression_checks: true` exactly — it is missing, `false`, or any other/malformed value — **skip this entire pass** — the draft is produced exactly as it would be without this section (byte-for-byte unchanged). The recommended default for this flag is OFF (unset) for safe rollout; only proceed past this step when the caller explicitly passed `enable_regression_checks: true`.\n\n2. **Derive the touched-symbol set.** From the draft's Requirements and *Relevant code* citations (or, if the ticket references an existing diff/PR, that diff/PR), extract the specific function/class/symbol names the proposed change touches.\n\n3. **Run the deterministic core.** Execute:\n ```bash\n npx -y @bridge_gpt/mcp-server regression-check --mode lightweight --json --symbols <derived,symbol,names>\n ```\n This is the same subcommand the standalone `regression-reviewer` agent and `regression-check` command use — do not hand-roll your own `ast-grep`/`ripgrep` discovery.\n\n4. **Fail-open on a degraded or failed run.** If the command errors, or `summary.degraded_flags` is non-empty, record that the pass ran degraded (or could not run) and proceed — the draft is still produced. Never halt ticket creation because this subcommand was unavailable.\n\n5. **Cross-check against Requirements + Acceptance Criteria.** Parse the JSON `findings` array (`symbol`, `call_sites.by_file`, `broad_mentions`). For each symbol, compare its real call-sites and broad mentions against what the draft's Requirements and Acceptance Criteria already cover. Flag any affected caller, migration, or contract (a file with a real call-site or an uninspected broad mention) that the criteria do NOT mention.\n\n6. **Record the flags — never rewrite Requirements/Acceptance Criteria.** Append a `[WARNING]` block immediately before the `## Materials & Access` section, listing each flagged item:\n ```markdown\n ## Regression Completeness Notes\n\n [WARNING] The following systems were not explicitly addressed in the Requirements or Acceptance Criteria above:\n - `path/to/affected_caller.py` — calls `changed_symbol` (N real call-sites); not mentioned in Requirements\n - `path/to/config.yml` — broad mention of `changed_symbol`; verify this reference is unaffected\n\n Degraded: [list summary.degraded_flags, or \"none — full structural analysis ran\"]\n ```\n If no flags were raised and the run was not degraded, write a single line instead: `Regression completeness pass: no unaddressed systems found.` If the run was degraded with zero findings either way, state that explicitly rather than implying a clean pass.\n\n### Output Formatting (Jira upload)\n\nThe ticket is uploaded to Jira, which converts the Markdown to Atlassian Document Format (ADF) and hard-caps the description at **32,767 characters**. Keep the output clean and within budget:\n\n- **Length**: aim for under ~30,000 characters. If the scope genuinely needs more, split into a parent ticket plus sub-tickets rather than one oversized ticket.\n- **Acceptance Criteria**: plain `-` bullets, not `- [ ]` (ADF has no native checkbox).\n- **No images**: do not embed images or use relative image links. This \"No images\" rule applies strictly to inline images in the description body; it does NOT restrict the attachments produced by the Materials Completeness Inventory / gather-and-attach pass, nor does it forbid recording a fetchable design/UI comp reference (its `attachment_id` or path).\n- **No empty headings**: every heading must have text on its line.\n- **Placeholders**: prefer `{placeholder}` over `<placeholder>`.\n\n## Quality Standards\n\n- **No vague language**: Replace \"should handle errors properly\" with \"should catch LLM provider timeouts and return a normalized error response with errorType 'TimeoutError'\"\n- **No assumptions without evidence**: Only reference code you actually read during research. If you're unsure about something, say so explicitly in the ticket.\n- **Appropriate scope**: The ticket should represent a coherent, deliverable unit of work. If the problem is too large, note that it may need to be broken into sub-tasks, but still write the parent ticket.\n- **Developer empathy**: Write as if the developer picking this up has general project knowledge but hasn't recently worked on this specific area. Give them enough context to get started quickly.\n\n## Important Reminders\n\n- Match the depth of the research phase to the scope of the change — do not abbreviate research a cross-cutting or ambiguous change genuinely needs, but a small, isolated change may stop once its relevant implementation path and conventions are understood.\n- Do NOT make up file paths or function names. Only reference code you have actually found and read.\n- DO create the markdown file — do not just output the content to the chat. Write it to disk.\n- If the project has specific conventions (from CLAUDE.md or similar), ensure your ticket's requirements align with those conventions.\n"
|
|
21
21
|
},
|
|
22
22
|
"refactor-reviewer": {
|
|
23
23
|
"frontmatter": {
|
package/build/bounded-wait.js
CHANGED
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
* keeps the reason vocabulary below closed: the primitive can only report states
|
|
25
25
|
* it can itself observe.
|
|
26
26
|
*
|
|
27
|
-
* NO RAW UPSTREAM DATA ESCAPES. A result carries a closed-vocabulary reason
|
|
28
|
-
*
|
|
27
|
+
* NO RAW UPSTREAM DATA ESCAPES. A result carries a closed-vocabulary reason plus
|
|
28
|
+
* whatever already-sanitized detail the caller's probe attached. Exception text, response bodies, headers, URLs, and credentials
|
|
29
29
|
* are never read into a result — a caller that needs upstream detail must obtain
|
|
30
30
|
* it inside its own probe and sanitize it there, the same bar
|
|
31
31
|
* `formatRecoverablePollGiveUp` sets for the existing poller.
|
|
@@ -48,7 +48,39 @@ const TERMINAL_REASON_SENTENCES = {
|
|
|
48
48
|
lost_after_retries: "The server restarted during the run and gave up after its automatic retries.",
|
|
49
49
|
handler_failed: "The run failed on the server.",
|
|
50
50
|
expired: "The run expired on the server before it finished.",
|
|
51
|
+
insufficient_ticket_detail: "The ticket description is too short for the server to work from.",
|
|
52
|
+
completed_without_artifact: "The run finished on the server without producing a document.",
|
|
51
53
|
};
|
|
54
|
+
/** Plain sentences for the probe-named terminal reasons (BAPI-1382). */
|
|
55
|
+
const WAIT_REASON_SENTENCES = {
|
|
56
|
+
NO_REVIEW_REQUESTED: "No review request is known for this ticket, and none is running on the server.",
|
|
57
|
+
NO_REVIEW_RUNNING: "A review was submitted, but the server has no review running and no document for it.",
|
|
58
|
+
};
|
|
59
|
+
/** The plain sentence for a probe-named wait reason, or `null` for the others. */
|
|
60
|
+
export function formatWaitReasonSentence(reason) {
|
|
61
|
+
return WAIT_REASON_SENTENCES[reason] ?? null;
|
|
62
|
+
}
|
|
63
|
+
function isWholeNonNegative(value) {
|
|
64
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The insufficient-detail sentence. It names whichever measurements the server
|
|
68
|
+
* sent — "26 of 200 characters" with both, the minimum alone when the length
|
|
69
|
+
* was not stored (the spawn-mode and review-run paths) — and otherwise falls
|
|
70
|
+
* back to the generic sentence, so a missing field is never printed as
|
|
71
|
+
* `undefined`. The server's `retry_hint` carries the next step.
|
|
72
|
+
*/
|
|
73
|
+
function insufficientDetailSentence(detail) {
|
|
74
|
+
const measured = detail?.measuredLength;
|
|
75
|
+
const threshold = detail?.threshold;
|
|
76
|
+
if (isWholeNonNegative(measured) && isWholeNonNegative(threshold)) {
|
|
77
|
+
return `The ticket description is too short (${measured} of ${threshold} characters).`;
|
|
78
|
+
}
|
|
79
|
+
if (isWholeNonNegative(threshold)) {
|
|
80
|
+
return `The ticket description is shorter than the ${threshold}-character minimum.`;
|
|
81
|
+
}
|
|
82
|
+
return TERMINAL_REASON_SENTENCES.insufficient_ticket_detail;
|
|
83
|
+
}
|
|
52
84
|
/**
|
|
53
85
|
* Build the plain recovery message for a terminal outcome, or `null` when the
|
|
54
86
|
* server sent neither a reason nor a retry hint (the caller then keeps its
|
|
@@ -60,6 +92,14 @@ export function formatTerminalRecoveryMessage(detail) {
|
|
|
60
92
|
if (!reason && !retryHint)
|
|
61
93
|
return null;
|
|
62
94
|
const parts = [];
|
|
95
|
+
if (reason === "insufficient_ticket_detail") {
|
|
96
|
+
parts.push(insufficientDetailSentence(detail));
|
|
97
|
+
// The server's hint for this reason is a full sentence naming the fix and
|
|
98
|
+
// the tool call, so it is printed as-is rather than after "Retry with:".
|
|
99
|
+
parts.push(retryHint ||
|
|
100
|
+
"Lengthen the ticket description (use update_ticket_description), then submit a new request.");
|
|
101
|
+
return parts.join(" ");
|
|
102
|
+
}
|
|
63
103
|
if (reason) {
|
|
64
104
|
parts.push(TERMINAL_REASON_SENTENCES[reason] ?? `The server reported: ${reason}.`);
|
|
65
105
|
}
|
|
@@ -123,12 +163,21 @@ export async function boundedWait(options) {
|
|
|
123
163
|
signal?.addEventListener("abort", finish, { once: true });
|
|
124
164
|
});
|
|
125
165
|
let consecutiveFailures = 0;
|
|
166
|
+
// BAPI-1356: latched, never reset. Set only where the server actually
|
|
167
|
+
// answered; a throw, a probe timeout, or a transient streak leaves it alone.
|
|
168
|
+
let observed = false;
|
|
169
|
+
const pending = (reason) => ({
|
|
170
|
+
state: "pending",
|
|
171
|
+
reason,
|
|
172
|
+
observed,
|
|
173
|
+
windowMs: effectiveBudgetMs,
|
|
174
|
+
});
|
|
126
175
|
try {
|
|
127
176
|
while (true) {
|
|
128
177
|
if (signal?.aborted)
|
|
129
178
|
return { state: "error", reason: "ABORTED" };
|
|
130
179
|
if (remaining() <= 0) {
|
|
131
|
-
return
|
|
180
|
+
return pending("WAIT_WINDOW_ELAPSED");
|
|
132
181
|
}
|
|
133
182
|
// One controller per probe, aborted by EITHER the per-probe timeout or the
|
|
134
183
|
// caller's signal. The probe therefore never outlives its own budget, and a
|
|
@@ -164,15 +213,22 @@ export async function boundedWait(options) {
|
|
|
164
213
|
if (threw || timedOut || outcome === null) {
|
|
165
214
|
consecutiveFailures += 1;
|
|
166
215
|
if (consecutiveFailures >= MAX_CONSECUTIVE_POLL_FAILURES) {
|
|
167
|
-
return
|
|
216
|
+
return pending("UPSTREAM_TRANSIENT");
|
|
168
217
|
}
|
|
169
218
|
}
|
|
170
219
|
else if (outcome.kind === "terminal") {
|
|
171
|
-
// Recovery detail
|
|
172
|
-
// captured some, so a legacy terminal result keeps
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
220
|
+
// Recovery detail and upstream evidence are additive: each is attached
|
|
221
|
+
// only when the probe captured some, so a legacy terminal result keeps
|
|
222
|
+
// its exact shape.
|
|
223
|
+
const terminal = {
|
|
224
|
+
state: "error",
|
|
225
|
+
reason: outcome.reason ?? "UPSTREAM_TERMINAL",
|
|
226
|
+
};
|
|
227
|
+
if (outcome.recovery)
|
|
228
|
+
terminal.recovery = outcome.recovery;
|
|
229
|
+
if (outcome.upstream)
|
|
230
|
+
terminal.upstream = outcome.upstream;
|
|
231
|
+
return terminal;
|
|
176
232
|
}
|
|
177
233
|
else if (outcome.kind === "malformed") {
|
|
178
234
|
return { state: "error", reason: "INVALID_RESPONSE" };
|
|
@@ -181,6 +237,7 @@ export async function boundedWait(options) {
|
|
|
181
237
|
// The server answered. Reset the streak BEFORE evaluating readiness — an
|
|
182
238
|
// answer of "not yet" is still proof the transport is healthy.
|
|
183
239
|
consecutiveFailures = 0;
|
|
240
|
+
observed = true;
|
|
184
241
|
if (outcome.ready === true) {
|
|
185
242
|
const value = outcome.value;
|
|
186
243
|
if (isReady === undefined || isReady(value)) {
|
|
@@ -189,7 +246,7 @@ export async function boundedWait(options) {
|
|
|
189
246
|
}
|
|
190
247
|
}
|
|
191
248
|
if (remaining() <= 0) {
|
|
192
|
-
return
|
|
249
|
+
return pending("WAIT_WINDOW_ELAPSED");
|
|
193
250
|
}
|
|
194
251
|
// The existing poller's curve plus bounded jitter, clamped to whatever is
|
|
195
252
|
// actually left so a backoff can never overrun the deadline it is inside.
|