@agentbridge1/cli 0.0.7 → 0.0.8

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,186 @@
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 local_proof_1 = require("./local-proof");
7
+ const INTENT_STOP_WORDS = new Set([
8
+ "a",
9
+ "an",
10
+ "the",
11
+ "on",
12
+ "in",
13
+ "to",
14
+ "for",
15
+ "and",
16
+ "or",
17
+ "of",
18
+ "fix",
19
+ "add",
20
+ "update",
21
+ "change",
22
+ "make",
23
+ "with",
24
+ "from",
25
+ "into",
26
+ "by",
27
+ "at",
28
+ "is",
29
+ "are",
30
+ "be",
31
+ "do",
32
+ "does",
33
+ "did",
34
+ "was",
35
+ "were",
36
+ "it",
37
+ "its",
38
+ "this",
39
+ "that",
40
+ "endpoint",
41
+ "bug",
42
+ "issue",
43
+ "error",
44
+ ]);
45
+ function normalizePath(path) {
46
+ return path.trim().replaceAll("\\", "/");
47
+ }
48
+ function intentKeywords(intent) {
49
+ return intent
50
+ .toLowerCase()
51
+ .split(/[^a-z0-9]+/)
52
+ .map((word) => word.trim())
53
+ .filter((word) => word.length >= 3 && !INTENT_STOP_WORDS.has(word));
54
+ }
55
+ function fileMatchesIntentKeywords(file, keywords) {
56
+ if (keywords.length === 0)
57
+ return true;
58
+ const lower = normalizePath(file).toLowerCase();
59
+ return keywords.some((keyword) => lower.includes(keyword));
60
+ }
61
+ function fileMatchesClaimedPaths(file, claimedPaths) {
62
+ if (claimedPaths.length === 0)
63
+ return true;
64
+ const normalized = normalizePath(file);
65
+ return claimedPaths.some((pattern) => {
66
+ const trimmed = normalizePath(pattern);
67
+ if (!trimmed)
68
+ return false;
69
+ if (trimmed.endsWith("/**")) {
70
+ const prefix = trimmed.slice(0, -3);
71
+ return normalized === prefix || normalized.startsWith(`${prefix}/`);
72
+ }
73
+ if (trimmed.endsWith("/*")) {
74
+ const prefix = trimmed.slice(0, -2);
75
+ const slash = normalized.lastIndexOf("/");
76
+ const dir = slash >= 0 ? normalized.slice(0, slash) : "";
77
+ return dir === prefix;
78
+ }
79
+ return normalized === trimmed || normalized.startsWith(`${trimmed}/`);
80
+ });
81
+ }
82
+ function detectOutOfScopeFiles(changedFiles, session, domains) {
83
+ const meaningful = changedFiles
84
+ .map(normalizePath)
85
+ .filter((file) => file.length > 0 && !(0, local_proof_1.isProofNoiseFile)(file));
86
+ if (meaningful.length === 0)
87
+ return [];
88
+ const claimedPaths = session?.claimedPaths ?? [];
89
+ if (claimedPaths.length > 0) {
90
+ return meaningful.filter((file) => !fileMatchesClaimedPaths(file, claimedPaths));
91
+ }
92
+ const intent = session?.intent?.trim() ?? "";
93
+ if (!intent)
94
+ return [];
95
+ const keywords = intentKeywords(intent);
96
+ if (keywords.length === 0)
97
+ return [];
98
+ const outOfScope = [];
99
+ for (const file of meaningful) {
100
+ if (fileMatchesIntentKeywords(file, keywords))
101
+ continue;
102
+ if (domains.length > 0) {
103
+ const domain = (0, domain_resolution_1.resolveDomainForFile)(file, domains).domain;
104
+ if (domain && keywords.some((keyword) => domain.toLowerCase().includes(keyword))) {
105
+ continue;
106
+ }
107
+ }
108
+ outOfScope.push(file);
109
+ }
110
+ return outOfScope;
111
+ }
112
+ function renderContractVerdict(session, changedFiles, proofRun, domains = []) {
113
+ const intent = session?.intent?.trim() || "(no intent declared)";
114
+ const meaningfulFiles = changedFiles
115
+ .map(normalizePath)
116
+ .filter((file) => file.length > 0 && !(0, local_proof_1.isProofNoiseFile)(file));
117
+ const outOfScope = detectOutOfScopeFiles(meaningfulFiles, session, domains);
118
+ const missing = [];
119
+ if (!session?.intent?.trim()) {
120
+ missing.push("No contract intent was recorded.");
121
+ }
122
+ if (meaningfulFiles.length === 0) {
123
+ missing.push("No files changed since the contract baseline.");
124
+ }
125
+ if (!proofRun) {
126
+ missing.push("No proof/test run was recorded.");
127
+ }
128
+ else if (proofRun.status === "failed") {
129
+ missing.push(`Proof failed: ${proofRun.command} (exit ${proofRun.exitCode}).`);
130
+ }
131
+ let nextStep = "Contract looks complete. Commit when ready.";
132
+ if (missing.length > 0 && outOfScope.length > 0) {
133
+ nextStep =
134
+ "Review out-of-scope files, run the relevant tests, then decide whether to accept or revert the extra changes.";
135
+ }
136
+ else if (missing.some((item) => item.includes("proof"))) {
137
+ nextStep = "Run the relevant test command so AgentBridge can record proof for this contract.";
138
+ }
139
+ else if (outOfScope.length > 0) {
140
+ nextStep =
141
+ "Ask the agent to justify the out-of-scope files, or revert them before accepting the work.";
142
+ }
143
+ else if (meaningfulFiles.length === 0) {
144
+ nextStep = "No work was recorded. Start a new contract when you are ready to implement.";
145
+ }
146
+ const lines = [
147
+ "AgentBridge Contract Verdict",
148
+ "─────────────────────────────────────────",
149
+ "",
150
+ "Here's what the contract was:",
151
+ ` ${intent}`,
152
+ "",
153
+ "Here's what the agent changed:",
154
+ ];
155
+ if (meaningfulFiles.length === 0) {
156
+ lines.push(" No files changed.");
157
+ }
158
+ else {
159
+ for (const file of meaningfulFiles) {
160
+ lines.push(` - ${file}`);
161
+ }
162
+ }
163
+ lines.push("", "Here's what appears missing:");
164
+ if (missing.length === 0) {
165
+ lines.push(" Nothing obvious from this contract check.");
166
+ }
167
+ else {
168
+ for (const item of missing) {
169
+ lines.push(` - ${item}`);
170
+ }
171
+ }
172
+ lines.push("", "Here's where the work drifted out of scope:");
173
+ if (outOfScope.length === 0) {
174
+ lines.push(" No out-of-scope files detected.");
175
+ }
176
+ else {
177
+ for (const file of outOfScope) {
178
+ const reason = session?.intent?.trim()
179
+ ? `${file} was changed, but the contract was about ${session.intent.trim()}.`
180
+ : `${file} was changed outside the declared contract.`;
181
+ lines.push(` - ${reason}`);
182
+ }
183
+ }
184
+ lines.push("", "Here's the next step:", ` ${nextStep}`, "");
185
+ return lines.join("\n");
186
+ }
@@ -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
  }
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isVagueIntent = isVagueIntent;
4
+ exports.validateIntent = validateIntent;
5
+ const errors_1 = require("./errors");
6
+ const VAGUE_PHRASES = [
7
+ "working on project",
8
+ "making changes",
9
+ "updating files",
10
+ "fix stuff",
11
+ "general work",
12
+ ];
13
+ function isVagueIntent(intent) {
14
+ const lower = intent.trim().toLowerCase();
15
+ if (!lower)
16
+ return true;
17
+ if (lower.split(/\s+/).length < 4)
18
+ return true;
19
+ return VAGUE_PHRASES.some((phrase) => lower.includes(phrase));
20
+ }
21
+ function validateIntent(intent) {
22
+ const trimmed = intent.trim();
23
+ if (!trimmed) {
24
+ throw (0, errors_1.catalogCliError)("START_SUMMARY_REQUIRED", {
25
+ what: "A work contract intent is required.",
26
+ next: 'Run: agentbridge start "Fix auth 401 on /rooms endpoint"',
27
+ });
28
+ }
29
+ if (isVagueIntent(trimmed)) {
30
+ throw (0, errors_1.catalogCliError)("START_SUMMARY_REQUIRED", {
31
+ what: "Intent is too vague for a work contract.",
32
+ why: "AgentBridge needs a specific goal to compare against file changes.",
33
+ next: 'Use a concrete intent, e.g. agentbridge start "Fix auth 401 on /rooms endpoint"',
34
+ });
35
+ }
36
+ return trimmed;
37
+ }
@@ -11,10 +11,18 @@ function normalizePath(path) {
11
11
  return path.trim().replaceAll("\\", "/");
12
12
  }
13
13
  function isProofNoiseFile(file) {
14
- const normalized = normalizePath(file).toLowerCase();
15
- return (normalized === "agentbridge.md" ||
16
- normalized === ".cursor" ||
17
- normalized.startsWith(".cursor/"));
14
+ const normalized = normalizePath(file);
15
+ const lower = normalized.toLowerCase();
16
+ if (lower === "agentbridge.md" ||
17
+ lower === ".cursor" ||
18
+ lower.startsWith(".cursor/")) {
19
+ return true;
20
+ }
21
+ // Vite ephemeral config bundles (e.g. vite.config.ts.timestamp-1781442511868-*.mjs)
22
+ if (/\.timestamp-\d+-[a-z0-9]+\.mjs$/i.test(normalized)) {
23
+ return true;
24
+ }
25
+ return false;
18
26
  }
19
27
  function normalizeProofMatchingFileSet(files) {
20
28
  return [...new Set(files.map(normalizePath).filter((file) => file.length > 0 && !isProofNoiseFile(file)))].sort();