@bridge_gpt/mcp-server 0.2.42 → 0.2.43

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.
@@ -20,6 +20,20 @@ import { readdirSync } from "fs";
20
20
  import { spawnSync } from "child_process";
21
21
  import path from "path";
22
22
  const DEFAULT_BUILD_DIR = "build";
23
+ // BAPI-877 — the SECOND directory the unit lane discovers. `mcp_server/scripts/`
24
+ // holds hand-authored ESM (`"type": "module"`) that the build itself runs:
25
+ // bundle-version.js, sync-agent-mirrors.js, and friends. They are NOT tsc inputs
26
+ // (tsconfig.json is rootDir "src" / include "src/**/*.ts"), so they compile to
27
+ // nothing and a `*.test.js` beside them has no build/ counterpart to discover.
28
+ // Before this constant existed, such a file ran in NO lane at all: not `npm
29
+ // test` (which walked build/ only), not the integration lane, not the pre-push
30
+ // gate. sync-agent-mirrors.test.js sat unrun for months that way.
31
+ //
32
+ // Discovered from source rather than from build/ for that reason, and unioned
33
+ // into the "normal" group — a `scripts/…` path can never match the
34
+ // build/integration/ prefix, so it needs no filter change to land there.
35
+ const SCRIPT_TEST_DIR = "scripts";
36
+ export const SCRIPTS_DIR_PREFIX = "scripts/";
23
37
  // The Windows CreateProcess command-line limit is ~8191 characters. This
24
38
  // budget stays well under it (accounting for execPath + node options +
25
39
  // separators) so a single batch can never approach the real ceiling even on a
@@ -28,9 +42,9 @@ const DEFAULT_BUILD_DIR = "build";
28
42
  export const MAX_COMMAND_LENGTH = 6000;
29
43
  // Explicit manifest: only tests that call node:test's `mock.module()` API
30
44
  // need --experimental-test-module-mocks. Everything else discovered under
