@agentbridge1/cli 0.0.7 → 0.0.9

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.
@@ -0,0 +1,239 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.detectOutOfScopeFiles = detectOutOfScopeFiles;
4
+ exports.renderContractVerdict = renderContractVerdict;
5
+ const domain_resolution_1 = require("./domain-resolution");
6
+ const contract_intelligence_1 = require("./contract-intelligence");
7
+ const diff_reader_1 = require("./diff-reader");
8
+ const local_proof_1 = require("./local-proof");
9
+ const INTENT_STOP_WORDS = new Set([
10
+ "a",
11
+ "an",
12
+ "the",
13
+ "on",
14
+ "in",
15
+ "to",
16
+ "for",
17
+ "and",
18
+ "or",
19
+ "of",
20
+ "fix",
21
+ "add",
22
+ "update",
23
+ "change",
24
+ "make",
25
+ "with",
26
+ "from",
27
+ "into",
28
+ "by",
29
+ "at",
30
+ "is",
31
+ "are",
32
+ "be",
33
+ "do",
34
+ "does",
35
+ "did",
36
+ "was",
37
+ "were",
38
+ "it",
39
+ "its",
40
+ "this",
41
+ "that",
42
+ "endpoint",
43
+ "bug",
44
+ "issue",
45
+ "error",
46
+ ]);
47
+ function normalizePath(path) {
48
+ return path.trim().replaceAll("\\", "/");
49
+ }
50
+ function intentKeywords(intent) {
51
+ return intent
52
+ .toLowerCase()
53
+ .split(/[^a-z0-9]+/)
54
+ .map((word) => word.trim())
55
+ .filter((word) => word.length >= 3 && !INTENT_STOP_WORDS.has(word));
56
+ }
57
+ function fileMatchesIntentKeywords(file, keywords) {
58
+ if (keywords.length === 0)
59
+ return true;
60
+ const lower = normalizePath(file).toLowerCase();
61
+ return keywords.some((keyword) => lower.includes(keyword));
62
+ }
63
+ function fileMatchesClaimedPaths(file, claimedPaths) {
64
+ if (claimedPaths.length === 0)
65
+ return true;
66
+ const normalized = normalizePath(file);
67
+ return claimedPaths.some((pattern) => {
68
+ const trimmed = normalizePath(pattern);
69
+ if (!trimmed)
70
+ return false;
71
+ if (trimmed.endsWith("/**")) {
72
+ const prefix = trimmed.slice(0, -3);
73
+ return normalized === prefix || normalized.startsWith(`${prefix}/`);
74
+ }
75
+ if (trimmed.endsWith("/*")) {
76
+ const prefix = trimmed.slice(0, -2);
77
+ const slash = normalized.lastIndexOf("/");
78
+ const dir = slash >= 0 ? normalized.slice(0, slash) : "";
79
+ return dir === prefix;
80
+ }
81
+ return normalized === trimmed || normalized.startsWith(`${trimmed}/`);
82
+ });
83
+ }
84
+ function detectOutOfScopeFiles(changedFiles, session, domains) {
85
+ const meaningful = changedFiles
86
+ .map(normalizePath)
87
+ .filter((file) => file.length > 0 && !(0, local_proof_1.isProofNoiseFile)(file));
88
+ if (meaningful.length === 0)
89
+ return [];
90
+ const claimedPaths = session?.claimedPaths ?? [];
91
+ if (claimedPaths.length > 0) {
92
+ return meaningful.filter((file) => !fileMatchesClaimedPaths(file, claimedPaths));
93
+ }
94
+ const intent = session?.intent?.trim() ?? "";
95
+ if (!intent)
96
+ return [];
97
+ const keywords = intentKeywords(intent);
98
+ if (keywords.length === 0)
99
+ return [];
100
+ const outOfScope = [];
101
+ for (const file of meaningful) {
102
+ if (fileMatchesIntentKeywords(file, keywords))
103
+ continue;
104
+ if (domains.length > 0) {
105
+ const domain = (0, domain_resolution_1.resolveDomainForFile)(file, domains).domain;
106
+ if (domain && keywords.some((keyword) => domain.toLowerCase().includes(keyword))) {
107
+ continue;
108
+ }
109
+ }
110
+ outOfScope.push(file);
111
+ }
112
+ return outOfScope;
113
+ }
114
+ function renderContractVerdict(session, changedFiles, proofRun, domains = []) {
115
+ const intent = session?.intent?.trim() || "(no intent declared)";
116
+ const meaningfulFiles = changedFiles
117
+ .map(normalizePath)
118
+ .filter((file) => file.length > 0 && !(0, local_proof_1.isProofNoiseFile)(file));
119
+ const outOfScope = detectOutOfScopeFiles(meaningfulFiles, session, domains);
120
+ const contract = session?.intent?.trim()
121
+ ? (0, contract_intelligence_1.localSessionToContractIntelligence)(session)
122
+ : null;
123
+ // Read diff once — fresh from git, not stored in session
124
+ const diff = meaningfulFiles.length > 0 ? (0, diff_reader_1.readRepoDiff)(meaningfulFiles) : null;
125
+ // Evaluate criteria if we have a session with intent
126
+ const criteriaEvals = contract
127
+ ? (0, contract_intelligence_1.evaluateCompletionCriteria)({
128
+ contract,
129
+ changedFiles: meaningfulFiles,
130
+ proofRun,
131
+ diff,
132
+ })
133
+ : null;
134
+ const proofNeeded = contract?.proofNeeded ?? [];
135
+ const evidenced = criteriaEvals?.filter((e) => e.status === "evidence_found") ?? [];
136
+ // Next step: criteria-driven if available, else generic
137
+ let nextStep;
138
+ if (criteriaEvals && criteriaEvals.length > 0) {
139
+ nextStep = (0, contract_intelligence_1.nextStepFromCriteria)(criteriaEvals, proofNeeded);
140
+ }
141
+ else if (!proofRun) {
142
+ nextStep = "Run the relevant test command so AgentBridge can record proof for this contract.";
143
+ }
144
+ else if (outOfScope.length > 0) {
145
+ nextStep = "Ask the agent to justify the out-of-scope files, or revert them before accepting the work.";
146
+ }
147
+ else if (meaningfulFiles.length === 0) {
148
+ nextStep = "No work was recorded. Start a new contract when you are ready to implement.";
149
+ }
150
+ else {
151
+ nextStep = "Contract looks complete. Commit when ready.";
152
+ }
153
+ const lines = [
154
+ "AgentBridge Contract Verdict",
155
+ "─────────────────────────────────────────",
156
+ "",
157
+ "Here's what the contract was:",
158
+ ` ${intent}`,
159
+ "",
160
+ "Here's what the agent changed:",
161
+ ];
162
+ if (meaningfulFiles.length === 0) {
163
+ lines.push(" No files changed.");
164
+ }
165
+ else {
166
+ for (const file of meaningfulFiles) {
167
+ lines.push(` - ${file}`);
168
+ }
169
+ }
170
+ if (criteriaEvals && criteriaEvals.length > 0) {
171
+ lines.push("", "Here's what \"done\" needed to prove:");
172
+ for (const e of criteriaEvals) {
173
+ const icon = e.status === "evidence_found"
174
+ ? "✓"
175
+ : e.status === "partial_evidence"
176
+ ? "~"
177
+ : e.status === "cannot_verify"
178
+ ? "?"
179
+ : "✗";
180
+ lines.push(` ${icon} [${e.status}] ${e.criterion}`);
181
+ lines.push(` ${e.evidence}`);
182
+ }
183
+ }
184
+ else {
185
+ lines.push("", "Here's what appears missing:");
186
+ const missing = [];
187
+ if (!session?.intent?.trim())
188
+ missing.push("No contract intent was recorded.");
189
+ if (meaningfulFiles.length === 0)
190
+ missing.push("No files changed since the contract baseline.");
191
+ if (!proofRun)
192
+ missing.push("No proof/test run was recorded.");
193
+ else if (proofRun.status === "failed")
194
+ missing.push(`Proof failed: ${proofRun.command} (exit ${proofRun.exitCode}).`);
195
+ if (missing.length === 0) {
196
+ lines.push(" Nothing obvious from this contract check.");
197
+ }
198
+ else {
199
+ for (const item of missing)
200
+ lines.push(` - ${item}`);
201
+ }
202
+ }
203
+ if (evidenced.length > 0 && criteriaEvals) {
204
+ lines.push("", "Here's what AgentBridge saw evidence for:");
205
+ for (const e of evidenced) {
206
+ lines.push(` - ${e.criterion}`);
207
+ }
208
+ }
209
+ const stillUnproven = criteriaEvals
210
+ ? criteriaEvals.filter((e) => e.status === "no_evidence")
211
+ : [];
212
+ lines.push("", "Here's what is still unproven:");
213
+ if (stillUnproven.length === 0 && (!criteriaEvals || criteriaEvals.length === 0)) {
214
+ lines.push(!proofRun ? " No proof/test run was recorded." : " Nothing obvious from this contract check.");
215
+ }
216
+ else if (stillUnproven.length === 0) {
217
+ lines.push(" Nothing detected as fully unproven.");
218
+ }
219
+ else {
220
+ for (const e of stillUnproven) {
221
+ lines.push(` - ${e.criterion}`);
222
+ lines.push(` ${e.evidence}`);
223
+ }
224
+ }
225
+ lines.push("", "Here's where the work drifted out of scope:");
226
+ if (outOfScope.length === 0) {
227
+ lines.push(" No out-of-scope files detected.");
228
+ }
229
+ else {
230
+ for (const file of outOfScope) {
231
+ const reason = session?.intent?.trim()
232
+ ? `${file} was changed, but the contract was about ${session.intent.trim()}.`
233
+ : `${file} was changed outside the declared contract.`;
234
+ lines.push(` - ${reason}`);
235
+ }
236
+ }
237
+ lines.push("", "Here's the next step:", ` ${nextStep}`, "");
238
+ return lines.join("\n");
239
+ }
@@ -0,0 +1,200 @@
1
+ "use strict";
2
+ /**
3
+ * Diff reader — parses `git diff HEAD` output into structured change data.
4
+ * All logic is deterministic regex on git unified diff format. No LLM.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.readRepoDiff = readRepoDiff;
8
+ exports.parseDiffString = parseDiffString;
9
+ exports.getDiffForFile = getDiffForFile;
10
+ exports.summariseDiff = summariseDiff;
11
+ const node_child_process_1 = require("node:child_process");
12
+ // Function declarations
13
+ const FN_ADDED_RE = /^\+\s*(?:export\s+)?(?:async\s+)?function\s+(\w+)/;
14
+ const FN_REMOVED_RE = /^-\s*(?:export\s+)?(?:async\s+)?function\s+(\w+)/;
15
+ // Arrow / const functions: `const foo = (` or `const foo = async (`
16
+ const ARROW_ADDED_RE = /^\+\s*(?:export\s+)?const\s+(\w+)\s*=\s*(?:async\s+)?\(/;
17
+ const ARROW_REMOVED_RE = /^-\s*(?:export\s+)?const\s+(\w+)\s*=\s*(?:async\s+)?\(/;
18
+ // Non-function consts: `const FOO_BAR =` (all-caps or mixed without open paren)
19
+ const CONST_ADDED_RE = /^\+\s*(?:export\s+)?const\s+(\w+)\s*=/;
20
+ const CONST_REMOVED_RE = /^-\s*(?:export\s+)?const\s+(\w+)\s*=/;
21
+ // Import lines
22
+ const IMPORT_ADDED_RE = /^\+\s*import\s+.+\s+from\s+['"]([^'"]+)['"]/;
23
+ const IMPORT_REMOVED_RE = /^-\s*import\s+.+\s+from\s+['"]([^'"]+)['"]/;
24
+ // Named exports (excluding consts/functions already captured)
25
+ const EXPORT_ADDED_RE = /^\+\s*export\s+(?:const|function|class|type|interface|enum)\s+(\w+)/;
26
+ const EXPORT_REMOVED_RE = /^-\s*export\s+(?:const|function|class|type|interface|enum)\s+(\w+)/;
27
+ // Guard clauses: if(!x) throw / return / continue / break
28
+ const GUARD_RE = /^\+.*\bif\s*\([^)]*\).*\b(?:throw|return new Error|return null|return undefined)\b/;
29
+ // File header in git diff: "diff --git a/foo b/foo"
30
+ const FILE_HEADER_RE = /^diff --git a\/(.+) b\/.+/;
31
+ // Hunk header: @@ -a,b +c,d @@
32
+ const HUNK_RE = /^@@/;
33
+ function isArrowOrFnConst(line, added) {
34
+ const re = added ? ARROW_ADDED_RE : ARROW_REMOVED_RE;
35
+ return re.exec(line)?.[1] ?? null;
36
+ }
37
+ function isRegularConst(line, added) {
38
+ // Only count if it's NOT an arrow function (no open paren immediately after =)
39
+ const arrowRe = added ? ARROW_ADDED_RE : ARROW_REMOVED_RE;
40
+ if (arrowRe.test(line))
41
+ return null;
42
+ const re = added ? CONST_ADDED_RE : CONST_REMOVED_RE;
43
+ return re.exec(line)?.[1] ?? null;
44
+ }
45
+ function parseFileDiff(fileName, diffLines) {
46
+ const fd = {
47
+ file: fileName,
48
+ functions_added: [],
49
+ functions_removed: [],
50
+ functions_changed: [],
51
+ constants_added: [],
52
+ constants_removed: [],
53
+ imports_added: [],
54
+ imports_removed: [],
55
+ exports_added: [],
56
+ exports_removed: [],
57
+ guard_clauses_added: 0,
58
+ lines_added: 0,
59
+ lines_removed: 0,
60
+ };
61
+ for (const line of diffLines) {
62
+ if (HUNK_RE.test(line))
63
+ continue;
64
+ const isAdd = line.startsWith("+") && !line.startsWith("+++");
65
+ const isDel = line.startsWith("-") && !line.startsWith("---");
66
+ if (isAdd)
67
+ fd.lines_added++;
68
+ if (isDel)
69
+ fd.lines_removed++;
70
+ // Functions
71
+ const fnAdded = FN_ADDED_RE.exec(line)?.[1] ?? isArrowOrFnConst(line, true);
72
+ if (fnAdded) {
73
+ if (fd.functions_removed.includes(fnAdded)) {
74
+ fd.functions_changed.push(fnAdded);
75
+ fd.functions_removed.splice(fd.functions_removed.indexOf(fnAdded), 1);
76
+ }
77
+ else {
78
+ fd.functions_added.push(fnAdded);
79
+ }
80
+ }
81
+ const fnRemoved = FN_REMOVED_RE.exec(line)?.[1] ?? isArrowOrFnConst(line, false);
82
+ if (fnRemoved) {
83
+ const addedIdx = fd.functions_added.indexOf(fnRemoved);
84
+ if (addedIdx >= 0) {
85
+ fd.functions_changed.push(fnRemoved);
86
+ fd.functions_added.splice(addedIdx, 1);
87
+ }
88
+ else {
89
+ fd.functions_removed.push(fnRemoved);
90
+ }
91
+ }
92
+ // Constants (non-function)
93
+ const constAdded = isRegularConst(line, true);
94
+ if (constAdded)
95
+ fd.constants_added.push(constAdded);
96
+ const constRemoved = isRegularConst(line, false);
97
+ if (constRemoved)
98
+ fd.constants_removed.push(constRemoved);
99
+ // Imports
100
+ const importAdded = IMPORT_ADDED_RE.exec(line)?.[1];
101
+ if (importAdded)
102
+ fd.imports_added.push(importAdded);
103
+ const importRemoved = IMPORT_REMOVED_RE.exec(line)?.[1];
104
+ if (importRemoved)
105
+ fd.imports_removed.push(importRemoved);
106
+ // Exports
107
+ const exportAdded = EXPORT_ADDED_RE.exec(line)?.[1];
108
+ if (exportAdded && !fd.functions_added.includes(exportAdded) && !fd.constants_added.includes(exportAdded)) {
109
+ fd.exports_added.push(exportAdded);
110
+ }
111
+ const exportRemoved = EXPORT_REMOVED_RE.exec(line)?.[1];
112
+ if (exportRemoved && !fd.functions_removed.includes(exportRemoved) && !fd.constants_removed.includes(exportRemoved)) {
113
+ fd.exports_removed.push(exportRemoved);
114
+ }
115
+ // Guard clauses
116
+ if (isAdd && GUARD_RE.test(line))
117
+ fd.guard_clauses_added++;
118
+ }
119
+ return fd;
120
+ }
121
+ /**
122
+ * Reads git diff HEAD for the given changed files and returns structured diffs.
123
+ * Falls back gracefully if git is unavailable or diff fails.
124
+ */
125
+ function readRepoDiff(changedFiles) {
126
+ if (changedFiles.length === 0)
127
+ return { files: [], totalLinesAdded: 0, totalLinesRemoved: 0 };
128
+ let rawDiff;
129
+ try {
130
+ rawDiff = (0, node_child_process_1.execFileSync)("git", ["diff", "HEAD", "--unified=0", "--", ...changedFiles], {
131
+ encoding: "utf-8",
132
+ stdio: ["ignore", "pipe", "ignore"],
133
+ timeout: 5000,
134
+ });
135
+ }
136
+ catch {
137
+ // Fallback: try diff against staged + unstaged
138
+ try {
139
+ rawDiff = (0, node_child_process_1.execFileSync)("git", ["diff", "--unified=0", "--", ...changedFiles], {
140
+ encoding: "utf-8",
141
+ stdio: ["ignore", "pipe", "ignore"],
142
+ timeout: 5000,
143
+ });
144
+ }
145
+ catch {
146
+ return { files: [], totalLinesAdded: 0, totalLinesRemoved: 0 };
147
+ }
148
+ }
149
+ return parseDiffString(rawDiff);
150
+ }
151
+ /**
152
+ * Parse a raw git diff string into RepoDiff. Exported for testing without git.
153
+ */
154
+ function parseDiffString(rawDiff) {
155
+ const lines = rawDiff.split("\n");
156
+ const fileBlocks = [];
157
+ let current = null;
158
+ for (const line of lines) {
159
+ const fileMatch = FILE_HEADER_RE.exec(line);
160
+ if (fileMatch) {
161
+ if (current)
162
+ fileBlocks.push(current);
163
+ current = { name: fileMatch[1], lines: [] };
164
+ }
165
+ else if (current) {
166
+ current.lines.push(line);
167
+ }
168
+ }
169
+ if (current)
170
+ fileBlocks.push(current);
171
+ const files = fileBlocks.map((b) => parseFileDiff(b.name, b.lines));
172
+ return {
173
+ files,
174
+ totalLinesAdded: files.reduce((s, f) => s + f.lines_added, 0),
175
+ totalLinesRemoved: files.reduce((s, f) => s + f.lines_removed, 0),
176
+ };
177
+ }
178
+ /**
179
+ * Returns a FileDiff for a specific file, or null if not in the diff.
180
+ */
181
+ function getDiffForFile(diff, file) {
182
+ const normalized = file.replaceAll("\\", "/");
183
+ return diff.files.find((f) => f.file === normalized || f.file.endsWith(`/${normalized}`) || normalized.endsWith(`/${f.file}`)) ?? null;
184
+ }
185
+ /**
186
+ * Summarise diff changes across all files in a few readable tokens for evidence strings.
187
+ */
188
+ function summariseDiff(diff) {
189
+ const allExportsAdded = diff.files.flatMap((f) => f.exports_added.concat(f.functions_added));
190
+ const allGuards = diff.files.reduce((s, f) => s + f.guard_clauses_added, 0);
191
+ const parts = [];
192
+ if (diff.totalLinesAdded > 0 || diff.totalLinesRemoved > 0) {
193
+ parts.push(`+${diff.totalLinesAdded}/-${diff.totalLinesRemoved} lines`);
194
+ }
195
+ if (allExportsAdded.length > 0)
196
+ parts.push(`${allExportsAdded.length} new export(s): ${allExportsAdded.slice(0, 3).join(", ")}`);
197
+ if (allGuards > 0)
198
+ parts.push(`${allGuards} guard clause(s) added`);
199
+ return parts.join("; ");
200
+ }
@@ -30,6 +30,19 @@ const CATALOG = {
30
30
  why: "Commands target the wrong room or environment.",
31
31
  next: "Verify AGENTBRIDGE_PROJECT_ID and rerun `agentbridge init --project <id>`.",
32
32
  },
