@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.
- package/dist/build-info.json +4 -4
- package/dist/commands/connect.js +58 -122
- package/dist/commands/doctor.js +46 -8
- package/dist/commands/setup-mcp.js +54 -44
- package/dist/commands/start.js +86 -22
- package/dist/commands/watch.js +662 -92
- package/dist/contract-intelligence.js +597 -0
- package/dist/contract-verdict.js +239 -0
- package/dist/diff-reader.js +200 -0
- package/dist/error-catalog.js +29 -0
- package/dist/git-status.js +6 -2
- package/dist/index.js +11 -5
- package/dist/intent-parser.js +178 -0
- package/dist/intent-validation.js +37 -0
- package/dist/local-proof.js +12 -4
- package/dist/mcp/agentbridge-mcp.js +1174 -37
- package/dist/mcp/agentbridge-mcp.js.map +4 -4
- package/dist/mcp-config.js +64 -0
- package/dist/proof-parser.js +118 -0
- package/dist/session-state.js +15 -0
- package/dist/session.js +10 -0
- package/dist/supervision.js +191 -48
- package/dist/test-runner.js +201 -15
- package/package.json +1 -1
|
@@ -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
|
+
}
|
package/dist/error-catalog.js
CHANGED
|
@@ -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",
|
package/dist/git-status.js
CHANGED
|
@@ -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 &&
|
|
51
|
+
if (entry.path && isSupervisionRelevantPath(entry.path)) {
|
|
48
52
|
files.add(entry.path);
|
|
49
53
|
}
|
|
50
|
-
if (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
|
-
"
|
|
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)({
|
|
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
|
}
|