@agentbridge1/cli 0.0.8 → 0.0.10

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.
@@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.detectOutOfScopeFiles = detectOutOfScopeFiles;
4
4
  exports.renderContractVerdict = renderContractVerdict;
5
5
  const domain_resolution_1 = require("./domain-resolution");
6
+ const contract_intelligence_1 = require("./contract-intelligence");
7
+ const diff_reader_1 = require("./diff-reader");
6
8
  const local_proof_1 = require("./local-proof");
7
9
  const INTENT_STOP_WORDS = new Set([
8
10
  "a",
@@ -115,34 +117,39 @@ function renderContractVerdict(session, changedFiles, proofRun, domains = []) {
115
117
  .map(normalizePath)
116
118
  .filter((file) => file.length > 0 && !(0, local_proof_1.isProofNoiseFile)(file));
117
119
  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"))) {
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) {
137
142
  nextStep = "Run the relevant test command so AgentBridge can record proof for this contract.";
138
143
  }
139
144
  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.";
145
+ nextStep = "Ask the agent to justify the out-of-scope files, or revert them before accepting the work.";
142
146
  }
143
147
  else if (meaningfulFiles.length === 0) {
144
148
  nextStep = "No work was recorded. Start a new contract when you are ready to implement.";
145
149
  }
150
+ else {
151
+ nextStep = "Contract looks complete. Commit when ready.";
152
+ }
146
153
  const lines = [
147
154
  "AgentBridge Contract Verdict",
148
155
  "─────────────────────────────────────────",
@@ -160,13 +167,59 @@ function renderContractVerdict(session, changedFiles, proofRun, domains = []) {
160
167
  lines.push(` - ${file}`);
161
168
  }
162
169
  }
163
- lines.push("", "Here's what appears missing:");
164
- if (missing.length === 0) {
165
- lines.push(" Nothing obvious from this contract check.");
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.");
166
218
  }
167
219
  else {
168
- for (const item of missing) {
169
- lines.push(` - ${item}`);
220
+ for (const e of stillUnproven) {
221
+ lines.push(` - ${e.criterion}`);
222
+ lines.push(` ${e.evidence}`);
170
223
  }
171
224
  }
172
225
  lines.push("", "Here's where the work drifted out of scope:");
@@ -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
+ }
@@ -13,7 +13,10 @@ function normalizePath(path) {
13
13
  }
14
14
  function isAgentbridgeLocalPath(path) {
15
15
  const normalized = normalizePath(path);
16
- return normalized === ".agentbridge" || normalized.startsWith(".agentbridge/");
16
+ const lower = normalized.toLowerCase();
17
+ if (lower === ".agentbridge" || lower.endsWith("/.agentbridge"))
18
+ return true;
19
+ return lower.includes(".agentbridge/");
17
20
  }
18
21
  function isRenameOrCopy(xy) {
19
22
  return xy.includes("R") || xy.includes("C");
@@ -0,0 +1,178 @@
1
+ "use strict";
2
+ /**
3
+ * Intent parser — extracts structured task specification from a raw intent string.
4
+ * All logic is deterministic: no LLM, no external calls.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.parseIntent = parseIntent;
8
+ exports.buildTaskSpecificCriteria = buildTaskSpecificCriteria;
9
+ const ACTIONS = ["fix", "add", "update", "refactor", "remove", "migrate", "rename", "revert", "delete", "implement", "improve", "patch"];
10
+ const DOMAIN_KEYWORD_MAP = {
11
+ auth: ["auth", "login", "logout", "session", "token", "oauth", "jwt", "password", "signin", "signup", "sign-in", "sign-up", "register", "credential", "permission", "role"],
12
+ database: ["database", "db", "schema", "migration", "migrate", "prisma", "sql", "query", "table", "column", "index", "model", "seed", "orm"],
13
+ payments: ["payment", "payments", "billing", "stripe", "invoice", "subscription", "checkout", "webhook"],
14
+ api: ["api", "endpoint", "route", "handler", "controller", "request", "response", "rest", "graphql"],
15
+ ui: ["ui", "style", "css", "component", "page", "layout", "copy", "design", "render", "display", "view", "frontend", "html", "template"],
16
+ mcp: ["mcp", "agentbridge", "cursor", "rules"],
17
+ tests: ["test", "tests", "spec", "vitest", "jest", "coverage", "proof"],
18
+ };
19
+ const SYMPTOMS = {
20
+ "401": ["401", "unauthorized", "unauthenticated"],
21
+ "403": ["403", "forbidden"],
22
+ "404": ["404", "not found", "notfound"],
23
+ "500": ["500", "internal server error", "server error"],
24
+ "null": ["null", "undefined", "nan", "nil"],
25
+ "crash": ["crash", "crashes", "crashed", "exception", "throws", "throw"],
26
+ "loop": ["loop", "infinite loop", "recursion"],
27
+ "hang": ["hang", "hangs", "timeout", "deadlock"],
28
+ "slow": ["slow", "performance", "latency", "memory leak", "memory"],
29
+ "duplicate": ["duplicate", "duplicated", "twice"],
30
+ "missing": ["missing", "not showing", "not found"],
31
+ };
32
+ // Route pattern: /something or /something/else (no file extension)
33
+ const ROUTE_RE = /(?<!\w)(\/[a-zA-Z0-9_-]+(?:\/[a-zA-Z0-9_:_-]+)*)/g;
34
+ // File with extension: something.ts, SomeThing.tsx, etc.
35
+ const FILE_RE = /\b([A-Za-z][A-Za-z0-9_-]*\.[a-z]{2,4})\b/g;
36
+ // PascalCase class or model name (≥2 caps or starts with uppercase followed by lowercase+uppercase)
37
+ const PASCAL_RE = /\b([A-Z][a-z]+(?:[A-Z][a-z]*)+)\b/g;
38
+ // camelCase function name — at least one internal uppercase letter, no spaces
39
+ const CAMEL_FN_RE = /\b([a-z][a-z0-9]*(?:[A-Z][a-z0-9]*)+)\b/g;
40
+ // Quoted phrase
41
+ const QUOTED_RE = /"([^"]+)"|'([^']+)'/g;
42
+ function detectDomain(lower) {
43
+ for (const [domain, keywords] of Object.entries(DOMAIN_KEYWORD_MAP)) {
44
+ if (keywords.some((kw) => lower.includes(kw)))
45
+ return domain;
46
+ }
47
+ return null;
48
+ }
49
+ function detectAction(lower) {
50
+ for (const action of ACTIONS) {
51
+ if (lower.startsWith(action) || lower.includes(` ${action} `))
52
+ return action;
53
+ }
54
+ return null;
55
+ }
56
+ function detectSymptom(lower) {
57
+ for (const [symptom, patterns] of Object.entries(SYMPTOMS)) {
58
+ if (patterns.some((p) => lower.includes(p)))
59
+ return symptom;
60
+ }
61
+ return null;
62
+ }
63
+ function extractTargets(raw) {
64
+ const targets = new Set();
65
+ // Quoted phrases first
66
+ for (const m of raw.matchAll(QUOTED_RE)) {
67
+ const phrase = m[1] ?? m[2];
68
+ if (phrase)
69
+ targets.add(phrase.trim());
70
+ }
71
+ // API routes
72
+ for (const m of raw.matchAll(ROUTE_RE)) {
73
+ targets.add(m[1]);
74
+ }
75
+ // Files with extensions
76
+ for (const m of raw.matchAll(FILE_RE)) {
77
+ targets.add(m[1]);
78
+ }
79
+ // PascalCase (models, classes)
80
+ for (const m of raw.matchAll(PASCAL_RE)) {
81
+ targets.add(m[1]);
82
+ }
83
+ // camelCase function names — only when short enough to be a name, not prose
84
+ for (const m of raw.matchAll(CAMEL_FN_RE)) {
85
+ if (m[1].length <= 40)
86
+ targets.add(m[1]);
87
+ }
88
+ return [...targets];
89
+ }
90
+ function inferLayers(domain, targets, symptom, action, rawLower) {
91
+ const layers = [];
92
+ const hasRoute = targets.some((t) => t.startsWith("/"));
93
+ const hasMigration = action === "migrate" || targets.some((t) => t.toLowerCase().includes("migration"));
94
+ if (domain === "auth") {
95
+ if (symptom === "401" || symptom === "403")
96
+ layers.push("route_handler", "auth_middleware");
97
+ else if (hasRoute)
98
+ layers.push("route_handler", "auth_middleware");
99
+ else
100
+ layers.push("auth_middleware");
101
+ }
102
+ if (domain === "api") {
103
+ if (hasRoute)
104
+ layers.push("route_handler");
105
+ else
106
+ layers.push("route_handler");
107
+ }
108
+ if (domain === "database") {
109
+ if (hasMigration)
110
+ layers.push("schema", "migration_file");
111
+ else
112
+ layers.push("model", "query");
113
+ }
114
+ if (domain === "ui") {
115
+ layers.push("component", "view");
116
+ }
117
+ if (domain === "payments") {
118
+ if (targets.some((t) => t.toLowerCase().includes("webhook")) || rawLower.includes("webhook")) {
119
+ layers.push("webhook_handler");
120
+ }
121
+ else {
122
+ layers.push("billing_handler");
123
+ }
124
+ }
125
+ if (domain === "mcp") {
126
+ layers.push("mcp_server", "config");
127
+ }
128
+ return layers;
129
+ }
130
+ function parseIntent(raw) {
131
+ if (!raw?.trim()) {
132
+ return { raw: raw ?? "", domain: null, targets: [], symptom: null, action: null, affected_layer: [] };
133
+ }
134
+ const lower = raw.toLowerCase();
135
+ const domain = detectDomain(lower);
136
+ const action = detectAction(lower);
137
+ const symptom = detectSymptom(lower);
138
+ const targets = extractTargets(raw);
139
+ const affected_layer = inferLayers(domain, targets, symptom, action, lower);
140
+ return { raw, domain, targets, symptom, action, affected_layer };
141
+ }
142
+ /**
143
+ * Builds task-specific criteria to prepend/append to a profile's generic criteria.
144
+ * Returns at most 2 sentences so the checklist stays readable.
145
+ */
146
+ function buildTaskSpecificCriteria(parsed) {
147
+ const extra = [];
148
+ const routeTargets = parsed.targets.filter((t) => t.startsWith("/"));
149
+ const fileTargets = parsed.targets.filter((t) => t.includes("."));
150
+ const namedTargets = parsed.targets.filter((t) => !t.startsWith("/") && !t.includes("."));
151
+ // Task-specific entry criterion (what to look at)
152
+ if (routeTargets.length > 0 && parsed.domain === "auth" && parsed.symptom) {
153
+ extra.push(`The ${routeTargets[0]} route handler or its auth middleware chain is updated to address the ${parsed.symptom}.`);
154
+ }
155
+ else if (routeTargets.length > 0 && parsed.domain === "api") {
156
+ extra.push(`The ${routeTargets[0]} endpoint handler is updated as intended.`);
157
+ }
158
+ else if (routeTargets.length > 0) {
159
+ extra.push(`Changes are focused on the ${routeTargets[0]} route.`);
160
+ }
161
+ else if (fileTargets.length > 0) {
162
+ extra.push(`Changes are focused on ${fileTargets[0]}.`);
163
+ }
164
+ else if (namedTargets.length > 0) {
165
+ extra.push(`Changes are focused on ${namedTargets[0]}.`);
166
+ }
167
+ // Task-specific proof criterion (what to verify)
168
+ if (parsed.symptom && routeTargets.length > 0) {
169
+ extra.push(`Proof exists for the ${parsed.symptom} behavior on ${routeTargets[0]}.`);
170
+ }
171
+ else if (parsed.symptom) {
172
+ extra.push(`Proof exists that the ${parsed.symptom} condition is resolved.`);
173
+ }
174
+ else if (parsed.action && parsed.domain) {
175
+ extra.push(`Proof exists that the ${parsed.action} to the ${parsed.domain} layer is correct.`);
176
+ }
177
+ return extra;
178
+ }
@@ -13,6 +13,11 @@ function normalizePath(path) {
13
13
  function isProofNoiseFile(file) {
14
14
  const normalized = normalizePath(file);
15
15
  const lower = normalized.toLowerCase();
16
+ if (lower === ".agentbridge" ||
17
+ lower.endsWith("/.agentbridge") ||
18
+ lower.includes(".agentbridge/")) {
19
+ return true;
20
+ }
16
21
  if (lower === "agentbridge.md" ||
17
22
  lower === ".cursor" ||
18
23
  lower.startsWith(".cursor/")) {