33
+ CONNECT_EXECUTION_SURFACE_MISSING: {
34
+ code: "CONNECT_EXECUTION_SURFACE_MISSING",
35
+ category: "IDENTITY_ERROR",
36
+ title: "Connection incomplete.",
37
+ what: "Project access was verified, but this key is not bound to an AgentConnection execution surface.",
38
+ why: "Connect requires a room connection key that resolves to work_identity on /hello.",
39
+ next: [
40
+ "In the AgentBridge dashboard, open this room and rotate the API key (or create a new room).",
41
+ "Update AGENTBRIDGE_API_KEY or .agentbridge/config.json with the new key.",
42
+ "Run: agentbridge connect --project <project-id> --api-key <new-key>",
43
+ "Connect does not create or rotate keys automatically.",
44
+ ].join("\n"),
45
+ },
33
46
  IDENTITY_ACTIVE_AGENT_STALE: {
34
47
  code: "IDENTITY_ACTIVE_AGENT_STALE",
35
48
  category: "IDENTITY_ERROR",
@@ -229,6 +242,22 @@ const CATALOG = {
229
242
  why: "Scoped work must target a recovered domain lane.",
230
243
  next: 'Pass `--domain "<domain>"` with start, or run `agentbridge init`.',
231
244
  },
245
+ START_LANE_CLAIM_CONFIRM_REQUIRED: {
246
+ code: "START_LANE_CLAIM_CONFIRM_REQUIRED",
247
+ category: "START_ERROR",
248
+ title: "Lane claim needs explicit confirmation.",
249
+ what: "Start inferred a domain with medium confidence.",
250
+ why: "Production-safe lane claim requires explicit confirmation when scope-to-domain mapping is ambiguous.",
251
+ next: 'Rerun with `--confirm-domain` or pass an explicit `--domain "<domain>"`.',
252
+ },
253
+ START_LANE_CLAIM_LOW_CONFIDENCE: {
254
+ code: "START_LANE_CLAIM_LOW_CONFIDENCE",
255
+ category: "START_ERROR",
256
+ title: "Lane claim confidence too low.",
257
+ what: "AgentBridge could not confidently bind this work to a single domain lane.",
258
+ why: "A wrong lane claim undermines scope enforcement and ownership attribution.",
259
+ next: 'Pass explicit `--domain "<domain>"` and tighter `--scope "<path>"`, then rerun start.',
260
+ },
232
261
  START_OWNER_UNRESOLVED: {
233
262
  code: "START_OWNER_UNRESOLVED",
234
263
  category: "START_ERROR",
@@ -7,6 +7,7 @@ exports.repoDirtySnapshotFromGitStatus = repoDirtySnapshotFromGitStatus;
7
7
  exports.getBranchName = getBranchName;
8
8
  exports.getDirtyWorkingTreeFiles = getDirtyWorkingTreeFiles;
9
9
  const node_child_process_1 = require("node:child_process");
10
+ const local_proof_1 = require("./local-proof");
10
11
  function normalizePath(path) {
11
12
  return path.trim().replaceAll("\\", "/");
12
13
  }
@@ -41,13 +42,16 @@ function parseGitStatusPorcelainZ(raw) {
41
42
  }
42
43
  return entries;
43
44
  }
45
+ function isSupervisionRelevantPath(path) {
46
+ return !isAgentbridgeLocalPath(path) && !(0, local_proof_1.isProofNoiseFile)(path);
47
+ }
44
48
  function collectChangedFilesFromStatusEntries(entries) {
45
49
  const files = new Set();
46
50
  for (const entry of entries) {
47
- if (entry.path && !isAgentbridgeLocalPath(entry.path)) {
51
+ if (entry.path && isSupervisionRelevantPath(entry.path)) {
48
52
  files.add(entry.path);
49
53
  }
50
- if (entry.originalPath && !isAgentbridgeLocalPath(entry.originalPath)) {
54
+ if (entry.originalPath && isSupervisionRelevantPath(entry.originalPath)) {
51
55
  files.add(entry.originalPath);
52
56
  }
53
57
  }
package/dist/index.js CHANGED
@@ -55,11 +55,11 @@ const COMMAND_HELP = {
55
55
  " Run `agentbridge connect` first to save credentials.\n",
56
56
  use: " agentbridge use <agent-id>\n" +
57
57
  " Set the active agent identity used by `watch` and `start`.\n",
58
- start: " agentbridge start [\"intent\"] [--details] [--yes]\n" +
58
+ start: " agentbridge start [\"intent\"] [--details] [--yes] [--confirm-domain]\n" +
59
59
  " Open or close a task checkpoint (not the live watcher).\n" +
60
60
  " For live supervision beside Cursor, run `agentbridge watch`.\n" +
61
61
  " Record proof: `agentbridge verify -- <test command>`.\n",
62
- watch: " agentbridge watch [--allow-dirty] [--once] [--daemon] [--details] [--task \"<summary>\"] [--scope \"<path>\"]\n" +
62
+ watch: " agentbridge watch [--allow-dirty] [--once] [--daemon] [--details] [--confirm-domain] [--task \"<summary>\"] [--scope \"<path>\"]\n" +
63
63
  " Primary live supervision command — run beside Cursor while the agent codes.\n" +
64
64
  " Shows MCP-declared intent and disk changes; warns on scope drift.\n" +
65
65
  " No --task or --scope required for normal use.\n" +
@@ -68,8 +68,9 @@ const COMMAND_HELP = {
68
68
  " --once Run one supervision pass and exit.\n" +
69
69
  " --daemon Non-interactive mode: skip prompts, auto-deny domain crossings.\n" +
70
70
  " --change-request / --execution-surface Advanced session binding.\n",
71
- "setup-mcp": " agentbridge setup-mcp [--editor cursor|windsurf|vscode]\n" +
72
- " Print the MCP server configuration snippet to add to your editor.\n",
71
+ "setup-mcp": " agentbridge setup-mcp [--local] [--editor cursor|windsurf|vscode]\n" +
72
+ " --local Write local-first MCP config (no API keys) to .cursor/mcp.json\n" +
73
+ " Print or write the MCP server configuration for your editor.\n",
73
74
  mcp: " agentbridge mcp\n" +
74
75
  " Start the AgentBridge MCP stdio server.\n",
75
76
  status: " agentbridge status [--repair] [--json]\n Show current work session status.\n",
@@ -300,7 +301,10 @@ async function main() {
300
301
  return;
301
302
  }
302
303
  if (command === "setup-mcp") {
303
- await (0, setup_mcp_1.runSetupMcp)({ editor: flags["--editor"] });
304
+ await (0, setup_mcp_1.runSetupMcp)({
305
+ editor: flags["--editor"],
306
+ local: flags["--local"] === "true",
307
+ });
304
308
  return;
305
309
  }
306
310
  if (command === "mcp") {
@@ -372,6 +376,7 @@ async function main() {
372
376
  daemon: flags["--daemon"] === "true",
373
377
  once: flags["--once"] === "true",
374
378
  details: flags["--details"] === "true",
379
+ confirmDomain: flags["--confirm-domain"] === "true",
375
380
  });
376
381
  return;
377
382
  }
@@ -391,6 +396,7 @@ async function main() {
391
396
  resume: flags["--resume"] === "true",
392
397
  details: flags["--details"] === "true",
393
398
  confirmClose: flags["--yes"] === "true",
399
+ confirmDomain: flags["--confirm-domain"] === "true",
394
400
  });
395
401
  return;
396
402
  }