@bridge_gpt/mcp-server 0.2.33 → 0.2.36
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 +456 -340
- package/build/agent-capabilities/probe-context.js +8 -1
- package/build/agent-capabilities/probes.js +7 -1
- package/build/agents.generated.js +1 -1
- package/build/claude-review-workflow.js +264 -0
- package/build/cli-release.js +53 -0
- package/build/commands.generated.js +4 -4
- package/build/conductor/bridge-api-client.js +252 -3
- package/build/conductor/deny-enforcement-preflight.js +1 -0
- package/build/conductor/done-gate.js +44 -5
- package/build/conductor/epic-reconcile.js +6 -0
- package/build/conductor/install-doctor.js +462 -0
- package/build/conductor-bin.js +3 -3
- package/build/conductor-bundle-artifacts.js +30 -9
- package/build/doctor.js +234 -1
- package/build/executor/cli.js +32 -5
- package/build/executor/credentials.js +45 -11
- package/build/executor/deps.js +14 -0
- package/build/executor/env.js +23 -6
- package/build/executor/index.js +4 -0
- package/build/executor/job-runner.js +119 -9
- package/build/executor/permissions.js +12 -2
- package/build/executor/preflight.js +95 -8
- package/build/executor/prompt-spec.js +51 -0
- package/build/executor/runner.js +15 -2
- package/build/executor/service-unit.js +876 -0
- package/build/executor/test-clock.js +8 -0
- package/build/executor/types.js +0 -17
- package/build/executor/worker-command.js +62 -9
- package/build/index.js +575 -143
- package/build/init.js +153 -51
- package/build/install-bridge-conductor.js +491 -0
- package/build/install-bridge.js +884 -176
- package/build/install-reexec.js +233 -0
- package/build/mcp-host-config.js +11 -1
- package/build/mcp-install-state.js +32 -0
- package/build/mcp-provisioning.js +22 -6
- package/build/pipelines.generated.js +14 -8
- package/build/readme.generated.js +1 -1
- package/build/run-unit-tests-launcher.js +257 -0
- package/build/setup-epic.js +117 -8
- package/build/upgrade-cli.js +1 -15
- package/build/version.generated.js +1 -1
- package/docs/CONDUCTOR.md +115 -4
- package/docs/install/mcp-tool-integrations.md +29 -21
- package/package.json +9 -6
- package/pipelines/implement-ticket.json +6 -1
- package/build/conductor/supervisor-judgment-python.js +0 -141
- package/build/conductor/supervisor-judgment.js +0 -215
|
@@ -32,8 +32,15 @@ export function buildHeadlessArgs(agentName, opts) {
|
|
|
32
32
|
}
|
|
33
33
|
// claude (and any positional-prompt agent without a cwd flag)
|
|
34
34
|
const args = ["-p"];
|
|
35
|
-
|
|
35
|
+
// BAPI-725: exactly ONE permission form, matching `buildClaudeExecutorArgv`.
|
|
36
|
+
// `permissionMode` wins when both are supplied — emitting both would let the
|
|
37
|
+
// skip flag take effect and the probe would report on a posture it never ran.
|
|
38
|
+
if (opts.permissionMode === "acceptEdits") {
|
|
39
|
+
args.push("--permission-mode", "acceptEdits");
|
|
40
|
+
}
|
|
41
|
+
else if (opts.skipPermissions === true) {
|
|
36
42
|
args.push("--dangerously-skip-permissions");
|
|
43
|
+
}
|
|
37
44
|
if (typeof opts.model === "string" && opts.model.trim().length > 0) {
|
|
38
45
|
args.push("--model", opts.model);
|
|
39
46
|
}
|
|
@@ -419,7 +419,13 @@ function evaluateDenyRun(run, allowedMarker) {
|
|
|
419
419
|
export async function runDenyEnforcementCheck(ctx, opts) {
|
|
420
420
|
const marker = ctx.marker("DENY_ALLOWED");
|
|
421
421
|
const prompt = denyProbePrompt();
|
|
422
|
-
const
|
|
422
|
+
const acceptEdits = opts?.permissionPosture === "accept_edits";
|
|
423
|
+
const runOpts = {
|
|
424
|
+
skipPermissions: !acceptEdits,
|
|
425
|
+
permissionMode: acceptEdits ? "acceptEdits" : undefined,
|
|
426
|
+
model: opts?.model,
|
|
427
|
+
timeoutMs: opts?.timeoutMs,
|
|
428
|
+
};
|
|
423
429
|
// 0) CONTROL: same temp-project shape and prompt, NO deny rule. The marker MUST
|
|
424
430
|
// surface here — otherwise the agent never attempted the canary Bash call and
|
|
425
431
|
// a marker-free deny run would be a refusal, not enforcement (NEVER a pass).
|
|
@@ -8,7 +8,7 @@ export const AGENTS = {
|
|
|
8
8
|
"model": "opus",
|
|
9
9
|
"color": "blue"
|
|
10
10
|
},
|
|
11
|
-
"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## 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 is the most critical phase. You MUST spend significant time here before writing anything. Do NOT rush this phase.\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 key source files thoroughly — do not skim\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- Read at least 5-15 relevant source files in full, more if the problem is complex\n- Follow import chains to understand dependencies\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\nYou 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 `/write-ticket` Stage 0.5 pre-draft pass). 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- **When the map is missing or has `applicable: false`** (a backend-only request, a no-comp request, a non-design request, or a degraded/unreadable comp): **ignore the artifact entirely**. 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- **When the map has `applicable: true`**: 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### 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### 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- **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- 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 gate first.** Call the `config_field` MCP tool with `operation` set to `\"get\"` and `field_name` set to `enable_regression_checks`. If the tool returns an error, `null`, or any value other than the literal string `\"true\"`, **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 it is explicitly `\"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- Do NOT skip or abbreviate the research phase. The quality of the ticket depends entirely on the depth of your codebase understanding.\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"
|
|
11
|
+
"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## 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 `/write-ticket` Stage 0.5 pre-draft 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### 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### 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- **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 `/write-ticket` command's Stage 0, 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"
|
|
12
12
|
},
|
|
13
13
|
"refactor-reviewer": {
|
|
14
14
|
"frontmatter": {
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parameterized `claude-review` workflow scaffold (BAPI-679).
|
|
3
|
+
*
|
|
4
|
+
* The Bridge repo's own `.github/workflows/claude-review.yml` carries three
|
|
5
|
+
* ORGANIZATION-SPECIFIC literals — the allowed bot slug, the OAuth secret name,
|
|
6
|
+
* and the branch triggers — so it cannot be copied into a consumer repo as-is.
|
|
7
|
+
* This module renders a structurally equivalent workflow from validated
|
|
8
|
+
* parameters, with no org value embedded anywhere in the template source.
|
|
9
|
+
*
|
|
10
|
+
* The template is a plain TypeScript string constant rather than a generated
|
|
11
|
+
* asset: the package has no workflow-asset generator, and adding one to ship a
|
|
12
|
+
* single template would create a `*.generated.ts` that must never be hand-edited
|
|
13
|
+
* for no benefit. `tsc` ships this module in the published package already.
|
|
14
|
+
*
|
|
15
|
+
* INJECTION SAFETY is the module's core constraint. Every interpolated value is
|
|
16
|
+
* validated against a narrow allowlist BEFORE rendering, so no input can close a
|
|
17
|
+
* YAML scalar, introduce a key, or inject a `${{ }}` expression. Validation
|
|
18
|
+
* failures throw before any filesystem collaborator is touched.
|
|
19
|
+
*
|
|
20
|
+
* All filesystem access is injected so the module is unit-testable with
|
|
21
|
+
* in-memory fakes.
|
|
22
|
+
*/
|
|
23
|
+
/** Repo-relative path this module owns. It writes NOTHING else. */
|
|
24
|
+
export const CLAUDE_REVIEW_WORKFLOW_RELPATH = ".github/workflows/claude-review.yml";
|
|
25
|
+
/** Thrown when a parameter fails validation. Carries no rendered YAML. */
|
|
26
|
+
export class ClaudeReviewWorkflowValidationError extends Error {
|
|
27
|
+
constructor(message) {
|
|
28
|
+
super(message);
|
|
29
|
+
this.name = "ClaudeReviewWorkflowValidationError";
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
// A GitHub App bot login: alphanumerics and hyphens, optionally suffixed `[bot]`.
|
|
33
|
+
// Deliberately rejects whitespace, quotes, newlines, `$`, `{`, and `}`.
|
|
34
|
+
const ALLOWED_BOT_PATTERN = /^[A-Za-z0-9][A-Za-z0-9-]*(\[bot\])?$/;
|
|
35
|
+
// GitHub secret identifiers are uppercase alphanumerics and underscores, not
|
|
36
|
+
// starting with a digit and not using the reserved `GITHUB_` prefix.
|
|
37
|
+
const SECRET_NAME_PATTERN = /^[A-Z_][A-Z0-9_]*$/;
|
|
38
|
+
// Branch trigger patterns: git ref characters plus the `*` glob GitHub accepts.
|
|
39
|
+
// Rejects whitespace, quotes, `:`, `$`, `{`, `}`, and leading/trailing slashes.
|
|
40
|
+
const BRANCH_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._\-/*]*$/;
|
|
41
|
+
const MAX_BOT_LENGTH = 100;
|
|
42
|
+
const MAX_SECRET_NAME_LENGTH = 100;
|
|
43
|
+
const MAX_BRANCH_LENGTH = 200;
|
|
44
|
+
const MAX_BRANCHES = 20;
|
|
45
|
+
function validateAllowedBot(value) {
|
|
46
|
+
if (typeof value !== "string" || value.length === 0 || value.length > MAX_BOT_LENGTH) {
|
|
47
|
+
throw new ClaudeReviewWorkflowValidationError("allowedBot must be a non-empty slug");
|
|
48
|
+
}
|
|
49
|
+
if (!ALLOWED_BOT_PATTERN.test(value)) {
|
|
50
|
+
throw new ClaudeReviewWorkflowValidationError("allowedBot must match a GitHub bot login (alphanumerics/hyphens, optional `[bot]` suffix)");
|
|
51
|
+
}
|
|
52
|
+
return value;
|
|
53
|
+
}
|
|
54
|
+
function validateSecretName(value) {
|
|
55
|
+
if (typeof value !== "string" ||
|
|
56
|
+
value.length === 0 ||
|
|
57
|
+
value.length > MAX_SECRET_NAME_LENGTH) {
|
|
58
|
+
throw new ClaudeReviewWorkflowValidationError("secretName must be a non-empty identifier");
|
|
59
|
+
}
|
|
60
|
+
if (!SECRET_NAME_PATTERN.test(value)) {
|
|
61
|
+
throw new ClaudeReviewWorkflowValidationError("secretName must be an uppercase GitHub secret identifier (A-Z, 0-9, underscore)");
|
|
62
|
+
}
|
|
63
|
+
if (value.startsWith("GITHUB_")) {
|
|
64
|
+
throw new ClaudeReviewWorkflowValidationError("secretName must not use the reserved GITHUB_ prefix");
|
|
65
|
+
}
|
|
66
|
+
return value;
|
|
67
|
+
}
|
|
68
|
+
function validateBranches(value) {
|
|
69
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
70
|
+
throw new ClaudeReviewWorkflowValidationError("branches must be a non-empty array");
|
|
71
|
+
}
|
|
72
|
+
if (value.length > MAX_BRANCHES) {
|
|
73
|
+
throw new ClaudeReviewWorkflowValidationError("branches list is too long");
|
|
74
|
+
}
|
|
75
|
+
const seen = new Set();
|
|
76
|
+
const out = [];
|
|
77
|
+
for (const raw of value) {
|
|
78
|
+
if (typeof raw !== "string" || raw.length === 0 || raw.length > MAX_BRANCH_LENGTH) {
|
|
79
|
+
throw new ClaudeReviewWorkflowValidationError("each branch must be a non-empty string");
|
|
80
|
+
}
|
|
81
|
+
if (!BRANCH_PATTERN.test(raw)) {
|
|
82
|
+
throw new ClaudeReviewWorkflowValidationError("each branch must be a valid git ref pattern (no whitespace, quotes, or expressions)");
|
|
83
|
+
}
|
|
84
|
+
if (seen.has(raw)) {
|
|
85
|
+
throw new ClaudeReviewWorkflowValidationError(`duplicate branch: ${raw}`);
|
|
86
|
+
}
|
|
87
|
+
seen.add(raw);
|
|
88
|
+
out.push(raw);
|
|
89
|
+
}
|
|
90
|
+
return out;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Serialize a validated scalar as a single-quoted YAML string.
|
|
94
|
+
*
|
|
95
|
+
* Safe because the value already passed an allowlist that excludes `'`, so the
|
|
96
|
+
* escape below can never be reached with adversarial input — it is kept as
|
|
97
|
+
* defense in depth rather than as the primary control.
|
|
98
|
+
*/
|
|
99
|
+
function yamlSingleQuoted(value) {
|
|
100
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
101
|
+
}
|
|
102
|
+
/** Serialize a validated string list as an inline YAML flow sequence. */
|
|
103
|
+
function yamlFlowList(values) {
|
|
104
|
+
return `[${values.map(yamlSingleQuoted).join(", ")}]`;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Render the workflow YAML deterministically from validated parameters.
|
|
108
|
+
*
|
|
109
|
+
* Structurally mirrors the Bridge repo's own workflow — same triggers,
|
|
110
|
+
* concurrency, permissions, action, sticky comment, and the verdict/
|
|
111
|
+
* `Reviewed-SHA`/`run-id` contract the Conductor's merge gate consumes — with
|
|
112
|
+
* every org-specific literal replaced by a parameter.
|
|
113
|
+
*
|
|
114
|
+
* @throws ClaudeReviewWorkflowValidationError before producing any output.
|
|
115
|
+
*/
|
|
116
|
+
export function renderClaudeReviewWorkflow(params) {
|
|
117
|
+
const allowedBot = validateAllowedBot(params.allowedBot);
|
|
118
|
+
const secretName = validateSecretName(params.secretName);
|
|
119
|
+
const branches = validateBranches(params.branches);
|
|
120
|
+
// `${'$'}{{ ... }}` is avoided by building the expression marker from a constant,
|
|
121
|
+
// so nothing in this template can be mistaken for a TS template substitution.
|
|
122
|
+
const gh = (expr) => "${{ " + expr + " }}";
|
|
123
|
+
return `# Generated by \`install-bridge conductor\` (Bridge API, BAPI-679).
|
|
124
|
+
# Safe to edit: this file is never overwritten without explicit consent.
|
|
125
|
+
name: Claude Code Review
|
|
126
|
+
|
|
127
|
+
on:
|
|
128
|
+
pull_request:
|
|
129
|
+
types: [opened, reopened, synchronize, ready_for_review]
|
|
130
|
+
branches: ${yamlFlowList(branches)}
|
|
131
|
+
|
|
132
|
+
# A new push to the PR cancels an in-flight review of the stale head, matching the
|
|
133
|
+
# Reviewed-SHA provenance the review emits: the Conductor rejects a verdict whose
|
|
134
|
+
# Reviewed-SHA does not match the current head, so cancelling a superseded review
|
|
135
|
+
# only avoids wasted minutes.
|
|
136
|
+
concurrency:
|
|
137
|
+
group: claude-review-${gh("github.event.pull_request.number")}
|
|
138
|
+
cancel-in-progress: true
|
|
139
|
+
|
|
140
|
+
jobs:
|
|
141
|
+
claude-review:
|
|
142
|
+
runs-on: ubuntu-latest
|
|
143
|
+
if: ${gh("github.event.pull_request.draft == false")}
|
|
144
|
+
permissions:
|
|
145
|
+
actions: read
|
|
146
|
+
contents: read
|
|
147
|
+
pull-requests: write
|
|
148
|
+
issues: write
|
|
149
|
+
id-token: write
|
|
150
|
+
steps:
|
|
151
|
+
- uses: actions/checkout@v7
|
|
152
|
+
with:
|
|
153
|
+
fetch-depth: 1
|
|
154
|
+
|
|
155
|
+
- name: Claude Code Review
|
|
156
|
+
id: review
|
|
157
|
+
uses: anthropics/claude-code-action@v1
|
|
158
|
+
with:
|
|
159
|
+
claude_code_oauth_token: ${gh(`secrets.${secretName}`)}
|
|
160
|
+
allowed_bots: ${yamlSingleQuoted(allowedBot)}
|
|
161
|
+
use_sticky_comment: true
|
|
162
|
+
track_progress: true
|
|
163
|
+
prompt: |
|
|
164
|
+
REPO: ${gh("github.repository")}
|
|
165
|
+
PR NUMBER: ${gh("github.event.pull_request.number")}
|
|
166
|
+
|
|
167
|
+
Review this pull request with a focus on:
|
|
168
|
+
- Bugs, logic errors, and edge cases
|
|
169
|
+
- Security issues (injection, auth bypasses, sensitive data exposure)
|
|
170
|
+
- Project conventions and patterns (see CLAUDE.md)
|
|
171
|
+
- Data-access correctness (parameterized queries, migration safety)
|
|
172
|
+
- Test coverage gaps for changed code
|
|
173
|
+
|
|
174
|
+
Be specific: cite file paths and line numbers.
|
|
175
|
+
|
|
176
|
+
Call out significant issues; ignore minor nits with no meaningful impact.
|
|
177
|
+
If there are no significant issues, just say "Looks good to me!"
|
|
178
|
+
|
|
179
|
+
Use \`gh pr comment\` for top-level feedback.
|
|
180
|
+
Use \`mcp__github_inline_comment__create_inline_comment\` (with \`confirmed: true\`)
|
|
181
|
+
to highlight specific code issues.
|
|
182
|
+
Only post GitHub comments - don't submit review text as messages.
|
|
183
|
+
|
|
184
|
+
After your feedback, end your top-level \`gh pr comment\` with EXACTLY ONE
|
|
185
|
+
machine-readable verdict line, on its own line, verbatim — this single line is
|
|
186
|
+
the source of truth consumed by automation (the Conductor merge gate):
|
|
187
|
+
- \`claude-review-verdict: approved\` — when the PR is safe to merge
|
|
188
|
+
- \`claude-review-verdict: changes_requested\` — when significant issues remain
|
|
189
|
+
Emit exactly one such verdict line, and do not use the words \`approved\` or
|
|
190
|
+
\`changes_requested\` as a verdict anywhere else in the comment.
|
|
191
|
+
|
|
192
|
+
Immediately BELOW the verdict line, on its own line, emit this exact
|
|
193
|
+
head-SHA provenance line verbatim — it binds the verdict to the exact commit
|
|
194
|
+
you reviewed so the Conductor can reject a stale verdict:
|
|
195
|
+
\`Reviewed-SHA: ${gh("github.event.pull_request.head.sha")}\`
|
|
196
|
+
|
|
197
|
+
Also include the GitHub Actions run id on its own line so overlapping
|
|
198
|
+
reviews resolve deterministically by run id, verbatim:
|
|
199
|
+
\`run-id: ${gh("github.run_id")}\`
|
|
200
|
+
claude_args: |
|
|
201
|
+
--allowedTools "mcp__github_inline_comment__create_inline_comment,Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(git diff:*),Bash(git log:*),Bash(git show:*),Read,Glob,Grep"
|
|
202
|
+
`;
|
|
203
|
+
}
|
|
204
|
+
function joinCwd(cwd, rel) {
|
|
205
|
+
const base = cwd.endsWith("/") ? cwd.slice(0, -1) : cwd;
|
|
206
|
+
return `${base}/${rel}`;
|
|
207
|
+
}
|
|
208
|
+
/** Absolute path of the workflow file for a project. */
|
|
209
|
+
export function claudeReviewWorkflowPath(cwd) {
|
|
210
|
+
return joinCwd(cwd, CLAUDE_REVIEW_WORKFLOW_RELPATH);
|
|
211
|
+
}
|
|
212
|
+
function isEnoent(err) {
|
|
213
|
+
return (typeof err === "object" && err !== null && err.code === "ENOENT");
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Classify an existing workflow file against the rendered content.
|
|
217
|
+
*
|
|
218
|
+
* Returns a status ONLY — never the existing file's content, and never the read
|
|
219
|
+
* error's message (which can echo an absolute path or, for a misconfigured
|
|
220
|
+
* checkout, file content).
|
|
221
|
+
*/
|
|
222
|
+
export async function inspectClaudeReviewWorkflow(cwd, renderedContent, deps) {
|
|
223
|
+
let existing;
|
|
224
|
+
try {
|
|
225
|
+
existing = await deps.readFile(claudeReviewWorkflowPath(cwd));
|
|
226
|
+
}
|
|
227
|
+
catch (err) {
|
|
228
|
+
return isEnoent(err) ? { status: "absent" } : { status: "unreadable" };
|
|
229
|
+
}
|
|
230
|
+
return existing === renderedContent ? { status: "identical" } : { status: "different" };
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Write the workflow, read-compare-write, with SEPARATE overwrite consent.
|
|
234
|
+
*
|
|
235
|
+
* `overwriteConsent` must be an explicit decision made for THIS file. It is
|
|
236
|
+
* never inferred from the installer's earlier base-URL or bootstrap consent:
|
|
237
|
+
* this file governs review automation on every PR, so silently replacing an
|
|
238
|
+
* operator's version could disable review repo-wide.
|
|
239
|
+
*
|
|
240
|
+
* Writes exactly one path — {@link CLAUDE_REVIEW_WORKFLOW_RELPATH} — and nothing else.
|
|
241
|
+
*/
|
|
242
|
+
export async function writeClaudeReviewWorkflow(cwd, renderedContent, deps, options = {}) {
|
|
243
|
+
const finalPath = claudeReviewWorkflowPath(cwd);
|
|
244
|
+
const inspection = await inspectClaudeReviewWorkflow(cwd, renderedContent, deps);
|
|
245
|
+
if (inspection.status === "identical") {
|
|
246
|
+
return { ok: true, outcome: "unchanged", path: finalPath };
|
|
247
|
+
}
|
|
248
|
+
if (inspection.status === "unreadable") {
|
|
249
|
+
return { ok: false, error: "existing workflow file could not be read" };
|
|
250
|
+
}
|
|
251
|
+
if (inspection.status === "different" && options.overwriteConsent !== true) {
|
|
252
|
+
return { ok: true, outcome: "skipped", path: finalPath };
|
|
253
|
+
}
|
|
254
|
+
try {
|
|
255
|
+
await deps.mkdir(joinCwd(cwd, ".github/workflows"), { recursive: true });
|
|
256
|
+
await deps.writeFile(finalPath, renderedContent);
|
|
257
|
+
}
|
|
258
|
+
catch (err) {
|
|
259
|
+
// Message is the fs error's own text (path + errno), never file content.
|
|
260
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
261
|
+
return { ok: false, error: `failed to write workflow: ${message}` };
|
|
262
|
+
}
|
|
263
|
+
return { ok: true, outcome: "written", path: finalPath };
|
|
264
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared npm release resolution for the Bridge CLI (BAPI-714).
|
|
3
|
+
*
|
|
4
|
+
* ONE bounded, fail-open lookup of the latest published `@bridge_gpt/mcp-server`
|
|
5
|
+
* version, shared by `upgrade` (which already re-exec'd from `@latest`) and by
|
|
6
|
+
* the `install` path's new self-re-exec. Extracted into its own leaf module so
|
|
7
|
+
* `install-reexec.ts` does not have to import `upgrade-cli.ts` — that module
|
|
8
|
+
* pulls in `runInit`, the terminal registry, and the agent registry, none of
|
|
9
|
+
* which the install hand-off needs.
|
|
10
|
+
*
|
|
11
|
+
* The contract is deliberately narrow: return a nonblank version string ONLY for
|
|
12
|
+
* a successful, well-formed registry response, and resolve to `null` for every
|
|
13
|
+
* other outcome — non-2xx, timeout/abort, network failure, unparseable body,
|
|
14
|
+
* non-object JSON, missing/blank/non-string `version`. It never throws and never
|
|
15
|
+
* logs, because both callers treat "no usable version" as "keep using the local
|
|
16
|
+
* copy" rather than as an error worth reporting to the user.
|
|
17
|
+
*/
|
|
18
|
+
/** The npm registry endpoint carrying the latest published release metadata. */
|
|
19
|
+
export const NPM_LATEST_ENDPOINT = "https://registry.npmjs.org/@bridge_gpt/mcp-server/latest";
|
|
20
|
+
/**
|
|
21
|
+
* Hard bound on the registry lookup. Owned here rather than duplicated at each
|
|
22
|
+
* call site: `install` is a first-time user's very first contact with Bridge, so
|
|
23
|
+
* a hung registry may never stall it past this budget.
|
|
24
|
+
*/
|
|
25
|
+
export const NPM_LATEST_TIMEOUT_MS = 3000;
|
|
26
|
+
/**
|
|
27
|
+
* Resolve the latest published version of `@bridge_gpt/mcp-server`, or `null`.
|
|
28
|
+
*
|
|
29
|
+
* Fail-open by construction: every failure mode collapses to `null` so the
|
|
30
|
+
* caller proceeds with its local `VERSION`. Emits no output on any path.
|
|
31
|
+
*/
|
|
32
|
+
export async function fetchLatestVersion(deps = {}) {
|
|
33
|
+
const doFetch = deps.fetch ?? globalThis.fetch;
|
|
34
|
+
const timeoutSignal = deps.timeoutSignal ?? ((ms) => AbortSignal.timeout(ms));
|
|
35
|
+
try {
|
|
36
|
+
const res = await doFetch(NPM_LATEST_ENDPOINT, {
|
|
37
|
+
signal: timeoutSignal(NPM_LATEST_TIMEOUT_MS),
|
|
38
|
+
});
|
|
39
|
+
if (!res || !res.ok)
|
|
40
|
+
return null;
|
|
41
|
+
const data = await res.json();
|
|
42
|
+
if (!data || typeof data !== "object" || Array.isArray(data))
|
|
43
|
+
return null;
|
|
44
|
+
const version = data.version;
|
|
45
|
+
if (typeof version !== "string")
|
|
46
|
+
return null;
|
|
47
|
+
const trimmed = version.trim();
|
|
48
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|