31
- // build/ is eligible for the "normal" group automatically — new tests do NOT
32
- // need to be registered anywhere else. Add an entry here only when a new test
33
- // needs module mocking.
45
+ // build/ OR scripts/ is eligible for the "normal" group automatically — new
46
+ // tests do NOT need to be registered anywhere else. Add an entry here only when
47
+ // a new test needs module mocking.
34
48
  export const MODULE_MOCK_MANIFEST = [
35
49
  "build/attachment-download.test.js",
36
50
  "build/attachment-upload.test.js",
@@ -40,6 +54,8 @@ export const MODULE_MOCK_MANIFEST = [
40
54
  "build/conductor/git-inspection.test.js",
41
55
  "build/conductor/paths.test.js",
42
56
  "build/conductor/pr-ci-producer-emit-seam.test.js",
57
+ "build/conductor/recovery-cli.test.js",
58
+ "build/conductor/recovery-operations.test.js",
43
59
  "build/conductor/security-regressions.test.js",
44
60
  "build/conductor/store-lifecycle.test.js",
45
61
  "build/conductor/store-queries.test.js",
@@ -57,6 +73,7 @@ export const MODULE_MOCK_MANIFEST = [
57
73
  "build/index-heavy-read-truncation.test.js",
58
74
  "build/index-output-path.test.js",
59
75
  "build/index.review-rounds.test.js",
76
+ "build/plane/cli.test.js",
60
77
  "build/recovery-formatting.test.js",
61
78
  "build/sfcc/client.test.js",
62
79
  "build/sfcc/permissions.test.js",
@@ -128,6 +145,10 @@ export const INTEGRATION_DISCOVERY_EXCLUSIONS = [
128
145
  // discovered into the unit lane automatically and needs no registration —
129
146
  // tests/pytest/mcp/test_mcp_server_test_registration_multicomponent.py fails if
130
147
  // a *.integration.test.ts ends up in neither lane.
148
+ //
149
+ // BAPI-877 — a `scripts/…` path must NEVER be listed here. This is not a style
150
+ // rule: `discoverIntegrationTestFiles` walks build/ only, so the entry would fail
151
+ // its own presence check below and throw on EVERY `npm run test:integration` run.
131
152
  export const INTEGRATION_LANE_ONLY = [
132
153
  "build/conductor/conductor-runtime.integration.test.js",
133
154
  ];
@@ -160,6 +181,55 @@ export function discoverTestFiles(options) {
160
181
  results.sort();
161
182
  return results;
162
183
  }
184
+ /**
185
+ * BAPI-877 — hand-authored `scripts/*.test.js`, sorted and package-relative.
186
+ *
187
+ * A thin delegation rather than a second walker: `buildDir` already generalizes
188
+ * (the walk is directory-agnostic and `path.relative(root, …)` yields
189
+ * `scripts/x.test.js` for free), and duplicating the ENOENT swallow, the sort,
190
+ * and the separator normalization would be three chances to diverge.
191
+ *
192
+ * Deliberately does NOT apply the empty-discovery guard — `discoverUnitTestFiles`
193
+ * owns that, so this stays a pure query usable in isolation.
194
+ */
195
+ export function discoverScriptTestFiles(options) {
196
+ return discoverTestFiles({ ...options, buildDir: SCRIPT_TEST_DIR });
197
+ }
198
+ /**
199
+ * BAPI-877 — everything the unit lane runs: compiled `build/**` plus authored
200
+ * `scripts/**`.
201
+ *
202
+ * WHY THE EMPTY-SCRIPTS GUARD IS SEPARATE. `selectTestGroup`'s "selected group
203
+ * is empty" refusal cannot fire for this: ~390 compiled files are always
204
+ * present, so a scripts-discovery regression would be masked and the lane would
205
+ * report green over zero scripts tests — the exact "coverage that exists only if
206
+ * someone runs it by hand" failure BAPI-877 exists to remove. Refusing here
207
+ * matches `runIntegrationLane` and `discoverIntegrationTestFiles`, which take the
208
+ * same posture for the same reason.
209
+ *
210
+ * The cost is deliberate: deleting every `scripts/*.test.js` breaks `npm test`
211
+ * until someone edits this file. Deleting all coverage for the build's version
212
+ * generator and agent-mirror synchronizer SHOULD require touching the line that
213
+ * says so, rather than silently shrinking the suite.
214
+ *
215
+ * WHY IT LIVES HERE and not in `runSelectedGroup`: the guard belongs on the
216
+ * production path only. Every test that injects its own `discoverFn` supplies a
217
+ * build-only fixture tree, and firing there would force `scripts/*` entries into
218
+ * BASELINE_NORMAL_FILES — falsifying the independent historical oracle this
219
+ * suite is built on.
220
+ */
221
+ export function discoverUnitTestFiles(options) {
222
+ const compiled = discoverTestFiles(options);
223
+ const authored = discoverScriptTestFiles(options);
224
+ if (authored.length === 0) {
225
+ throw new Error(`run-unit-tests: discovered zero ${SCRIPTS_DIR_PREFIX}*.test.js under ` +
226
+ `"${options.root}" — refusing to run. A scripts-lane discovery regression is ` +
227
+ `invisible beside the compiled tests that still ran, which is the failure ` +
228
+ `this lane exists to prevent (BAPI-877). If ${SCRIPTS_DIR_PREFIX} genuinely ` +
229
+ `has no test file any more, remove this guard deliberately.`);
230
+ }
231
+ return [...compiled, ...authored].sort();
232
+ }
163
233
  /** Throws if the module-mock, force-exit-quarantine, or integration-lane manifest has drifted from the discovered build output. */
164
234
  export function validateManifest(discovered) {
165
235
  const discoveredSet = new Set(discovered);
@@ -264,8 +334,8 @@ export function runSelectedGroup(mode, options) {
264
334
  if (mode !== "normal" && mode !== "module-mocks") {
265
335
  throw new Error(`run-unit-tests: unknown group "${mode}" (expected "normal" or "module-mocks")`);
266
336
  }
267
- const { root, execPath = process.execPath, spawnFn = spawnSync, discoverFn = (opts) => discoverTestFiles(opts), log = console.log, } = options;
268
- const discovered = discoverFn({ root });
337
+ const { root, execPath = process.execPath, spawnFn = spawnSync, discoverFn = (opts) => discoverUnitTestFiles(opts), fsImpl, log = console.log, } = options;
338
+ const discovered = discoverFn({ root, fsImpl });
269
339
  const selected = selectTestGroup(mode, discovered);
270
340
  // Split out the force-exit quarantine: those files run last, each as its own
271
341
  // single-file spawn WITH --test-force-exit (exact tally, prompt exit despite
@@ -55,8 +55,14 @@
55
55
  * A run with no feature branch is byte-identical to before: no parse read, no
56
56
  * `git`, no scope, no polling, no extra JSON keys.
57
57
  *
58
- * Read-only against the local filesystem: it reads the plan sidecar and the
59
- * optional policy file, and writes nothing.
58
+ * Reads the plan sidecar and the optional policy file from the local
59
+ * filesystem. As of BAPI-872 it can also WRITE the local filesystem in one
60
+ * narrow case: after a successful create/reuse, if a live `plane up` manifest
61
+ * exists for this exact repository root, setup-epic binds the resolved
62
+ * `epic_run_id` onto it (`.bridge/plane/plane.json`) so a later `plane down`
63
+ * can stop that run automatically instead of guessing from repository-wide
64
+ * active-run state. Every other workflow (remote, no local plane) is
65
+ * unaffected and performs no such write.
60
66
  */
61
67
  import { readFile as fsReadFile, stat as fsStat } from "node:fs/promises";
62
68
  import os from "node:os";
@@ -75,6 +81,11 @@ import { getConfigFieldBaseBranch, getIndexScopeLifecycle, getParseStatus, } fro
75
81
  import { createExecFileRunCommand, ensureCommitResolvableLocally, normalizeCommitSha, performExactIndexScopeCut, pollIndexScopeLifecycle, readRemoteBranchHead, runGit, SCOPE_LIFECYCLE_LABELS, } from "./conduct-epic/cut-protocol.js";
76
82
  import { hashPlan } from "./conductor/plan.js";
77
83
  import { MCP_PACKAGE_NAME } from "./mcp-identity.js";
84
+ // BAPI-872: local plane-manifest binding, so `plane down` can later stop the
85
+ // run this setup resolved WITHOUT guessing from repository-wide active-run
86
+ // state. See `planeBinding` on `SetupEpicDeps` below.
87
+ import { createPlaneFsDeps, createPlaneProcessDeps } from "./plane/defaults.js";
88
+ import { bindPlaneManifestEpicRun, manifestHasLiveProcess, readPlaneManifest, } from "./plane/manifest.js";
78
89
  /** Accepted `policy_json.review_policy.source` values (the `ReviewPolicy` surface). */
79
90
  export const SETUP_EPIC_REVIEW_POLICY_SOURCES = [
80
91
  "verdict_protocol",
@@ -114,6 +125,11 @@ export function createDefaultSetupEpicDeps() {
114
125
  isTTY: Boolean(process.stdin.isTTY),
115
126
  promptLine: defaultPromptLine,
116
127
  runCommand: createExecFileRunCommand(),
128
+ planeBinding: {
129
+ readManifest: (repoRoot) => readPlaneManifest(repoRoot, createPlaneFsDeps()),
130
+ isPlaneProcessAlive: (pid) => createPlaneProcessDeps().isAlive(pid),
131
+ bind: (repoRoot, planeId, epicRunId) => bindPlaneManifestEpicRun(repoRoot, planeId, epicRunId, createPlaneFsDeps()),
132
+ },
117
133
  };
118
134
  }
119
135
  /** User-facing usage text. */
@@ -159,6 +175,12 @@ export function getSetupEpicUsage() {
159
175
  " post-create PATCH is needed. Contradicts nothing",
160
176
  " silently: if the file and --feature-branch or",
161
177
  " --review-policy disagree, setup-epic errors naming both.",
178
+ " policy_json.job_timeouts values must be below the job",
179
+ " type's gate ceiling (implement / spec_review / resume",
180
+ " 10799; remediate / ci_fix 3599; rebase / smoke 2699;",
181
+ " merge 1799); the server rejects a higher value with a",
182
+ " 422 naming the offending field. Full table:",
183
+ " docs/claude/epic-conductor-v2-operator-runbook.md §4.",
162
184
  " --replace-policy Authorize replacing a LIVE run's stored policy with the",
163
185
  " policy file, as a complete replacement. Without it, a",
164
186
  " divergent policy on a reused run is refused with a",
@@ -1016,12 +1038,19 @@ async function resolveScopeIdForRun(access, fetchImpl, epicRunId) {
1016
1038
  const match = listing.value.scopes.find((scope) => scope.epic_run_id === epicRunId);
1017
1039
  return match?.scope_id ?? null;
1018
1040
  }
1019
- /** Operator-facing guidance for a bounded scope failure category. */
1020
- function scopeFailureGuidance(reason, featureBranch) {
1041
+ /**
1042
+ * Operator-facing guidance for a bounded scope failure category.
1043
+ *
1044
+ * BAPI-872: abandonment guidance names the conductor CLI verb — never a raw
1045
+ * `PATCH` recipe an operator would have to hand-roll. `epicRunId` is the
1046
+ * concrete, already-resolved run ID at this call site (the failure is scoped to
1047
+ * ONE run), so it is always named explicitly rather than left as a placeholder.
1048
+ */
1049
+ function scopeFailureGuidance(reason, featureBranch, epicRunId) {
1021
1050
  if (reason === "canonical_index_advanced") {
1022
1051
  return (`The canonical index advanced before the scope could be seeded. The cut must be ` +
1023
1052
  `re-driven at the newer commit: delete origin/${featureBranch}, abandon this run ` +
1024
- `(PATCH /jira/epic-runs/runs/<epic_run_id> {"status": "abandoned"}), and re-run setup-epic.`);
1053
+ `(conductor abandon-run --epic-run-id ${epicRunId}), and re-run setup-epic.`);
1025
1054
  }
1026
1055
  return `Recovery: ${SETUP_EPIC_SCOPE_RECOVERY_COMMAND}.`;
1027
1056
  }
@@ -1246,7 +1275,7 @@ export async function runSetupEpicCli(argv, overrides = {}) {
1246
1275
  else if (err instanceof ConductorBridgeApiError && err.status === 409) {
1247
1276
  deps.errorLog(`Epic ${opts.epicKey} has MULTIPLE active runs — it is wedged, and every ` +
1248
1277
  `plan call will keep failing. Abandon the duplicate before retrying:\n` +
1249
- ` PATCH /jira/epic-runs/runs/<epic_run_id> {"status": "abandoned"}\n` +
1278
+ ` conductor abandon-run --epic-run-id <duplicate_epic_run_id>\n` +
1250
1279
  `Detail: ${errorDetail(err)}`);
1251
1280
  return 1;
1252
1281
  }
@@ -1332,7 +1361,7 @@ export async function runSetupEpicCli(argv, overrides = {}) {
1332
1361
  `validated because the epic itself is wedged, and every plan call will ` +
1333
1362
  `keep failing. This is NOT a problem with your plan. Abandon the ` +
1334
1363
  `duplicate before retrying:\n` +
1335
- ` PATCH /jira/epic-runs/runs/<epic_run_id> {"status": "abandoned"}\n` +
1364
+ ` conductor abandon-run --epic-run-id <duplicate_epic_run_id>\n` +
1336
1365
  `No run was created and no automation-start charge occurred.\n` +
1337
1366
  `Detail: ${errorDetail(err)}`);
1338
1367
  return 1;
@@ -1617,6 +1646,51 @@ export async function runSetupEpicCli(argv, overrides = {}) {
1617
1646
  return 1;
1618
1647
  }
1619
1648
  }
1649
+ // --- Plane-manifest binding (BAPI-872) ------------------------------------
1650
+ // A live `plane up` for THIS exact repository root can now be told which run
1651
+ // it is driving, so a later `plane down` can stop it automatically instead
1652
+ // of guessing from repository-wide active-run state (which risks stopping
1653
+ // the WRONG run). Absence of a local plane — remote runs, CI, any non-plane
1654
+ // workflow — is normal and produces neither an attempt nor a warning.
1655
+ if (deps.planeBinding) {
1656
+ try {
1657
+ const manifestRead = await deps.planeBinding.readManifest(deps.cwd);
1658
+ if (manifestRead.kind === "valid") {
1659
+ const manifest = manifestRead.manifest;
1660
+ const isLive = manifestHasLiveProcess(manifest, {
1661
+ isAlive: deps.planeBinding.isPlaneProcessAlive,
1662
+ });
1663
+ if (isLive) {
1664
+ const bound = await deps.planeBinding.bind(deps.cwd, manifest.planeId, result.epic_run_id);
1665
+ if (bound.ok) {
1666
+ say(bound.alreadyBound
1667
+ ? `Plane: already bound to run ${result.epic_run_id}`
1668
+ : `Plane: bound to run ${result.epic_run_id} — \`plane down\` will stop it automatically`);
1669
+ }
1670
+ else {
1671
+ const msg = `Could not bind the local plane to run ${result.epic_run_id} (${bound.message}). ` +
1672
+ "A later `plane down` cannot be guaranteed to stop it automatically — if you need " +
1673
+ `to stop this run, run \`conductor stop-run --epic-run-id ${result.epic_run_id}\`.`;
1674
+ warnings.push(msg);
1675
+ say(`Plane: [warn] ${msg}`);
1676
+ }
1677
+ }
1678
+ // A recorded-but-not-live manifest is normal: `plane up` will revalidate
1679
+ // and replace a stale record on its own, and there is nothing running to
1680
+ // hand this binding to yet.
1681
+ }
1682
+ // A missing or unvalidated manifest is normal — no attempt, no warning.
1683
+ }
1684
+ catch (err) {
1685
+ // The binding capability itself must never fail setup-epic as a whole;
1686
+ // an unexpected error here is reported as a warning, never thrown.
1687
+ const msg = `Could not check for a local plane to bind run ${result.epic_run_id} to (${errorDetail(err)}). ` +
1688
+ "A later `plane down` cannot be guaranteed to stop it automatically — if you need to stop " +
1689
+ `this run, run \`conductor stop-run --epic-run-id ${result.epic_run_id}\`.`;
1690
+ warnings.push(msg);
1691
+ say(`Plane: [warn] ${msg}`);
1692
+ }
1693
+ }
1620
1694
  // --- The exact cut (BAPI-850) --------------------------------------------
1621
1695
  // AFTER the run exists and BEFORE the plan is stored/approved: the scope's run
1622
1696
  // association is immutable server-side, so the cut is driven with this run's
@@ -1806,7 +1880,7 @@ export async function runSetupEpicCli(argv, overrides = {}) {
1806
1880
  deps.errorLog(`Index scope ${scopeId} FAILED (reason: ${verdict.reason}) — this is a recorded ` +
1807
1881
  `failure, not an in-progress state. Ticket dispatch remains blocked: the reconciler ` +
1808
1882
  `will not dispatch against a failed scope.`);
1809
- deps.errorLog(scopeFailureGuidance(verdict.reason, effectiveFeatureBranch ?? ""));
1883
+ deps.errorLog(scopeFailureGuidance(verdict.reason, effectiveFeatureBranch ?? "", result.epic_run_id));
1810
1884
  exitCode = 1;
1811
1885
  }
1812
1886
  else {
@@ -1,2 +1,3 @@
1
1
  // AUTO-GENERATED — do not edit manually. Regenerate with: npm run build
2
- export const VERSION = "0.2.42";
2
+ export const VERSION = "0.2.43";
3
+ export const BUILD_COMMIT = "1840a67a5a0d-dirty";
@@ -97,21 +97,32 @@ function pickWorktreePathField(parsed) {
97
97
  return undefined;
98
98
  }
99
99
  /**
100
- * F7: decide whether a PRE-EXISTING branch is safe to reuse as a conductor
101
- * worktree base. A branch whose tip is an ancestor of the resolved base carries
102
- * no commits beyond base (nothing stale to build on) and is safe. A branch with
103
- * commits not on base is a leftover from a prior run — refuse it. Prefers the
104
- * authoritative `origin/<base>` ref when present. Conservative: any inability to
105
- * prove ancestry refuses (when in doubt, refuse).
100
+ * The remediation sentence for a branch left over by a prior FAILED run.
101
+ * Unchanged since the guard was introduced.
102
+ */
103
+ function staleLeftoverRemedy(branch, baseRef) {
104
+ return (`it carries commits not on the resolved base (likely a leftover from a prior run). ` +
105
+ `Refusing to reuse a stale worktree — delete it (git worktree remove + git branch -D ${branch}) ` +
106
+ `or rebase it onto ${baseRef}, then re-dispatch.`);
107
+ }
108
+ /**
109
+ * BAPI-862: the remediation for a branch that carries a SUCCEEDED implement whose
110
+ * pull request was never attached.
106
111
  *
107
- * `baseStartPoint` (BAPI-527) is the SAME effective base new worktrees are cut
108
- * from: a branch name for interactive dispatch, or the fetched immutable commit
109
- * SHA for non-mutating conductor dispatch. When it is a SHA the `origin/<sha>`
110
- * probe below naturally fails to resolve, so the ancestry check falls back to
111
- * comparing the existing branch directly against that fetched SHA never
112
- * against the local `main` branch name.
112
+ * Names the `pr_not_attached` gate observation so the executor refusal and the
113
+ * reconciler's own state are one searchable vocabulary, points at attachment, and
114
+ * states plainly that the two remedies above destroy completed work. It must not
115
+ * contain the words "delete" or "rebase" at all: an operator scanning a wall of
116
+ * job output for the next command is exactly who lost work last time.
113
117
  */
114
- export async function isExistingBranchSafeToReuse(deps, branch, baseStartPoint) {
118
+ function unattachedSuccessRemedy(branch) {
119
+ return (`it carries the commits of a SUCCEEDED implement whose pull request was never attached ` +
120
+ `(implement gate observation: pr_not_attached). Refusing to reuse the worktree — but this ` +
121
+ `branch is finished work, not a leftover. Open or attach a pull request from '${branch}' and ` +
122
+ `let the reconciler bind it. Do NOT remove or re-write the branch: either would destroy a ` +
123
+ `completed implementation.`);
124
+ }
125
+ export async function isExistingBranchSafeToReuse(deps, branch, baseStartPoint, classification = "unknown") {
115
126
  let baseRef = baseStartPoint;
116
127
  const originRef = `origin/${baseStartPoint}`;
117
128
  const originExists = await deps.runCommand("git", ["rev-parse", "--verify", "--quiet", originRef], { cwd: deps.cwd });
@@ -122,11 +133,14 @@ export async function isExistingBranchSafeToReuse(deps, branch, baseStartPoint)
122
133
  const ancestor = await deps.runCommand("git", ["merge-base", "--is-ancestor", branch, baseRef], { cwd: deps.cwd });
123
134
  if (commandSucceeded(ancestor))
124
135
  return { safe: true };
136
+ // The REFUSAL is identical either way (BAPI-862 non-goal: refusal behavior is
137
+ // unchanged). Only the diagnosis and the remedy differ.
138
+ const remedy = classification === "succeeded_pr_not_attached"
139
+ ? unattachedSuccessRemedy(branch)
140
+ : staleLeftoverRemedy(branch, baseRef);
125
141
  return {
126
142
  safe: false,
127
- reason: `existing branch '${branch}' is not an ancestor of ${baseRef}; it carries commits not on the ` +
128
- `resolved base (likely a leftover from a prior run). Refusing to reuse a stale worktree — delete it ` +
129
- `(git worktree remove + git branch -D ${branch}) or rebase it onto ${baseRef}, then re-dispatch.`,
143
+ reason: `existing branch '${branch}' is not an ancestor of ${baseRef}; ${remedy}`,
130
144
  };
131
145
  }
132
146
  /**
@@ -263,7 +277,7 @@ export async function createWorktreeForTicket(deps, key, branchOverrides, worktr
263
277
  // check compares against the SAME effective base start point (branch name or
264
278
  // fetched SHA) that a NEW branch would be cut from below.
265
279
  if (exists && guardStaleWorktree) {
266
- const safety = await isExistingBranchSafeToReuse(deps, branch, baseStartPoint);
280
+ const safety = await isExistingBranchSafeToReuse(deps, branch, baseStartPoint, behavior.staleBranchClassification ?? "unknown");
267
281
  if (!safety.safe) {
268
282
  return {
269
283
  key,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bridge_gpt/mcp-server",
3
- "version": "0.2.42",
3
+ "version": "0.2.43",
4
4
  "description": "Bridge API MCP server — exposes Jira endpoints as MCP tools for Claude Code agents",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -0,0 +1,286 @@
1
+ {
2
+ "name": "greenfield-setup",
3
+ "description": "Onboard a brand-new project that has no code to learn from yet. Offers a bounded interview (or generic defaults), ratifies the open non-functional requirements on a decision page, settles version control, convenes a general council, synthesizes one coherent set of project standards behind a single approval, writes the unset standards fields, and closes with the one authoritative install-manifest apply plus the read-after-write manifest /install-bridge resumes from.",
4
+ "variables": [
5
+ "docs_dir",
6
+ "repo_name"
7
+ ],
8
+ "steps": [
9
+ {
10
+ "type": "mcp_call",
11
+ "tool": "ping",
12
+ "params": {},
13
+ "description": "Verify Bridge API connectivity"
14
+ },
15
+ {
16
+ "type": "agent_task",
17
+ "instruction": "Create the required output directory by running: mkdir -p {docs_dir}/greenfield/{repo_name}/standards",
18
+ "description": "Create the greenfield working directory"
19
+ },
20
+ {
21
+ "type": "mcp_call",
22
+ "id": "read_install_manifest",
23
+ "tool": "get_install_manifest",
24
+ "params": {},
25
+ "description": "Read the install manifest once and keep its snapshot token for the final apply"
26
+ },
27
+ {
28
+ "type": "mcp_call",
29
+ "id": "fetch_architecture_instructions",
30
+ "tool": "config_field",
31
+ "params": {
32
+ "field_name": "architecture_instructions",
33
+ "operation": "get"
34
+ },
35
+ "description": "Check whether architecture_instructions already has content",
36
+ "on_error": "warn_and_continue"
37
+ },
38
+ {
39
+ "type": "mcp_call",
40
+ "id": "fetch_review_instructions",
41
+ "tool": "config_field",
42
+ "params": {
43
+ "field_name": "review_instructions",
44
+ "operation": "get"
45
+ },
46
+ "description": "Check whether review_instructions already has content",
47
+ "on_error": "warn_and_continue"
48
+ },
49
+ {
50
+ "type": "mcp_call",
51
+ "id": "fetch_documentation_instructions",
52
+ "tool": "config_field",
53
+ "params": {
54
+ "field_name": "documentation_instructions",
55
+ "operation": "get"
56
+ },
57
+ "description": "Check whether documentation_instructions already has content",
58
+ "on_error": "warn_and_continue"
59
+ },
60
+ {
61
+ "type": "mcp_call",
62
+ "id": "fetch_unit_testing_instructions",
63
+ "tool": "config_field",
64
+ "params": {
65
+ "field_name": "unit_testing_instructions",
66
+ "operation": "get"
67
+ },
68
+ "description": "Check whether unit_testing_instructions already has content",
69
+ "on_error": "warn_and_continue"
70
+ },
71
+ {
72
+ "type": "mcp_call",
73
+ "id": "fetch_e2e_testing_instructions",
74
+ "tool": "config_field",
75
+ "params": {
76
+ "field_name": "e2e_testing_instructions",
77
+ "operation": "get"
78
+ },
79
+ "description": "Check whether e2e_testing_instructions already has content",
80
+ "on_error": "warn_and_continue"
81
+ },
82
+ {
83
+ "type": "mcp_call",
84
+ "id": "fetch_frontend_correctness_standards",
85
+ "tool": "config_field",
86
+ "params": {
87
+ "field_name": "frontend_correctness_standards",
88
+ "operation": "get"
89
+ },
90
+ "description": "Check whether frontend_correctness_standards already has content",
91
+ "on_error": "warn_and_continue"
92
+ },
93
+ {
94
+ "type": "mcp_call",
95
+ "id": "fetch_backend_correctness_standards",
96
+ "tool": "config_field",
97
+ "params": {
98
+ "field_name": "backend_correctness_standards",
99
+ "operation": "get"
100
+ },
101
+ "description": "Check whether backend_correctness_standards already has content",
102
+ "on_error": "warn_and_continue"
103
+ },
104
+ {
105
+ "type": "mcp_call",
106
+ "id": "fetch_template_correctness_standards",
107
+ "tool": "config_field",
108
+ "params": {
109
+ "field_name": "template_correctness_standards",
110
+ "operation": "get"
111
+ },
112
+ "description": "Check whether template_correctness_standards already has content",
113
+ "on_error": "warn_and_continue"
114
+ },
115
+ {
116
+ "type": "mcp_call",
117
+ "id": "fetch_style_correctness_standards",
118
+ "tool": "config_field",
119
+ "params": {
120
+ "field_name": "style_correctness_standards",
121
+ "operation": "get"
122
+ },
123
+ "description": "Check whether style_correctness_standards already has content",
124
+ "on_error": "warn_and_continue"
125
+ },
126
+ {
127
+ "type": "mcp_call",
128
+ "id": "fetch_design_principles",
129
+ "tool": "config_field",
130
+ "params": {
131
+ "field_name": "design_principles",
132
+ "operation": "get"
133
+ },
134
+ "description": "Check whether design_principles already has content",
135
+ "on_error": "warn_and_continue"
136
+ },
137
+ {
138
+ "type": "agent_task",
139
+ "id": "greenfield_interview",
140
+ "instruction_file": "greenfield-interview.md",
141
+ "description": "Offer the interview, settle the project framing and version control, and commit the decisions"
142
+ },
143
+ {
144
+ "type": "agent_task",
145
+ "id": "synthesize_standards",
146
+ "instruction_file": "greenfield-synthesize-standards.md",
147
+ "description": "Draft the project standards from the committed framing and take one approval"
148
+ },
149
+ {
150
+ "type": "mcp_call",
151
+ "id": "upload_architecture_instructions",
152
+ "tool": "config_field",
153
+ "params": {
154
+ "field_name": "architecture_instructions",
155
+ "file_path": "{docs_dir}/greenfield/{repo_name}/standards/architecture_instructions.md",
156
+ "operation": "update",
157
+ "only_if_null": true
158
+ },
159
+ "description": "Save the architecture standard",
160
+ "on_error": "warn_and_continue"
161
+ },
162
+ {
163
+ "type": "mcp_call",
164
+ "id": "upload_review_instructions",
165
+ "tool": "config_field",
166
+ "params": {
167
+ "field_name": "review_instructions",
168
+ "file_path": "{docs_dir}/greenfield/{repo_name}/standards/review_instructions.md",
169
+ "operation": "update",
170
+ "only_if_null": true
171
+ },
172
+ "description": "Save the code-review standard",
173
+ "on_error": "warn_and_continue"
174
+ },
175
+ {
176
+ "type": "mcp_call",
177
+ "id": "upload_documentation_instructions",
178
+ "tool": "config_field",
179
+ "params": {
180
+ "field_name": "documentation_instructions",
181
+ "file_path": "{docs_dir}/greenfield/{repo_name}/standards/documentation_instructions.md",
182
+ "operation": "update",
183
+ "only_if_null": true
184
+ },
185
+ "description": "Save the documentation standard",
186
+ "on_error": "warn_and_continue"
187
+ },
188
+ {
189
+ "type": "mcp_call",
190
+ "id": "upload_unit_testing_instructions",
191
+ "tool": "config_field",
192
+ "params": {
193
+ "field_name": "unit_testing_instructions",
194
+ "file_path": "{docs_dir}/greenfield/{repo_name}/standards/unit_testing_instructions.md",
195
+ "operation": "update",
196
+ "only_if_null": true
197
+ },
198
+ "description": "Save the unit-testing standard",
199
+ "on_error": "warn_and_continue"
200
+ },
201
+ {
202
+ "type": "mcp_call",
203
+ "id": "upload_e2e_testing_instructions",
204
+ "tool": "config_field",
205
+ "params": {
206
+ "field_name": "e2e_testing_instructions",
207
+ "file_path": "{docs_dir}/greenfield/{repo_name}/standards/e2e_testing_instructions.md",
208
+ "operation": "update",
209
+ "only_if_null": true
210
+ },
211
+ "description": "Save the end-to-end testing standard",
212
+ "on_error": "warn_and_continue"
213
+ },
214
+ {
215
+ "type": "mcp_call",
216
+ "id": "upload_frontend_correctness_standards",
217
+ "tool": "config_field",
218
+ "params": {
219
+ "field_name": "frontend_correctness_standards",
220
+ "file_path": "{docs_dir}/greenfield/{repo_name}/standards/frontend_correctness_standards.md",
221
+ "operation": "update",
222
+ "only_if_null": true
223
+ },
224
+ "description": "Save the frontend correctness standard",
225
+ "on_error": "warn_and_continue"
226
+ },
227
+ {
228
+ "type": "mcp_call",
229
+ "id": "upload_backend_correctness_standards",
230
+ "tool": "config_field",
231
+ "params": {
232
+ "field_name": "backend_correctness_standards",
233
+ "file_path": "{docs_dir}/greenfield/{repo_name}/standards/backend_correctness_standards.md",
234
+ "operation": "update",
235
+ "only_if_null": true
236
+ },
237
+ "description": "Save the backend correctness standard",
238
+ "on_error": "warn_and_continue"
239
+ },
240
+ {
241
+ "type": "mcp_call",
242
+ "id": "upload_template_correctness_standards",
243
+ "tool": "config_field",
244
+ "params": {
245
+ "field_name": "template_correctness_standards",
246
+ "file_path": "{docs_dir}/greenfield/{repo_name}/standards/template_correctness_standards.md",
247
+ "operation": "update",
248
+ "only_if_null": true
249
+ },
250
+ "description": "Save the template correctness standard",
251
+ "on_error": "warn_and_continue"
252
+ },
253
+ {
254
+ "type": "mcp_call",
255
+ "id": "upload_style_correctness_standards",
256
+ "tool": "config_field",
257
+ "params": {
258
+ "field_name": "style_correctness_standards",
259
+ "file_path": "{docs_dir}/greenfield/{repo_name}/standards/style_correctness_standards.md",
260
+ "operation": "update",
261
+ "only_if_null": true
262
+ },
263
+ "description": "Save the style correctness standard",
264
+ "on_error": "warn_and_continue"
265
+ },
266
+ {
267
+ "type": "mcp_call",
268
+ "id": "upload_design_principles",
269
+ "tool": "config_field",
270
+ "params": {
271
+ "field_name": "design_principles",
272
+ "file_path": "{docs_dir}/greenfield/{repo_name}/standards/design_principles.md",
273
+ "operation": "update",
274
+ "only_if_null": true
275
+ },
276
+ "description": "Save the design principles",
277
+ "on_error": "warn_and_continue"
278
+ },
279
+ {
280
+ "type": "agent_task",
281
+ "id": "apply_and_report",
282
+ "instruction_file": "greenfield-apply-and-report.md",
283
+ "description": "Save the project setup and report where the project stands"
284
+ }
285
+ ]
286
+ }