@bridge_gpt/mcp-server 0.2.66 → 0.2.68
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 +421 -83
- package/build/agent-capabilities/probes.js +15 -5
- package/build/agent-launchers/claude-executor-adapter.js +70 -16
- package/build/agent-launchers/executor-adapter.js +7 -4
- package/build/agents.generated.js +22 -2
- package/build/bitbucket-workspace-remote.js +136 -0
- package/build/bootstrap-project-identity.js +24 -0
- package/build/bounded-wait.js +67 -10
- package/build/bridge-permissions.js +829 -0
- package/build/claude-settings.js +18 -0
- package/build/cli-help.js +21 -0
- package/build/command-assets-doctor.js +24 -70
- package/build/command-provisioning.js +29 -5
- package/build/commands.generated.js +12 -12
- package/build/conductor/bridge-api-client.js +857 -1
- package/build/conductor/bring-up-facts.js +1 -1
- package/build/conductor/command-drift-production.js +92 -0
- package/build/conductor/command-drift.js +89 -0
- package/build/conductor/doctor.js +72 -29
- package/build/conductor/epic-branch-ci-inspection.js +1375 -0
- package/build/conductor/epic-implementer-cli.js +98 -28
- package/build/conductor/errors.js +4 -3
- package/build/conductor/gate-discovery.js +890 -0
- package/build/conductor/guided-repair.js +6 -2
- package/build/conductor/hook-runtime-qualification.js +1 -1
- package/build/conductor/install-doctor.js +15 -3
- package/build/conductor/json-output.js +63 -0
- 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 +113 -12
- package/build/conductor/readiness-sections.js +410 -9
- package/build/conductor/readiness.js +388 -33
- package/build/conductor/recovery-cli.js +310 -12
- package/build/conductor/recovery-operations.js +167 -3
- package/build/conductor/repair-contract.js +54 -3
- package/build/conductor/repository-workflow-remedy.js +125 -0
- package/build/conductor/tools.js +2 -2
- package/build/conductor-bundle-artifacts.js +8 -0
- package/build/connect-bitbucket-api.js +41 -6
- package/build/connect-bitbucket.js +106 -33
- package/build/credential-store.js +207 -47
- package/build/docs.generated.js +62 -3
- package/build/doctor.js +92 -1
- package/build/drive-epic-key.js +6 -5
- package/build/drive-epic.js +129 -61
- package/build/epic-coherence-input.js +2 -1
- package/build/epic-coherence-input.test-fixtures.js +4 -3
- package/build/epic-implementer-bin.js +1379 -28
- package/build/epic-implementer-bundle-cli.js +2 -2
- package/build/epic-workarounds-path.js +39 -0
- package/build/executor/cli.js +33 -2
- package/build/executor/completion-contract.js +66 -10
- package/build/executor/completion-supervision.js +4 -0
- package/build/executor/deps.js +4 -1
- package/build/executor/env.js +19 -0
- package/build/executor/gate-qualification-job.js +499 -0
- package/build/executor/guarded-fast-forward-push.js +129 -0
- package/build/executor/heartbeat.js +1 -1
- package/build/executor/http-client.js +20 -0
- package/build/executor/integration-job.js +122 -8
- package/build/executor/job-errors.js +33 -1
- package/build/executor/job-runner.js +813 -87
- package/build/executor/likely-tests.js +290 -0
- package/build/executor/live-worker-registry.js +1 -1
- package/build/executor/observation.js +37 -3
- package/build/executor/packaged-recipe.js +106 -0
- package/build/executor/permissions.js +115 -28
- package/build/executor/platform.js +1 -1
- package/build/executor/preflight.js +112 -26
- package/build/executor/process-containment.js +36 -12
- package/build/executor/process.js +15 -7
- package/build/executor/prompt-spec.js +33 -1
- package/build/executor/recently-failed-tests-file.js +71 -0
- package/build/executor/reconcile-overlap-job.js +41 -23
- package/build/executor/required-verification.js +1 -1
- package/build/executor/required-work-ledger.js +111 -18
- package/build/executor/resume-pre-spawn.js +2 -0
- package/build/executor/retirement.js +69 -0
- package/build/executor/runner.js +122 -13
- package/build/executor/service-unit.js +7 -1
- package/build/executor/test-clock.js +15 -0
- package/build/executor/types.js +32 -0
- package/build/executor/viewer-tabs.js +58 -8
- package/build/executor/worker-command.js +23 -7
- package/build/executor/worker-diagnostics.js +37 -0
- package/build/executor/worker-finalization.js +113 -34
- package/build/executor/worker-guard-hook.js +316 -58
- package/build/executor/worker-log.js +4 -3
- package/build/executor/worker-verification-guidance.js +37 -0
- package/build/executor/worktree.js +166 -8
- package/build/first-build-advisory.js +97 -0
- package/build/generated-assets-advisory.js +14 -0
- package/build/generated-assets-catalog.js +66 -0
- package/build/generated-assets-context.js +46 -0
- package/build/generated-assets-dependencies.js +26 -0
- package/build/generated-assets-doctor.js +50 -0
- package/build/generated-assets-health.js +65 -0
- package/build/generated-assets-host.js +17 -0
- package/build/generated-assets-inventory.js +17 -0
- package/build/generated-assets-paths.js +60 -0
- package/build/generated-assets-publisher.js +133 -0
- package/build/generated-assets-remedy.js +25 -0
- package/build/generated-assets-repair.js +72 -0
- package/build/generated-assets-session.js +71 -0
- package/build/generated-assets-test-support.js +35 -0
- package/build/index.js +1224 -1054
- package/build/init.js +54 -177
- package/build/install-bridge-conductor-gate.js +1372 -0
- package/build/install-bridge-conductor.js +296 -41
- package/build/install-bridge.js +547 -40
- package/build/mcp-install-state.js +50 -4
- package/build/merge-workflow-install.js +3 -4
- package/build/phase-context-git-probe.js +68 -0
- package/build/phase-result-artifacts.js +173 -5
- package/build/pipeline-utils.js +6 -2
- package/build/pipelines.generated.js +34 -160
- package/build/plan-execution-ledger.js +2 -32
- 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 +136 -2
- package/build/plane/member-roster.js +94 -19
- package/build/plane/preflight.js +83 -2
- package/build/plane/process-cwd.js +67 -0
- package/build/plane/restart.js +542 -0
- package/build/plane/shutdown.js +31 -0
- package/build/plane/status.js +143 -8
- package/build/plane/supervisor.js +586 -179
- package/build/plane/types.js +54 -2
- package/build/printed-remedy-commands.js +52 -0
- package/build/readiness-check.js +1 -1
- package/build/readme.generated.js +1 -1
- package/build/repair-assets.js +93 -0
- package/build/review-generation.js +22 -0
- package/build/run-unit-tests-launcher.js +130 -49
- package/build/secret-redaction.js +33 -0
- package/build/setup-epic.js +152 -22
- package/build/sfcc/config.js +9 -0
- package/build/sfcc/credentials.js +100 -38
- package/build/sfcc/hostname.js +67 -0
- package/build/sfcc/permissions.js +17 -13
- package/build/sfcc/register.js +1 -1
- package/build/sfcc/tool-wrapper.js +39 -43
- package/build/sfcc/write-grants.js +17 -11
- package/build/sfcc/write-result.js +4 -3
- package/build/sfcc/writes-site-preference.js +20 -8
- package/build/start-tickets-prereqs.js +70 -16
- package/build/start-tickets.js +208 -36
- package/build/tool-error-envelope.js +61 -0
- package/build/tool-surface-gating.js +36 -0
- package/build/upgrade-cli.js +55 -1
- package/build/version.generated.js +3 -3
- package/build/visual-diff.js +22 -1
- package/build/worker-guard-hook-bin.js +4 -6
- package/build/worker-guard-hook-main.js +47 -0
- package/build/worktree-core.js +145 -6
- package/docs/EPIC-IMPLEMENTER-INSTALL.md +867 -0
- package/docs/EPIC-IMPLEMENTER.md +728 -0
- package/docs/install/mcp-tool-integrations.md +24 -15
- package/docs/install/sfcc-integration.md +12 -4
- package/package.json +3 -3
- package/pipelines/greenfield-setup.json +4 -128
- package/pipelines/implement-ticket.json +3 -2
- 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
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The shared contract for the
|
|
2
|
+
* The shared contract for the eight operator repair verbs: BAPI-1154's
|
|
3
3
|
* `scope-reparse`, `dispatch-remediation`, and `merge-child`, BAPI-1176's
|
|
4
|
-
* `scope-catch-up`, `patch-policy`, and `resume-run`,
|
|
5
|
-
* ticket-addressed `resume-ci`.
|
|
4
|
+
* `scope-catch-up`, `patch-policy`, and `resume-run`, BAPI-1326's
|
|
5
|
+
* ticket-addressed `resume-ci`, and BAPI-1388's ticket-addressed `rerun-ci`.
|
|
6
6
|
*
|
|
7
7
|
* CLI-only, exactly like the BAPI-872 recovery verbs: none of these names is ever
|
|
8
8
|
* registered as an MCP tool (`index-static.test.ts` pins that). This module is the
|
|
@@ -22,6 +22,7 @@ export const VERB_SCOPE_CATCH_UP = "scope-catch-up";
|
|
|
22
22
|
export const VERB_PATCH_POLICY = "patch-policy";
|
|
23
23
|
export const VERB_RESUME_RUN = "resume-run";
|
|
24
24
|
export const VERB_RESUME_CI = "resume-ci";
|
|
25
|
+
export const VERB_RERUN_CI = "rerun-ci";
|
|
25
26
|
/** Every repair verb, in the order an operator meets them. */
|
|
26
27
|
export const OPERATOR_REPAIR_VERBS = [
|
|
27
28
|
VERB_SCOPE_REPARSE,
|
|
@@ -31,6 +32,7 @@ export const OPERATOR_REPAIR_VERBS = [
|
|
|
31
32
|
VERB_PATCH_POLICY,
|
|
32
33
|
VERB_RESUME_RUN,
|
|
33
34
|
VERB_RESUME_CI,
|
|
35
|
+
VERB_RERUN_CI,
|
|
34
36
|
];
|
|
35
37
|
/**
|
|
36
38
|
* The ONE cap on driver context, in UTF-8 BYTES — the same number the server
|
|
@@ -156,3 +158,52 @@ export const CI_RESUME_RECEIPT_RETENTION = "latest_only";
|
|
|
156
158
|
export function isCiResumeRefusalCode(code) {
|
|
157
159
|
return (typeof code === "string" && CI_RESUME_REFUSAL_CODES.includes(code));
|
|
158
160
|
}
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
// BAPI-1388 — `rerun-ci`
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
/**
|
|
165
|
+
* The closed `rerun-ci` refusal subset — mirrors `RERUN_CI_REFUSAL_CODES` in
|
|
166
|
+
* `operator_repair_contract.py`. It mints no new code: every member already
|
|
167
|
+
* exists in {@link OPERATOR_REPAIR_REFUSAL_CODES}.
|
|
168
|
+
*/
|
|
169
|
+
export const RERUN_CI_REFUSAL_CODES = [
|
|
170
|
+
"TICKET_NOT_IN_RUN",
|
|
171
|
+
"GUIDED_REPAIR_DISABLED",
|
|
172
|
+
"REPAIR_SUBJECT_STALE",
|
|
173
|
+
"RUN_NOT_DISPATCHABLE",
|
|
174
|
+
"TICKET_NOT_REMEDIABLE",
|
|
175
|
+
"REMEDIATION_JOB_IN_FLIGHT",
|
|
176
|
+
"REMEDIATION_LANE_BUSY",
|
|
177
|
+
"REMEDIATION_HELD",
|
|
178
|
+
"REMEDIATION_NO_PULL_REQUEST",
|
|
179
|
+
"ROW_VERSION_CONFLICT",
|
|
180
|
+
"DISPATCH_ENQUEUE_FAILED",
|
|
181
|
+
];
|
|
182
|
+
/** True when `code` is one of the closed `rerun-ci` refusal codes. */
|
|
183
|
+
export function isRerunCiRefusalCode(code) {
|
|
184
|
+
return (typeof code === "string" && RERUN_CI_REFUSAL_CODES.includes(code));
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* The bounded reason a `rerun-ci` `ROW_VERSION_CONFLICT` carries when releasing a
|
|
188
|
+
* reservation lost its guarded write. Whether the unenqueued workflow runs are
|
|
189
|
+
* still recorded is UNCONFIRMED; the CLI exits 3.
|
|
190
|
+
*/
|
|
191
|
+
export const RERUN_CI_UNCONFIRMED_REASON = "rerun_ci_unconfirmed";
|
|
192
|
+
/**
|
|
193
|
+
* The closed per-check outcome vocabulary. None of them says a check is green: a
|
|
194
|
+
* provider re-run is an action, not a verdict.
|
|
195
|
+
*/
|
|
196
|
+
export const RERUN_CI_CHECK_STATUSES = [
|
|
197
|
+
"enqueued",
|
|
198
|
+
"deduplicated",
|
|
199
|
+
"deferred",
|
|
200
|
+
"not_re_runnable",
|
|
201
|
+
];
|
|
202
|
+
/** Why one failed check is not re-run: a closed token. */
|
|
203
|
+
export const RERUN_CI_NOT_RUNNABLE_REASONS = [
|
|
204
|
+
"not_github_actions",
|
|
205
|
+
"no_workflow_run_id",
|
|
206
|
+
"claude_review_excluded",
|
|
207
|
+
];
|
|
208
|
+
/** The closed overall outcomes of an applied `rerun-ci`. */
|
|
209
|
+
export const RERUN_CI_OUTCOMES = ["enqueued", "partial", "nothing_to_enqueue"];
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/** The two fixed workflow files unattended auto-merge reads. Named once here. */
|
|
2
|
+
export const REVIEW_WORKFLOW_PATH = ".github/workflows/claude-review.yml";
|
|
3
|
+
export const CI_WORKFLOW_PATH = ".github/workflows/epic-implementer-ci.yml";
|
|
4
|
+
/** The migration guard's stable job id and its exact checker invocation. */
|
|
5
|
+
export const MIGRATION_GUARD_JOB_ID = "migration-guard";
|
|
6
|
+
export const MIGRATION_GUARD_CHECKER_PATH = "scripts/check_alembic_migration_graph.py";
|
|
7
|
+
export const MIGRATION_GUARD_INVOCATION = `python ${MIGRATION_GUARD_CHECKER_PATH}`;
|
|
8
|
+
const RETRY = "Run the readiness check again, or pass --attended. " +
|
|
9
|
+
"If it keeps failing, contact Bridge operations.";
|
|
10
|
+
const TEMPLATES = {
|
|
11
|
+
review_workflow_file: `${REVIEW_WORKFLOW_PATH} is missing from the default branch. Add it, or pass --attended.`,
|
|
12
|
+
review_run_id_marker: `${REVIEW_WORKFLOW_PATH} does not emit the run-id: marker in its verdict comment. ` +
|
|
13
|
+
"Add it, or pass --attended.",
|
|
14
|
+
review_reviewed_sha_marker: `${REVIEW_WORKFLOW_PATH} does not emit the Reviewed-SHA: marker in its verdict comment. ` +
|
|
15
|
+
"Add it, or pass --attended.",
|
|
16
|
+
review_workflow_unverifiable: `Bridge could not read ${REVIEW_WORKFLOW_PATH} on the default branch. ${RETRY}`,
|
|
17
|
+
ci_workflow_file: `${CI_WORKFLOW_PATH} is missing from the default branch. Add it, or pass --attended.`,
|
|
18
|
+
migration_guard_job: `${CI_WORKFLOW_PATH} has no job with id ${MIGRATION_GUARD_JOB_ID} running ${MIGRATION_GUARD_INVOCATION}. ` +
|
|
19
|
+
"It is required because this repository has alembic.ini on its default branch, " +
|
|
20
|
+
`and an equivalent checker at ${MIGRATION_GUARD_CHECKER_PATH} is required too. ` +
|
|
21
|
+
"Add that job, or pass --attended.",
|
|
22
|
+
migration_chain_unverifiable: "Bridge could not confirm whether this repository has alembic.ini on its default branch, " +
|
|
23
|
+
`so it could not decide if the ${MIGRATION_GUARD_JOB_ID} job is required. ${RETRY}`,
|
|
24
|
+
ci_workflow_unverifiable: `Bridge could not read ${CI_WORKFLOW_PATH} on the default branch. ${RETRY}`,
|
|
25
|
+
// An older server never reported whether the guard is conditional, so its
|
|
26
|
+
// guard has always been required. This states that requirement and NOTHING
|
|
27
|
+
// about alembic.ini: an older server observed no such file.
|
|
28
|
+
migration_guard_job_legacy: `${CI_WORKFLOW_PATH} has no job with id ${MIGRATION_GUARD_JOB_ID} running ${MIGRATION_GUARD_INVOCATION}. ` +
|
|
29
|
+
"Add that job, or pass --attended.",
|
|
30
|
+
};
|
|
31
|
+
/** Shown when the report is missing, incomplete, or not in a shape we can name. */
|
|
32
|
+
const UNVERIFIABLE_FALLBACK = "Bridge could not verify the review and CI workflows on the default branch. " + RETRY;
|
|
33
|
+
function classifyReview(w) {
|
|
34
|
+
if (w === null || w === undefined || typeof w !== "object")
|
|
35
|
+
return "malformed";
|
|
36
|
+
if (w.probe_succeeded !== true)
|
|
37
|
+
return ["review_workflow_unverifiable"];
|
|
38
|
+
if (w.workflow_present === false)
|
|
39
|
+
return ["review_workflow_file"];
|
|
40
|
+
if (w.workflow_present !== true ||
|
|
41
|
+
typeof w.emits_run_id !== "boolean" ||
|
|
42
|
+
typeof w.emits_reviewed_sha !== "boolean") {
|
|
43
|
+
return "malformed";
|
|
44
|
+
}
|
|
45
|
+
const missing = [];
|
|
46
|
+
if (!w.emits_run_id)
|
|
47
|
+
missing.push("review_run_id_marker");
|
|
48
|
+
if (!w.emits_reviewed_sha)
|
|
49
|
+
missing.push("review_reviewed_sha_marker");
|
|
50
|
+
return missing;
|
|
51
|
+
}
|
|
52
|
+
function classifyCi(c) {
|
|
53
|
+
if (c === null || c === undefined || typeof c !== "object")
|
|
54
|
+
return "malformed";
|
|
55
|
+
if (c.probe_succeeded !== true)
|
|
56
|
+
return ["ci_workflow_unverifiable"];
|
|
57
|
+
if (c.workflow_present === false)
|
|
58
|
+
return ["ci_workflow_file"];
|
|
59
|
+
if (c.workflow_present !== true || typeof c.migration_guard_present !== "boolean") {
|
|
60
|
+
return "malformed";
|
|
61
|
+
}
|
|
62
|
+
if (c.migration_guard_present)
|
|
63
|
+
return [];
|
|
64
|
+
const required = c.migration_guard_required;
|
|
65
|
+
const chainProbed = c.migration_chain_probe_succeeded;
|
|
66
|
+
// Older server: neither field reported. The guard was always required there,
|
|
67
|
+
// and the absent fields must never read as "not required".
|
|
68
|
+
if (required === undefined && chainProbed === undefined) {
|
|
69
|
+
return ["migration_guard_job_legacy"];
|
|
70
|
+
}
|
|
71
|
+
if (typeof required !== "boolean" || typeof chainProbed !== "boolean")
|
|
72
|
+
return "malformed";
|
|
73
|
+
if (chainProbed && !required)
|
|
74
|
+
return []; // confirmed guard-free
|
|
75
|
+
if (chainProbed && required)
|
|
76
|
+
return ["migration_guard_job"];
|
|
77
|
+
return ["migration_chain_unverifiable"];
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Name the first failing workflow section of a parsed readiness report, its
|
|
81
|
+
* missing facts in stable order, and one fixed-template message.
|
|
82
|
+
*
|
|
83
|
+
* `section: null` with the fallback text means the report is missing or malformed
|
|
84
|
+
* — "could not verify", never "fine". An empty `missing` with `section: null`
|
|
85
|
+
* cannot occur: a report with nothing failing yields the fallback only when it is
|
|
86
|
+
* unreadable, and otherwise the empty result below.
|
|
87
|
+
*/
|
|
88
|
+
export function describeRepositoryWorkflowRemedy(readiness) {
|
|
89
|
+
const review = classifyReview(readiness?.review_workflow);
|
|
90
|
+
if (review === "malformed") {
|
|
91
|
+
return { section: null, missing: [], text: UNVERIFIABLE_FALLBACK };
|
|
92
|
+
}
|
|
93
|
+
if (review.length > 0)
|
|
94
|
+
return build("review_workflow", review);
|
|
95
|
+
const ci = classifyCi(readiness?.conductor_ci_workflow);
|
|
96
|
+
if (ci === "malformed") {
|
|
97
|
+
return { section: null, missing: [], text: UNVERIFIABLE_FALLBACK };
|
|
98
|
+
}
|
|
99
|
+
if (ci.length > 0)
|
|
100
|
+
return build("conductor_ci_workflow", ci);
|
|
101
|
+
return { section: null, missing: [], text: "" };
|
|
102
|
+
}
|
|
103
|
+
function build(section, missing) {
|
|
104
|
+
return { section, missing, text: missing.map((token) => TEMPLATES[token]).join(" ") };
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* The remedy sentence for a repository whose workflows are not confirmed. Never
|
|
108
|
+
* empty: a report with nothing to name (the server said unconfirmed but this
|
|
109
|
+
* client can name no item) gets the unverifiable fallback rather than silence.
|
|
110
|
+
*/
|
|
111
|
+
export function repositoryWorkflowRemedy(readiness) {
|
|
112
|
+
const described = describeRepositoryWorkflowRemedy(readiness);
|
|
113
|
+
return described.text === "" ? UNVERIFIABLE_FALLBACK : described.text;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* The remedy for the CI workflow section alone — what the readiness CI row shows
|
|
117
|
+
* when the migration guard is absent. Same templates, same words as
|
|
118
|
+
* {@link repositoryWorkflowRemedy}, so the row and the refusals cannot drift.
|
|
119
|
+
*/
|
|
120
|
+
export function ciWorkflowRemedy(c) {
|
|
121
|
+
const tokens = classifyCi(c);
|
|
122
|
+
if (tokens === "malformed" || tokens.length === 0)
|
|
123
|
+
return UNVERIFIABLE_FALLBACK;
|
|
124
|
+
return tokens.map((token) => TEMPLATES[token]).join(" ");
|
|
125
|
+
}
|
package/build/conductor/tools.js
CHANGED
|
@@ -82,9 +82,9 @@ function registerExplainRunTool(registerTool) {
|
|
|
82
82
|
// is the field list: a caller cannot discover the answer shape from a
|
|
83
83
|
// tool that only says "explain a run", and the whole point of the tool is
|
|
84
84
|
// that the answer is in one place.
|
|
85
|
-
description: "Explain why
|
|
85
|
+
description: "Explain why an Epic Implementer run, or one ticket in it, is stuck. One snapshot: " +
|
|
86
86
|
"current gate, latest observation and age, outstanding jobs with queue position, " +
|
|
87
|
-
"lease owner/expiry, blocker reasons, expected
|
|
87
|
+
"lease owner/expiry, blocker reasons, expected/observed head SHA, committed and " +
|
|
88
88
|
"pending disposition, usage ceiling. Pass ticket_key to narrow. Read-only; 404 " +
|
|
89
89
|
"returns status 'unknown'.",
|
|
90
90
|
inputSchema: {
|
|
@@ -27,6 +27,14 @@ import { validateTicketKey } from "./ticket-key-utils.js";
|
|
|
27
27
|
const EPIC_SLUG_RE = /^[A-Za-z][A-Za-z0-9_-]*$/;
|
|
28
28
|
/** Placeholder node identity written by `decompose-epic.md` before tickets exist. */
|
|
29
29
|
const PLACEHOLDER_RE = /TBD-\d+/;
|
|
30
|
+
/**
|
|
31
|
+
* A whole plan-node key that is still an authoring placeholder (`TBD-<digits>`),
|
|
32
|
+
* case-insensitive. Anchored on purpose and stateless (no `g` or `y` flag, so
|
|
33
|
+
* `.test()` carries no `lastIndex`). This is NOT {@link PLACEHOLDER_RE}: that one
|
|
34
|
+
* is an unanchored scan of a whole serialized plan, and
|
|
35
|
+
* {@link findResidualPlaceholder} keeps using it unchanged.
|
|
36
|
+
*/
|
|
37
|
+
export const PLACEHOLDER_NODE_KEY_RE = /^TBD-\d+$/i;
|
|
30
38
|
/**
|
|
31
39
|
* The conductor's hard spec bound, mirroring `_SPEC_REVIEW_TICKET_SPEC_MAX_CHARS`
|
|
32
40
|
* (64,000) in `api/library/epic_implementer/reconciler.py`. Since PR #848 the
|
|
@@ -87,6 +87,25 @@ function asString(value) {
|
|
|
87
87
|
function asNullableString(value) {
|
|
88
88
|
return typeof value === "string" && value.length > 0 ? value : null;
|
|
89
89
|
}
|
|
90
|
+
/** Same unambiguous alphabet the server draws its codes and check values from. */
|
|
91
|
+
const APPROVAL_CHECK_RE = /^[ABCDEFGHJKLMNPQRSTUVWXYZ23456789]{4,8}$/;
|
|
92
|
+
/**
|
|
93
|
+
* Decide the mint's mode defensively (BAPI-1401). Approval mode needs BOTH an explicit
|
|
94
|
+
* `approval_mode: "approve"` and a well-formed check value; any other combination is
|
|
95
|
+
* code mode, so a partial or unexpected response can never strand the user without the
|
|
96
|
+
* code they would need.
|
|
97
|
+
*/
|
|
98
|
+
function parseApprovalMode(body) {
|
|
99
|
+
const check = typeof body.approval_check === "string" ? body.approval_check.trim() : "";
|
|
100
|
+
if (body.approval_mode === "approve" && APPROVAL_CHECK_RE.test(check)) {
|
|
101
|
+
return { approvalMode: "approve", approvalCheck: check };
|
|
102
|
+
}
|
|
103
|
+
return { approvalMode: "code", approvalCheck: null };
|
|
104
|
+
}
|
|
105
|
+
/** Parse the optional, additive `reason` on a status body. Unknown → null. */
|
|
106
|
+
function parseStatusReason(value) {
|
|
107
|
+
return value === "declined" ? "declined" : null;
|
|
108
|
+
}
|
|
90
109
|
// ---------------------------------------------------------------------------
|
|
91
110
|
// Retry-After
|
|
92
111
|
// ---------------------------------------------------------------------------
|
|
@@ -121,13 +140,23 @@ export function parseRetryAfterMs(header, remainingMs, nowMs) {
|
|
|
121
140
|
// ---------------------------------------------------------------------------
|
|
122
141
|
// Endpoints
|
|
123
142
|
// ---------------------------------------------------------------------------
|
|
124
|
-
/**
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
143
|
+
/**
|
|
144
|
+
* Mint a CLI-origin connection code and its Forge install-page URL.
|
|
145
|
+
*
|
|
146
|
+
* BAPI-1401: when `workspaceSlug` is given, it is sent as the request's workspace claim,
|
|
147
|
+
* asking the server for an approval-mode connection request. Without it the request body
|
|
148
|
+
* is exactly the legacy shape. Either way the response decides the mode.
|
|
149
|
+
*/
|
|
150
|
+
export async function mintBitbucketConnection(deps, repoName, workspaceSlug) {
|
|
151
|
+
const payload = { repo_name: repoName };
|
|
152
|
+
if (workspaceSlug)
|
|
153
|
+
payload.workspace_slug = workspaceSlug;
|
|
154
|
+
const res = await postJson(deps, "/setup/bitbucket/cli/connection-code", payload);
|
|
129
155
|
if (!res.ok)
|
|
130
156
|
return res;
|
|
157
|
+
if (res.value.status === 429) {
|
|
158
|
+
return { ok: false, kind: "too-many-pending" };
|
|
159
|
+
}
|
|
131
160
|
if (res.value.status !== 200) {
|
|
132
161
|
return { ok: false, kind: classifyStatus(res.value.status) };
|
|
133
162
|
}
|
|
@@ -143,7 +172,11 @@ export async function mintBitbucketConnection(deps, repoName) {
|
|
|
143
172
|
// uses. Required-field strictness above is untouched — an optional field must not
|
|
144
173
|
// become a new way for a server change to fail a mint.
|
|
145
174
|
const redeemUrlTemplate = asNullableString(body.redeem_url_template);
|
|
146
|
-
|
|
175
|
+
const { approvalMode, approvalCheck } = parseApprovalMode(body);
|
|
176
|
+
return {
|
|
177
|
+
ok: true,
|
|
178
|
+
value: { state, installUrl, ttlSeconds, redeemUrlTemplate, approvalMode, approvalCheck },
|
|
179
|
+
};
|
|
147
180
|
}
|
|
148
181
|
/** Strictly parse candidates. A candidate missing a usable identity fails the whole list. */
|
|
149
182
|
function parseCandidates(value) {
|
|
@@ -198,6 +231,7 @@ export async function fetchBitbucketConnectionStatus(deps, repoName, state, nowM
|
|
|
198
231
|
status: status,
|
|
199
232
|
candidates,
|
|
200
233
|
repoDisplayName: asNullableString(body.repo_display_name),
|
|
234
|
+
reason: parseStatusReason(body.reason),
|
|
201
235
|
// A single read has no deadline of its own to clamp against; the poller applies
|
|
202
236
|
// its own bound. POLL_DEADLINE_MS is the widest a caller could honor.
|
|
203
237
|
retryAfterMs: parseRetryAfterMs(res.value.retryAfter, POLL_DEADLINE_MS, nowMs),
|
|
@@ -348,6 +382,7 @@ export async function pollBitbucketConnection(deps, poll, repoName, state) {
|
|
|
348
382
|
status: typed,
|
|
349
383
|
candidates,
|
|
350
384
|
repoDisplayName: asNullableString(body.repo_display_name),
|
|
385
|
+
reason: parseStatusReason(body.reason),
|
|
351
386
|
retryAfterMs: null,
|
|
352
387
|
},
|
|
353
388
|
};
|
|
@@ -5,20 +5,28 @@
|
|
|
5
5
|
* The whole point of this command is that it needs no Bridge web session: a user who has
|
|
6
6
|
* a Bridge API key can connect Bitbucket without ever opening the setup UI. The flow is
|
|
7
7
|
*
|
|
8
|
-
* mint → browser (install the Forge app on Bitbucket,
|
|
9
|
-
*
|
|
8
|
+
* mint → browser (install the Forge app on Bitbucket, then approve the request on the
|
|
9
|
+
* app's workspace settings page) → poll → pick a repository → confirm
|
|
10
10
|
*
|
|
11
11
|
* and the browser half authenticates to *Bitbucket*, not to Bridge. Bridge never asks
|
|
12
12
|
* for, accepts, echoes, or transports a Bitbucket credential. The only thing crossing
|
|
13
13
|
* between the browser and this process is a Bridge-issued state nonce, held in memory.
|
|
14
14
|
*
|
|
15
|
+
* BAPI-1401 — two modes. **Approval mode** (the default when a Bitbucket workspace is
|
|
16
|
+
* known from `--workspace` or this project's git remote, and the server supports it):
|
|
17
|
+
* the mint is a connection request listed on that workspace's Forge settings page, and a
|
|
18
|
+
* workspace admin clicks Approve on the card showing the check value printed here. No
|
|
19
|
+
* code is printed. **Code mode** (`--use-code`, no workspace, or an older server): the
|
|
20
|
+
* legacy flow — the printed code is pasted into the same settings page.
|
|
21
|
+
*
|
|
15
22
|
* Two properties are load-bearing and easy to erode:
|
|
16
23
|
*
|
|
17
24
|
* 1. **The nonce and install URL are never printed to output the CLI narrates around.**
|
|
18
25
|
* The URL carries the state as a query parameter, so putting it in a log line or an
|
|
19
26
|
* error message would paste a live credential-equivalent into the user's scrollback.
|
|
20
|
-
*
|
|
21
|
-
* the one deliberate exception — see `runBitbucketConnectionFlow`
|
|
27
|
+
* In code mode the printed code shown to the user for pasting into Bitbucket's own
|
|
28
|
+
* settings page is the one deliberate exception — see `runBitbucketConnectionFlow`
|
|
29
|
+
* below. In approval mode it is never printed.
|
|
22
30
|
* 2. **Nothing binds without an explicit human choice.** Even a single-repository
|
|
23
31
|
* installation is confirmed by hand — the server stages candidates and this command
|
|
24
32
|
* asks. There is deliberately no `--yes`.
|
|
@@ -35,25 +43,30 @@ import { DEFAULT_BAPI_BASE_URL } from "./install-bridge.js";
|
|
|
35
43
|
import { validateRepoName } from "./bridge-config.js";
|
|
36
44
|
import { resolveStartTicketsRepoName } from "./start-tickets-repo.js";
|
|
37
45
|
import { resolveBapiCredentials, } from "./credential-store.js";
|
|
46
|
+
import { defaultGitRemoteRunner, resolveBitbucketWorkspace, } from "./bitbucket-workspace-remote.js";
|
|
38
47
|
import { POLL_DEADLINE_MS, confirmBitbucketConnection, mintBitbucketConnection, pollBitbucketConnection, } from "./connect-bitbucket-api.js";
|
|
39
|
-
const USAGE = `Usage: connect-bitbucket [--repo <repo_name>] [--workspace <slug>]
|
|
48
|
+
const USAGE = `Usage: connect-bitbucket [--repo <repo_name>] [--workspace <slug>] [--use-code]
|
|
40
49
|
|
|
41
50
|
Connect a Bitbucket repository to a Bridge project from your terminal.
|
|
42
51
|
|
|
43
|
-
Opens the Bitbucket Forge app's install page in your browser
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
repository to connect.
|
|
52
|
+
Opens the Bitbucket Forge app's install page in your browser. Install the app,
|
|
53
|
+
then in Bitbucket open Workspace settings → Forge Apps → "Connect Bitbucket to
|
|
54
|
+
Bridge API" and click Approve on the request this command started (it prints a
|
|
55
|
+
short check value so you can tell it apart). That is how you authenticate to
|
|
56
|
+
Bitbucket; Bridge never asks for a Bitbucket token or app password. Once the
|
|
57
|
+
app reports back, this command asks which single repository to connect.
|
|
58
|
+
|
|
59
|
+
Without a known workspace, or with --use-code, it prints a connection code to
|
|
60
|
+
paste on that same page instead.
|
|
49
61
|
|
|
50
62
|
Options:
|
|
51
63
|
--repo <repo_name> Bridge project to connect (inferred from this directory
|
|
52
64
|
when omitted; you will be asked to confirm).
|
|
53
|
-
--workspace <slug> Bitbucket workspace slug.
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
65
|
+
--workspace <slug> Bitbucket workspace slug. Inferred from this project's
|
|
66
|
+
origin git remote when omitted. Lists the request on that
|
|
67
|
+
workspace's settings page and prints a direct link to it.
|
|
68
|
+
--use-code Print a connection code to paste instead of approving a
|
|
69
|
+
request.
|
|
57
70
|
--help Show this message.`;
|
|
58
71
|
/**
|
|
59
72
|
* Bitbucket workspace slugs are NOT Bridge project names, so they do not go through
|
|
@@ -94,7 +107,7 @@ export function validateBitbucketWorkspace(raw) {
|
|
|
94
107
|
* resolve only through the shared credential-store seam).
|
|
95
108
|
*/
|
|
96
109
|
export function parseConnectBitbucketArgs(argv) {
|
|
97
|
-
const out = { help: false };
|
|
110
|
+
const out = { useCode: false, help: false };
|
|
98
111
|
for (let i = 0; i < argv.length; i += 1) {
|
|
99
112
|
const arg = argv[i];
|
|
100
113
|
if (arg === "--help" || arg === "-h") {
|
|
@@ -137,6 +150,10 @@ export function parseConnectBitbucketArgs(argv) {
|
|
|
137
150
|
out.workspace = validated.value;
|
|
138
151
|
continue;
|
|
139
152
|
}
|
|
153
|
+
if (arg === "--use-code") {
|
|
154
|
+
out.useCode = true;
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
140
157
|
if (arg === "--yes" || arg === "-y") {
|
|
141
158
|
return {
|
|
142
159
|
ok: false,
|
|
@@ -255,6 +272,7 @@ export function createDefaultConnectBitbucketDeps() {
|
|
|
255
272
|
openBrowser: (url) => defaultOpenBrowser(process.platform, url),
|
|
256
273
|
stdout: (message) => process.stdout.write(`${message}\n`),
|
|
257
274
|
stderr: (message) => process.stderr.write(`${message}\n`),
|
|
275
|
+
runGit: defaultGitRemoteRunner,
|
|
258
276
|
};
|
|
259
277
|
}
|
|
260
278
|
/**
|
|
@@ -313,7 +331,7 @@ export async function openBitbucketInstallPage(deps, installUrl) {
|
|
|
313
331
|
// ---------------------------------------------------------------------------
|
|
314
332
|
const STEPS = [
|
|
315
333
|
"Connect Bitbucket",
|
|
316
|
-
"
|
|
334
|
+
"Install the Bitbucket Forge app and connect it in Bitbucket",
|
|
317
335
|
"Verify connection",
|
|
318
336
|
"Choose repository",
|
|
319
337
|
"Confirm connection",
|
|
@@ -333,7 +351,7 @@ function renderStep(deps, index) {
|
|
|
333
351
|
* must never leave the user with nothing. The words are the app's own Bitbucket
|
|
334
352
|
* navigation, so they stay correct without any Bridge-side configuration at all.
|
|
335
353
|
*/
|
|
336
|
-
export const BITBUCKET_REDEEM_NAVIGATION_PATH = 'Workspace settings → Apps → "Connect Bitbucket to Bridge API"';
|
|
354
|
+
export const BITBUCKET_REDEEM_NAVIGATION_PATH = 'Workspace settings → Forge Apps → "Connect Bitbucket to Bridge API"';
|
|
337
355
|
/** The one `{workspace}` token a redemption template may carry. */
|
|
338
356
|
const WORKSPACE_TOKEN = "{workspace}";
|
|
339
357
|
/**
|
|
@@ -431,6 +449,24 @@ function renderRedemptionGuidance(deps, template, workspace) {
|
|
|
431
449
|
deps.stderr(" Replace {workspace} with your Bitbucket workspace slug.");
|
|
432
450
|
}
|
|
433
451
|
}
|
|
452
|
+
/**
|
|
453
|
+
* Print what to do in approval mode (BAPI-1401): install, then Approve the request that
|
|
454
|
+
* shows this run's check value. The code is never printed. `firstRequest` is false on a
|
|
455
|
+
* renewal, where the browser is already open and only the new request's check changes.
|
|
456
|
+
*/
|
|
457
|
+
function renderApprovalGuidance(deps, repoName, approvalCheck, template, workspace, firstRequest) {
|
|
458
|
+
if (firstRequest) {
|
|
459
|
+
deps.stderr("Step 1: install the app in the browser window that just opened.");
|
|
460
|
+
}
|
|
461
|
+
else {
|
|
462
|
+
deps.stderr("A new request was created. The app install page is already open in your browser.");
|
|
463
|
+
}
|
|
464
|
+
deps.stderr(`Step 2: in Bitbucket, go to ${BITBUCKET_REDEEM_NAVIGATION_PATH} and click Approve on ` +
|
|
465
|
+
`the request for ${repoName} showing check ${approvalCheck}.`);
|
|
466
|
+
const link = renderBitbucketRedeemLink(template, workspace);
|
|
467
|
+
if (link)
|
|
468
|
+
deps.stderr(` Direct link: ${link}`);
|
|
469
|
+
}
|
|
434
470
|
/** Display identity for a candidate: prefer the unambiguous workspace/repo slug form. */
|
|
435
471
|
function candidateLabel(c) {
|
|
436
472
|
const slugForm = `${c.workspace_slug}/${c.repository_slug}`;
|
|
@@ -443,6 +479,8 @@ function candidateLabel(c) {
|
|
|
443
479
|
const OUTCOME_MESSAGES = {
|
|
444
480
|
expired: "The connection request expired before the Forge app reported back. Run connect-bitbucket again.",
|
|
445
481
|
invalid: "This connection request is no longer valid. Run connect-bitbucket again.",
|
|
482
|
+
// BAPI-1401: an `invalid` status that carries reason "declined".
|
|
483
|
+
declined: "The connection request was declined in Bitbucket. Run connect-bitbucket again to start a new request.",
|
|
446
484
|
"no-repositories": "The Bitbucket Forge app installation did not include any repositories Bridge can " +
|
|
447
485
|
"access. Re-run connect-bitbucket and grant access to at least one repository.",
|
|
448
486
|
};
|
|
@@ -453,9 +491,13 @@ const FAILURE_MESSAGES = {
|
|
|
453
491
|
"not-found": "Bridge does not recognize this project. Check --repo matches your Bridge project name.",
|
|
454
492
|
server: "Bridge API returned an error. Run connect-bitbucket again shortly.",
|
|
455
493
|
malformed: "Bridge API returned an unexpected response. Run connect-bitbucket again shortly.",
|
|
456
|
-
deadline: "Timed out waiting for the Bitbucket Forge app. If you
|
|
457
|
-
"the code, run connect-bitbucket again to pick up the connection.",
|
|
494
|
+
deadline: "Timed out waiting for the Bitbucket Forge app. If you installed the app and approved " +
|
|
495
|
+
"the request (or pasted the code), run connect-bitbucket again to pick up the connection.",
|
|
496
|
+
"too-many-pending": "Too many Bitbucket connection requests are waiting. Approve or decline them in " +
|
|
497
|
+
"Bitbucket, or wait up to 15 minutes for them to expire, then run connect-bitbucket again.",
|
|
458
498
|
};
|
|
499
|
+
/** BAPI-1401: offered after the first approval-mode expiry or deadline. */
|
|
500
|
+
const USE_CODE_FALLBACK_HINT = "Can't see the request? Re-run with --use-code";
|
|
459
501
|
/** Only an explicit, case-insensitive `y`/`yes` approves. One definition, used everywhere. */
|
|
460
502
|
function isAffirmative(answer) {
|
|
461
503
|
const normalized = answer.trim().toLowerCase();
|
|
@@ -548,7 +590,7 @@ async function chooseCandidate(deps, candidates) {
|
|
|
548
590
|
* a renewal can never confirm against a previous code.
|
|
549
591
|
*/
|
|
550
592
|
export async function runBitbucketConnectionFlow(deps, api, repoName, options = {}) {
|
|
551
|
-
const { workspace, initialMintApproved = false } = options;
|
|
593
|
+
const { workspace, useCode = false, initialMintApproved = false } = options;
|
|
552
594
|
renderStep(deps, 0);
|
|
553
595
|
if (!initialMintApproved) {
|
|
554
596
|
// A standalone run asks before it mints: minting is a side effect (a live code, a
|
|
@@ -559,8 +601,11 @@ export async function runBitbucketConnectionFlow(deps, api, repoName, options =
|
|
|
559
601
|
}
|
|
560
602
|
let browserOpened = false;
|
|
561
603
|
let expiryCount = 0;
|
|
604
|
+
let useCodeHintShown = false;
|
|
562
605
|
for (;;) {
|
|
563
|
-
|
|
606
|
+
// BAPI-1401: the workspace is sent as an approval-mode claim unless the user asked
|
|
607
|
+
// for the code flow. The server's response, not this request, decides the mode.
|
|
608
|
+
const minted = await mintBitbucketConnection(api, repoName, useCode ? undefined : workspace);
|
|
564
609
|
if (!minted.ok) {
|
|
565
610
|
// Includes a renewal mint: a failed fresh mint is terminal and resumable, never
|
|
566
611
|
// silently retried. An automatic retry here would spend codes the user cannot see.
|
|
@@ -568,6 +613,13 @@ export async function runBitbucketConnectionFlow(deps, api, repoName, options =
|
|
|
568
613
|
}
|
|
569
614
|
// Iteration-local. Nothing outside this block may read a previous code.
|
|
570
615
|
const connection = minted.value;
|
|
616
|
+
// Approval mode needs a workspace to have been claimed; a server that answers
|
|
617
|
+
// "approve" to a claimless mint is treated as code mode rather than trusted.
|
|
618
|
+
const approval = connection.approvalMode === "approve" &&
|
|
619
|
+
connection.approvalCheck !== null &&
|
|
620
|
+
!useCode &&
|
|
621
|
+
workspace !== undefined;
|
|
622
|
+
const firstRequest = !browserOpened;
|
|
571
623
|
if (!browserOpened) {
|
|
572
624
|
renderStep(deps, 1);
|
|
573
625
|
deps.stderr("Opening Bitbucket…");
|
|
@@ -581,13 +633,20 @@ export async function runBitbucketConnectionFlow(deps, api, repoName, options =
|
|
|
581
633
|
// both noise and a stale destination.
|
|
582
634
|
browserOpened = true;
|
|
583
635
|
}
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
636
|
+
if (approval) {
|
|
637
|
+
// BAPI-1401: nothing to paste. `connection.state` stays in memory, used only for
|
|
638
|
+
// polling and confirmation below.
|
|
639
|
+
renderApprovalGuidance(deps, repoName, connection.approvalCheck, connection.redeemUrlTemplate, workspace, firstRequest);
|
|
640
|
+
}
|
|
641
|
+
else {
|
|
642
|
+
renderRedemptionGuidance(deps, connection.redeemUrlTemplate, workspace);
|
|
643
|
+
// The code itself is not a bearer credential the way the URL's state parameter is
|
|
644
|
+
// treated elsewhere in this flow — it must be visible so the user can paste it into
|
|
645
|
+
// the installed Forge app's settings page, which is how redemption happens in code
|
|
646
|
+
// mode (there is no GitHub-style browser redirect back to Bridge). This is the
|
|
647
|
+
// single statement in the command that prints a code, and it runs once per mint.
|
|
648
|
+
deps.stderr(`Paste this code there: ${connection.state}`);
|
|
649
|
+
}
|
|
591
650
|
renderStep(deps, 2);
|
|
592
651
|
const minutes = Math.floor(POLL_DEADLINE_MS / 60_000);
|
|
593
652
|
deps.stderr(`Waiting for the Bitbucket Forge app… (up to ~${minutes} minutes)`);
|
|
@@ -599,14 +658,23 @@ export async function runBitbucketConnectionFlow(deps, api, repoName, options =
|
|
|
599
658
|
if (renewable) {
|
|
600
659
|
expiryCount += 1;
|
|
601
660
|
deps.stderr("");
|
|
602
|
-
deps.stderr(
|
|
661
|
+
deps.stderr(approval
|
|
662
|
+
? "That request expired and can no longer be approved."
|
|
663
|
+
: "That code expired and can no longer be used.");
|
|
664
|
+
if (approval && !useCodeHintShown) {
|
|
665
|
+
deps.stderr(USE_CODE_FALLBACK_HINT);
|
|
666
|
+
useCodeHintShown = true;
|
|
667
|
+
}
|
|
603
668
|
if (expiryCount > 1) {
|
|
604
669
|
// Said only after it has happened more than once — by then the likely cause is
|
|
605
670
|
// not slowness but a wrong page or an app that was never installed.
|
|
606
671
|
deps.stderr("If this keeps happening, check that the Forge app is installed and that you " +
|
|
607
672
|
"have the right workspace's settings page open.");
|
|
608
673
|
}
|
|
609
|
-
|
|
674
|
+
const renewQuestion = approval
|
|
675
|
+
? "Create a fresh request and keep waiting? [y/N]: "
|
|
676
|
+
: "Issue a fresh code and keep waiting? [y/N]: ";
|
|
677
|
+
if (await confirm(deps, renewQuestion)) {
|
|
610
678
|
continue;
|
|
611
679
|
}
|
|
612
680
|
return reportNoConnection(deps, polled.ok ? OUTCOME_MESSAGES.expired : FAILURE_MESSAGES.deadline, repoName, "incomplete");
|
|
@@ -624,7 +692,8 @@ export async function runBitbucketConnectionFlow(deps, api, repoName, options =
|
|
|
624
692
|
return { kind: "connected", displayName };
|
|
625
693
|
}
|
|
626
694
|
if (result.status !== "staged") {
|
|
627
|
-
|
|
695
|
+
const outcomeKey = result.status === "invalid" && result.reason === "declined" ? "declined" : result.status;
|
|
696
|
+
return reportNoConnection(deps, OUTCOME_MESSAGES[outcomeKey] ?? OUTCOME_MESSAGES.invalid, repoName);
|
|
628
697
|
}
|
|
629
698
|
const candidates = result.candidates;
|
|
630
699
|
if (candidates.length === 0) {
|
|
@@ -729,8 +798,12 @@ export async function runConnectBitbucketCli(argv, injected) {
|
|
|
729
798
|
baseUrl: deps.env.BAPI_BASE_URL?.trim() || DEFAULT_BAPI_BASE_URL,
|
|
730
799
|
apiKey: cred.credentials.apiKey,
|
|
731
800
|
};
|
|
801
|
+
// BAPI-1401: explicit --workspace, else the origin remote. Inferred even with
|
|
802
|
+
// --use-code, where it still resolves the printed settings-page link.
|
|
803
|
+
const workspace = await resolveBitbucketWorkspace(parsed.value.workspace, deps.cwd, deps.runGit, deps.stderr);
|
|
732
804
|
const result = await runBitbucketConnectionFlow(deps, api, repo.value, {
|
|
733
|
-
workspace
|
|
805
|
+
workspace,
|
|
806
|
+
useCode: parsed.value.useCode,
|
|
734
807
|
// Standalone: nobody has asked yet, so the flow owns the initial confirmation.
|
|
735
808
|
initialMintApproved: false,
|
|
736
809
|
});
|