@bridge_gpt/mcp-server 0.2.2 → 0.2.4

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.
Files changed (113) hide show
  1. package/README.md +97 -15
  2. package/build/agent-config-credential-migration.js +272 -0
  3. package/build/agents.generated.js +1 -1
  4. package/build/chain-orchestrator.js +16 -1
  5. package/build/commands.generated.js +9 -7
  6. package/build/conductor/bridge-api-client.js +625 -0
  7. package/build/conductor/claude-hook.js +251 -0
  8. package/build/conductor/cli.js +1048 -0
  9. package/build/conductor/data-normalization.js +114 -0
  10. package/build/conductor/doctor.js +164 -0
  11. package/build/conductor/done-gate.js +325 -0
  12. package/build/conductor/epic-reconcile.js +139 -0
  13. package/build/conductor/epic-runtime.js +611 -0
  14. package/build/conductor/epic-state.js +125 -0
  15. package/build/conductor/errors.js +85 -0
  16. package/build/conductor/git-ci-types.js +129 -0
  17. package/build/conductor/git-hooks.js +218 -0
  18. package/build/conductor/git-inspection.js +185 -0
  19. package/build/conductor/git-producer.js +137 -0
  20. package/build/conductor/merge-ledger.js +198 -0
  21. package/build/conductor/paths.js +224 -0
  22. package/build/conductor/plan.js +77 -0
  23. package/build/conductor/pr-ci-producer.js +427 -0
  24. package/build/conductor/pr-discovery.js +135 -0
  25. package/build/conductor/producer-ledger.js +125 -0
  26. package/build/conductor/redaction.js +112 -0
  27. package/build/conductor/store.js +1156 -0
  28. package/build/conductor/supervisor-config.js +150 -0
  29. package/build/conductor/supervisor-escalation.js +244 -0
  30. package/build/conductor/supervisor-judgment-python.js +141 -0
  31. package/build/conductor/supervisor-judgment.js +215 -0
  32. package/build/conductor/supervisor-ledger.js +119 -0
  33. package/build/conductor/supervisor-merge.js +127 -0
  34. package/build/conductor/supervisor-message-relay.js +61 -0
  35. package/build/conductor/supervisor-notification.js +39 -0
  36. package/build/conductor/supervisor-runtime.js +351 -0
  37. package/build/conductor/supervisor-state.js +572 -0
  38. package/build/conductor/supervisor-types.js +16 -0
  39. package/build/conductor/taxonomy.js +58 -0
  40. package/build/conductor/tools.js +367 -0
  41. package/build/conductor/types.js +9 -0
  42. package/build/conductor-bin.js +21 -0
  43. package/build/conductor-claude-hook-bin.js +21 -0
  44. package/build/credential-store.js +175 -4
  45. package/build/credentials-cli.js +223 -0
  46. package/build/decision-page-schema.js +60 -0
  47. package/build/decision-page-template.js +262 -10
  48. package/build/doctor.js +5 -1
  49. package/build/index.js +554 -66
  50. package/build/pipeline-orchestrator.js +5 -1
  51. package/build/pipeline-utils.js +45 -5
  52. package/build/pipelines.generated.js +37 -9
  53. package/build/readme.generated.js +1 -1
  54. package/build/review-tickets.js +596 -0
  55. package/build/scheduled-prompt.js +16 -10
  56. package/build/start-tickets-conductor.js +496 -0
  57. package/build/start-tickets-prereqs.js +32 -23
  58. package/build/start-tickets-repo.js +49 -0
  59. package/build/start-tickets.js +682 -81
  60. package/build/version.generated.js +1 -1
  61. package/design-assets/favicon/android-chrome-192x192.png +0 -0
  62. package/design-assets/favicon/android-chrome-512x512.png +0 -0
  63. package/design-assets/favicon/apple-touch-icon.png +0 -0
  64. package/design-assets/favicon/favicon-16x16.png +0 -0
  65. package/design-assets/favicon/favicon-32x32.png +0 -0
  66. package/design-assets/favicon/favicon.ico +0 -0
  67. package/design-assets/favicon/site.webmanifest +1 -0
  68. package/design-assets/just-logo-rough-draft.png +0 -0
  69. package/package.json +17 -5
  70. package/pipelines/idea-to-ticket.json +5 -0
  71. package/pipelines/plan-epic.json +16 -1
  72. package/pipelines/review-ticket.json +2 -1
  73. package/public/css/main.min.css +2 -0
  74. package/public/css/main.min.css.map +1 -0
  75. package/public/fonts/OFL.txt +93 -0
  76. package/public/fonts/SourceSansPro-Black.ttf +0 -0
  77. package/public/fonts/SourceSansPro-BlackItalic.ttf +0 -0
  78. package/public/fonts/SourceSansPro-Bold.ttf +0 -0
  79. package/public/fonts/SourceSansPro-BoldItalic.ttf +0 -0
  80. package/public/fonts/SourceSansPro-ExtraLight.ttf +0 -0
  81. package/public/fonts/SourceSansPro-ExtraLightItalic.ttf +0 -0
  82. package/public/fonts/SourceSansPro-Italic.ttf +0 -0
  83. package/public/fonts/SourceSansPro-Light.ttf +0 -0
  84. package/public/fonts/SourceSansPro-LightItalic.ttf +0 -0
  85. package/public/fonts/SourceSansPro-Regular.ttf +0 -0
  86. package/public/fonts/SourceSansPro-SemiBold.ttf +0 -0
  87. package/public/fonts/SourceSansPro-SemiBoldItalic.ttf +0 -0
  88. package/public/img/bridge-logo-160x51.webp +0 -0
  89. package/public/img/bridge-logo-300x92.webp +0 -0
  90. package/public/img/favicon/android-chrome-192x192.png +0 -0
  91. package/public/img/favicon/android-chrome-512x512.png +0 -0
  92. package/public/img/favicon/apple-touch-icon.png +0 -0
  93. package/public/img/favicon/favicon-16x16.png +0 -0
  94. package/public/img/favicon/favicon-32x32.png +0 -0
  95. package/public/img/favicon/favicon.ico +0 -0
  96. package/public/img/favicon/site.webmanifest +1 -0
  97. package/public/img/installation/bitbucket/app-password-1.png +0 -0
  98. package/public/img/installation/bitbucket/app-password-2.png +0 -0
  99. package/public/img/installation/bitbucket/create-token-1.png +0 -0
  100. package/public/img/installation/bitbucket/create-token-2.png +0 -0
  101. package/public/img/installation/bitbucket/webhook-1.png +0 -0
  102. package/public/img/installation/github/github-review-webhook.png +0 -0
  103. package/public/img/installation/jira/credentials/api-key.png +0 -0
  104. package/public/img/installation/jira/webhook/create-rule.png +0 -0
  105. package/public/img/installation/jira/webhook/project-settings.png +0 -0
  106. package/public/img/installation/jira/webhook/rule-create-1.png +0 -0
  107. package/public/img/installation/jira/webhook/rule-create-2.png +0 -0
  108. package/public/img/installation/jira/webhook/rule-create-3.png +0 -0
  109. package/public/img/installation/pinecone/pinecone-api-key.png +0 -0
  110. package/public/img/installation/pinecone/pinecone-index.png +0 -0
  111. package/public/js/main.min.js +2 -0
  112. package/public/js/main.min.js.map +1 -0
  113. package/smoke-test/SMOKE-TEST.md +17 -9
@@ -0,0 +1,224 @@
1
+ /**
2
+ * Local conductor ledger path resolution and filesystem hardening.
3
+ *
4
+ * The ledger lives next to the Tier-1 credential store at
5
+ * `~/.config/bridge/events.db` (honoring `XDG_CONFIG_HOME`), reusing the exact
6
+ * directory precedent from credential-store.ts so the two local-state files
7
+ * stay co-located and XDG-consistent. Directories are created `0700` and the DB
8
+ * file `0600` on POSIX; Windows relies on user-profile ACLs (no chmod). All
9
+ * health/warning output is secret-free: only paths, filesystem types, and mode
10
+ * bits are ever surfaced.
11
+ */
12
+ import fs from "node:fs";
13
+ import os from "node:os";
14
+ import path from "node:path";
15
+ import { execFileSync } from "node:child_process";
16
+ import { getPrimaryCredentialStorePath } from "../credential-store.js";
17
+ /**
18
+ * Resolve the ledger DB path: take the credential-store path (which honors
19
+ * `XDG_CONFIG_HOME` exactly) and swap `credentials.json` for `events.db`, so the
20
+ * directory derivation is shared rather than re-implemented.
21
+ */
22
+ export function getConductorLedgerPath() {
23
+ const credentialsPath = getPrimaryCredentialStorePath({
24
+ env: process.env,
25
+ homedir: os.homedir,
26
+ });
27
+ return path.join(path.dirname(credentialsPath), "events.db");
28
+ }
29
+ /** The directory containing the ledger DB file. */
30
+ export function getConductorLedgerDirectory() {
31
+ return path.dirname(getConductorLedgerPath());
32
+ }
33
+ /**
34
+ * Create the ledger directory recursively and, on POSIX, enforce `0700`. On
35
+ * Windows the chmod is skipped (user-profile ACLs apply instead).
36
+ */
37
+ export function ensureConductorLedgerDirectory() {
38
+ const dir = getConductorLedgerDirectory();
39
+ fs.mkdirSync(dir, { recursive: true });
40
+ if (process.platform !== "win32") {
41
+ fs.chmodSync(dir, 0o700);
42
+ }
43
+ return dir;
44
+ }
45
+ /**
46
+ * Ensure the `events.db` file exists with restrictive permissions BEFORE the
47
+ * SQLite driver opens it for writes. Creates the file `0600` if absent (without
48
+ * truncating an existing file) and re-applies `0600` on POSIX. Windows skips the
49
+ * chmod enforcement.
50
+ */
51
+ export function ensureConductorDatabaseFile() {
52
+ ensureConductorLedgerDirectory();
53
+ const dbPath = getConductorLedgerPath();
54
+ if (!fs.existsSync(dbPath)) {
55
+ // `wx`-style create-if-absent without truncation; close immediately so the
56
+ // SQLite driver owns the handle. Mode is applied on creation on POSIX.
57
+ const fd = fs.openSync(dbPath, "a", 0o600);
58
+ fs.closeSync(fd);
59
+ }
60
+ if (process.platform !== "win32") {
61
+ fs.chmodSync(dbPath, 0o600);
62
+ }
63
+ return dbPath;
64
+ }
65
+ /** Render the low 9 permission bits of a stat mode as an octal string (e.g. "700"). */
66
+ function formatMode(mode) {
67
+ return (mode & 0o777).toString(8).padStart(3, "0");
68
+ }
69
+ /**
70
+ * Inspect ledger directory/file existence and permissions without creating or
71
+ * modifying anything. Insecure (group/world-accessible) modes are reported as
72
+ * `*_permissions_ok: false` plus a `warnings` entry rather than throwing.
73
+ */
74
+ export function getConductorPathHealth() {
75
+ const dbPath = getConductorLedgerPath();
76
+ const dir = path.dirname(dbPath);
77
+ const warnings = [];
78
+ let directoryExists = false;
79
+ let directoryMode = null;
80
+ let directoryPermissionsOk = true;
81
+ try {
82
+ const dirStat = fs.statSync(dir);
83
+ directoryExists = true;
84
+ directoryMode = formatMode(dirStat.mode);
85
+ if (process.platform !== "win32" && (dirStat.mode & 0o077) !== 0) {
86
+ directoryPermissionsOk = false;
87
+ warnings.push(`Conductor ledger directory ${dir} is group/world-accessible; it should be mode 0700.`);
88
+ }
89
+ }
90
+ catch {
91
+ directoryExists = false;
92
+ }
93
+ let databaseExists = false;
94
+ let databaseMode = null;
95
+ let databasePermissionsOk = true;
96
+ try {
97
+ const fileStat = fs.statSync(dbPath);
98
+ databaseExists = true;
99
+ databaseMode = formatMode(fileStat.mode);
100
+ if (process.platform !== "win32" && (fileStat.mode & 0o077) !== 0) {
101
+ databasePermissionsOk = false;
102
+ warnings.push(`Conductor ledger file ${dbPath} is group/world-accessible; it should be mode 0600.`);
103
+ }
104
+ }
105
+ catch {
106
+ databaseExists = false;
107
+ }
108
+ return {
109
+ path: dbPath,
110
+ directory_path: dir,
111
+ directory_exists: directoryExists,
112
+ database_exists: databaseExists,
113
+ directory_mode: directoryMode,
114
+ database_mode: databaseMode,
115
+ directory_permissions_ok: directoryPermissionsOk,
116
+ database_permissions_ok: databasePermissionsOk,
117
+ warnings,
118
+ };
119
+ }
120
+ /** Filesystem-type fragments that indicate a network-mounted home directory. */
121
+ const NETWORK_FS_MARKERS = [
122
+ "nfs",
123
+ "cifs",
124
+ "smb",
125
+ "smbfs",
126
+ "afpfs",
127
+ "sshfs",
128
+ "fuse.sshfs",
129
+ "fuse.gvfs",
130
+ "9p",
131
+ "webdav",
132
+ ];
133
+ function matchesNetworkFsType(raw) {
134
+ const lowered = raw.trim().toLowerCase();
135
+ if (lowered.length === 0)
136
+ return null;
137
+ for (const marker of NETWORK_FS_MARKERS) {
138
+ if (lowered.includes(marker)) {
139
+ return lowered;
140
+ }
141
+ }
142
+ return null;
143
+ }
144
+ /**
145
+ * Best-effort detection of a network-mounted home directory. Concurrent
146
+ * SQLite/WAL writes on NFS/SMB are unreliable, so we surface a degraded warning
147
+ * rather than failing. Detection that cannot run returns a non-throwing
148
+ * "unknown" result (`network_mounted: false`, no warning).
149
+ *
150
+ * - win32: flag UNC home paths (`\\server\share`).
151
+ * - linux: `stat -f -c %T <home>` and match the filesystem type.
152
+ * - darwin: `stat -f %T <home>` and match the filesystem type.
153
+ */
154
+ export function detectNetworkMountedHome() {
155
+ let home = "";
156
+ try {
157
+ home = os.homedir();
158
+ }
159
+ catch {
160
+ return { network_mounted: false, filesystem_type: null, home: "", warning: null };
161
+ }
162
+ if (process.platform === "win32") {
163
+ if (home.startsWith("\\\\") || home.startsWith("//")) {
164
+ return {
165
+ network_mounted: true,
166
+ filesystem_type: "unc",
167
+ home,
168
+ warning: `Home directory ${home} appears to be a UNC network share; ` +
169
+ `concurrent SQLite writes to the conductor ledger may be unreliable.`,
170
+ };
171
+ }
172
+ return { network_mounted: false, filesystem_type: null, home, warning: null };
173
+ }
174
+ const args = process.platform === "darwin"
175
+ ? ["-f", "%T", home]
176
+ : ["-f", "-c", "%T", home];
177
+ try {
178
+ const output = execFileSync("stat", args, {
179
+ encoding: "utf-8",
180
+ timeout: 1500,
181
+ stdio: ["ignore", "pipe", "ignore"],
182
+ });
183
+ const fsType = matchesNetworkFsType(output);
184
+ if (fsType) {
185
+ return {
186
+ network_mounted: true,
187
+ filesystem_type: fsType,
188
+ home,
189
+ warning: `Home directory ${home} appears to be on a network filesystem (${fsType}); ` +
190
+ `concurrent SQLite writes to the conductor ledger may be unreliable.`,
191
+ };
192
+ }
193
+ return {
194
+ network_mounted: false,
195
+ filesystem_type: output.trim().toLowerCase() || null,
196
+ home,
197
+ warning: null,
198
+ };
199
+ }
200
+ catch {
201
+ // Detection failed (no `stat`, timeout, permission). Degrade gracefully.
202
+ return { network_mounted: false, filesystem_type: null, home, warning: null };
203
+ }
204
+ }
205
+ /** Process-level guard so the network-home warning is emitted at most once. */
206
+ let networkHomeWarned = false;
207
+ /** Reset the one-time warning guard. Test-only seam. */
208
+ export function resetNetworkHomeWarning() {
209
+ networkHomeWarned = false;
210
+ }
211
+ /**
212
+ * Emit a single process-level warning when the home directory looks
213
+ * network-mounted, and return the detection so MCP/CLI responses can report
214
+ * `degraded: true`. Only filesystem-type/path metadata is logged — never event
215
+ * payloads or secrets.
216
+ */
217
+ export function warnIfNetworkMountedHome() {
218
+ const detection = detectNetworkMountedHome();
219
+ if (detection.network_mounted && detection.warning && !networkHomeWarned) {
220
+ networkHomeWarned = true;
221
+ console.error(`Warning: ${detection.warning}`);
222
+ }
223
+ return detection;
224
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Epic Supervisor plan representation — machine-readable DAG, canonical
3
+ * serialization, and content-addressed hashing.
4
+ *
5
+ * The plan is an immutable, content-addressed blob keyed by {@link hashPlan}.
6
+ * The sibling `epic-tick` reconcile loop enforces the gate:
7
+ * `stableJsonHash(currentPlan) === epic_runs.approved_plan_hash`
8
+ * and FAIL-CLOSES (dispatches nothing, escalates, waits for re-approval) on
9
+ * any mismatch. The hash function exported here is the canonical implementation
10
+ * both sides import so the loop-side hash and the approved hash are always
11
+ * byte-identical (mirroring how `makeMergeActionKey` is kept in lock-step with
12
+ * the Python `build_merge_action_key`).
13
+ *
14
+ * The source of `EpicPlanDAG` values is LLM generation recipes (e.g.
15
+ * `/plan-epic`). The supervisor computes the ready-set deterministically from
16
+ * the DAG (`depends_on` all `done`) and never asks an LLM "what's next."
17
+ *
18
+ * Architecture note: the plan blob is stored as a dedicated durable-store row
19
+ * (not a decision-page artifact) via the protected `/jira/epic-runs/runs/{epicKey}/plan`
20
+ * endpoint, keeping the decision-page surface for human-facing approvals only.
21
+ * Plan data is immutable per `(epic_run_id, plan_version)` — a re-plan writes a
22
+ * new `plan_version + 1`, never mutating an existing blob.
23
+ */
24
+ import { stableJsonHash } from "./git-ci-types.js";
25
+ // ---------------------------------------------------------------------------
26
+ // Canonicalization
27
+ // ---------------------------------------------------------------------------
28
+ /**
29
+ * Produce a fully-normalized copy of the plan DAG so that equivalent plans
30
+ * hash identically regardless of input ordering or whitespace:
31
+ *
32
+ * - Trims outer whitespace from every `ticket_key` (nodes and edges).
33
+ * - Sorts each node's `depends_on` array alphabetically.
34
+ * - Sorts `nodes` lexicographically by normalized `ticket_key`.
35
+ * - Sorts `edges` lexicographically by `from` then `to`.
36
+ *
37
+ * The canonical form is fed to {@link hashPlan} / `stableJsonHash`, which
38
+ * additionally sorts object keys recursively, so two independently constructed
39
+ * equal plans always produce a byte-identical SHA-256 digest.
40
+ */
41
+ export function canonicalizePlanDAG(plan) {
42
+ const nodes = plan.nodes
43
+ .map((node) => ({
44
+ ...node,
45
+ ticket_key: node.ticket_key.trim(),
46
+ depends_on: [...node.depends_on].map((k) => k.trim()).sort(),
47
+ }))
48
+ .sort((a, b) => a.ticket_key.localeCompare(b.ticket_key));
49
+ const edges = [...plan.edges]
50
+ .map((e) => ({ from: e.from.trim(), to: e.to.trim() }))
51
+ .sort((a, b) => {
52
+ const cmp = a.from.localeCompare(b.from);
53
+ return cmp !== 0 ? cmp : a.to.localeCompare(b.to);
54
+ });
55
+ return { plan_version: plan.plan_version, nodes, edges };
56
+ }
57
+ // ---------------------------------------------------------------------------
58
+ // Content-addressed hashing
59
+ // ---------------------------------------------------------------------------
60
+ /**
61
+ * Compute the canonical SHA-256 content-address of the plan. Canonicalizes
62
+ * before hashing so the result is insensitive to object key order, array
63
+ * order in `depends_on`, node order, and whitespace in ticket keys — but
64
+ * sensitive to any semantic change (added/removed node, edge, automation
65
+ * kind, or `base_lineage`). Two independently constructed equal plans always
66
+ * hash identically (cross-machine requirement from OQ1/OQ3).
67
+ *
68
+ * Reuses {@link stableJsonHash} from `git-ci-types.ts` — never invents a
69
+ * second hashing path.
70
+ *
71
+ * Note: `status` is part of the hash (by design — it is part of the type).
72
+ * The gate's stability guarantee holds because the approved blob always has all
73
+ * statuses as `"planned"`; the supervisor tracks live progress externally.
74
+ */
75
+ export function hashPlan(plan) {
76
+ return stableJsonHash(canonicalizePlanDAG(plan));
77
+ }