@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,597 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildContractIntelligence = buildContractIntelligence;
4
+ exports.mapFilesToExpectedSurfaces = mapFilesToExpectedSurfaces;
5
+ exports.evaluateCompletionCriteria = evaluateCompletionCriteria;
6
+ exports.nextStepFromCriteria = nextStepFromCriteria;
7
+ exports.renderLiveExpectationSummary = renderLiveExpectationSummary;
8
+ exports.localSessionToContractIntelligence = localSessionToContractIntelligence;
9
+ exports.sessionNeedsContractBackfill = sessionNeedsContractBackfill;
10
+ exports.ensureSessionContractIntelligence = ensureSessionContractIntelligence;
11
+ exports.buildProofRequiredCommands = buildProofRequiredCommands;
12
+ exports.buildLocalHelloPayload = buildLocalHelloPayload;
13
+ exports.renderLiveContractBanner = renderLiveContractBanner;
14
+ const intent_parser_1 = require("./intent-parser");
15
+ const proof_parser_1 = require("./proof-parser");
16
+ const diff_reader_1 = require("./diff-reader");
17
+ const session_state_1 = require("./session-state");
18
+ const MCP_RULE_PROFILE = "mcp_rules_config_install";
19
+ const AUTH_PROFILE = "auth_login_session";
20
+ const DB_PROFILE = "database_schema_migration";
21
+ const UI_PROFILE = "ui_style_copy";
22
+ const API_PROFILE = "api_endpoint";
23
+ const TEST_PROFILE = "tests";
24
+ const PAYMENTS_PROFILE = "payments_webhooks";
25
+ const GENERIC_PROFILE = "generic";
26
+ function unique(values) {
27
+ return [...new Set(values.map((value) => value.trim()).filter(Boolean))];
28
+ }
29
+ function matchIntent(intent, keywords) {
30
+ const lower = intent.toLowerCase();
31
+ return keywords.some((keyword) => lower.includes(keyword));
32
+ }
33
+ function detectProfiles(intent) {
34
+ const profiles = [];
35
+ if (matchIntent(intent, ["mcp"]) &&
36
+ matchIntent(intent, ["rule", "rules", "install", "setup", "config", "configuration"])) {
37
+ profiles.push(MCP_RULE_PROFILE);
38
+ }
39
+ if (matchIntent(intent, ["auth", "login", "session", "token", "oauth"])) {
40
+ profiles.push(AUTH_PROFILE);
41
+ }
42
+ if (matchIntent(intent, ["database", "db", "schema", "migration", "prisma", "sql"])) {
43
+ profiles.push(DB_PROFILE);
44
+ }
45
+ if (matchIntent(intent, ["ui", "style", "css", "copy", "layout", "component"])) {
46
+ profiles.push(UI_PROFILE);
47
+ }
48
+ if (matchIntent(intent, ["api", "endpoint", "route", "handler", "controller"])) {
49
+ profiles.push(API_PROFILE);
50
+ }
51
+ if (matchIntent(intent, ["test", "tests", "vitest", "jest", "proof"])) {
52
+ profiles.push(TEST_PROFILE);
53
+ }
54
+ if (matchIntent(intent, ["payment", "payments", "billing", "stripe", "webhook"])) {
55
+ profiles.push(PAYMENTS_PROFILE);
56
+ }
57
+ return profiles.length > 0 ? profiles : [GENERIC_PROFILE];
58
+ }
59
+ function profileBlueprint(profile) {
60
+ switch (profile) {
61
+ case MCP_RULE_PROFILE:
62
+ return {
63
+ expectedSurfaces: ["mcp", "rules_installation", "configuration_flow", "tests_or_manual_proof"],
64
+ expectedFileAreas: [
65
+ "mcp/**",
66
+ "mcp/agentbridge-mcp.ts",
67
+ "AGENTBRIDGE.md",
68
+ ".cursor/rules/**",
69
+ "cli/src/mcp-config.ts",
70
+ "cli/src/commands/setup-mcp.ts",
71
+ ],
72
+ completionCriteria: [
73
+ "MCP configuration path triggers AgentBridge rule installation.",
74
+ "Rules are created or updated in the expected Cursor rules location.",
75
+ "Existing rules/config are preserved or safely updated.",
76
+ "Re-running MCP configuration is idempotent and does not duplicate rules.",
77
+ "Proof exists that MCP configuration causes rules to be installed.",
78
+ ],
79
+ proofNeeded: [
80
+ "Run MCP setup/configuration flow in a clean repo.",
81
+ "Confirm the AgentBridge Cursor rules file is created.",
82
+ "Run setup/configuration again to confirm idempotency.",
83
+ "Record test/manual proof output.",
84
+ ],
85
+ riskyOmissions: [
86
+ "MCP server file changed but no install trigger wired to the configuration path.",
87
+ "Rules are installed only in local mode or only in server mode.",
88
+ "Existing Cursor rules are overwritten.",
89
+ "No idempotency check.",
90
+ "No proof that the auto-install behavior actually runs.",
91
+ ],
92
+ contractProfiles: [MCP_RULE_PROFILE],
93
+ };
94
+ case AUTH_PROFILE:
95
+ return {
96
+ expectedSurfaces: ["auth", "session", "tests_or_manual_proof"],
97
+ expectedFileAreas: ["auth/**", "src/**/auth/**", "src/**/session/**", "middleware/**"],
98
+ completionCriteria: [
99
+ "Authentication/session flow handling is updated where intended.",
100
+ "Authorization boundaries remain explicit and unchanged outside scope.",
101
+ "Proof exists for the auth/session behavior change.",
102
+ ],
103
+ proofNeeded: ["Run auth/session flow tests or a reproducible manual proof."],
104
+ riskyOmissions: [
105
+ "Auth logic changed without session or middleware checks.",
106
+ "No proof of login/session behavior.",
107
+ ],
108
+ contractProfiles: [AUTH_PROFILE],
109
+ };
110
+ case DB_PROFILE:
111
+ return {
112
+ expectedSurfaces: ["database", "schema_migration", "tests_or_manual_proof"],
113
+ expectedFileAreas: ["prisma/**", "migrations/**", "src/**/db/**", "src/**/database/**"],
114
+ completionCriteria: [
115
+ "Schema/data-layer changes are applied in intended files.",
116
+ "Migration or compatibility impact is accounted for.",
117
+ "Proof exists for migration/query behavior.",
118
+ ],
119
+ proofNeeded: ["Run migration/database verification command and capture output."],
120
+ riskyOmissions: [
121
+ "Schema changed without migration/compatibility proof.",
122
+ "No evidence of migration/query validation.",
123
+ ],
124
+ contractProfiles: [DB_PROFILE],
125
+ };
126
+ case UI_PROFILE:
127
+ return {
128
+ expectedSurfaces: ["ui", "copy_or_style", "tests_or_manual_proof"],
129
+ expectedFileAreas: ["src/**/components/**", "src/**/pages/**", "src/**/*.css", "src/**/*.html"],
130
+ completionCriteria: [
131
+ "UI/copy/style surface for the contract is updated.",
132
+ "Unrelated UI areas are not changed unintentionally.",
133
+ "Proof exists for the visible behavior change.",
134
+ ],
135
+ proofNeeded: ["Record manual verification steps (or visual/tests) for UI outcome."],
136
+ riskyOmissions: [
137
+ "UI files changed without a clear visible proof.",
138
+ "Broad style changes without scope evidence.",
139
+ ],
140
+ contractProfiles: [UI_PROFILE],
141
+ };
142
+ case API_PROFILE:
143
+ return {
144
+ expectedSurfaces: ["api", "endpoint_logic", "tests_or_manual_proof"],
145
+ expectedFileAreas: ["src/**/routes/**", "src/**/api/**", "src/**/controllers/**"],
146
+ completionCriteria: [
147
+ "Target API/endpoint behavior is changed in the correct surface.",
148
+ "Request/response behavior is evidenced.",
149
+ "Proof exists for endpoint behavior.",
150
+ ],
151
+ proofNeeded: ["Run endpoint tests or reproducible request/response proof."],
152
+ riskyOmissions: [
153
+ "Endpoint files changed without request/response evidence.",
154
+ "No proof for API behavior change.",
155
+ ],
156
+ contractProfiles: [API_PROFILE],
157
+ };
158
+ case TEST_PROFILE:
159
+ return {
160
+ expectedSurfaces: ["tests", "proof"],
161
+ expectedFileAreas: ["**/*.test.ts", "**/*.spec.ts", "tests/**"],
162
+ completionCriteria: [
163
+ "Test/proof artifacts relevant to the contract are updated.",
164
+ "Test execution evidence is recorded.",
165
+ ],
166
+ proofNeeded: ["Run tests and record command + result output."],
167
+ riskyOmissions: ["Tests changed but no execution proof recorded."],
168
+ contractProfiles: [TEST_PROFILE],
169
+ };
170
+ case PAYMENTS_PROFILE:
171
+ return {
172
+ expectedSurfaces: ["payments", "webhooks", "tests_or_manual_proof"],
173
+ expectedFileAreas: ["src/**/billing/**", "src/**/payments/**", "src/**/webhooks/**"],
174
+ completionCriteria: [
175
+ "Payments/webhook handling logic is updated in scope.",
176
+ "Critical payment side effects are evidenced.",
177
+ "Proof exists for payment/webhook behavior.",
178
+ ],
179
+ proofNeeded: ["Run payment/webhook verification flow and capture output."],
180
+ riskyOmissions: [
181
+ "Webhook/payment logic changed without behavior proof.",
182
+ "No idempotency/effect validation for financial flows.",
183
+ ],
184
+ contractProfiles: [PAYMENTS_PROFILE],
185
+ };
186
+ default:
187
+ return {
188
+ expectedSurfaces: ["implementation_surface", "tests_or_manual_proof"],
189
+ expectedFileAreas: ["**"],
190
+ completionCriteria: [
191
+ "Files changed align with the declared contract intent.",
192
+ "Proof exists for the intended behavior change.",
193
+ ],
194
+ proofNeeded: ["Run relevant tests or provide manual proof output."],
195
+ riskyOmissions: ["Code changed without proof linked to the declared intent."],
196
+ contractProfiles: [GENERIC_PROFILE],
197
+ };
198
+ }
199
+ }
200
+ function buildContractIntelligence(intent, createdBy) {
201
+ const normalizedIntent = intent?.trim() ?? "";
202
+ const profiles = detectProfiles(normalizedIntent);
203
+ const combined = profiles.map((profile) => profileBlueprint(profile));
204
+ const baseCompletionCriteria = unique(combined.flatMap((item) => item.completionCriteria));
205
+ // Inject task-specific criteria derived from the intent string
206
+ const parsed = (0, intent_parser_1.parseIntent)(normalizedIntent);
207
+ const taskCriteria = (0, intent_parser_1.buildTaskSpecificCriteria)(parsed);
208
+ // Prepend the first task-specific criterion (focused target), append the last (proof)
209
+ const firstTask = taskCriteria[0];
210
+ const lastTask = taskCriteria[1];
211
+ const completionCriteria = unique([
212
+ ...(firstTask ? [firstTask] : []),
213
+ ...baseCompletionCriteria.filter((c) => !c.toLowerCase().startsWith("proof")),
214
+ ...(lastTask ? [lastTask] : []),
215
+ ...baseCompletionCriteria.filter((c) => c.toLowerCase().startsWith("proof")),
216
+ ]);
217
+ return {
218
+ expectedSurfaces: unique(combined.flatMap((item) => item.expectedSurfaces)),
219
+ expectedFileAreas: unique(combined.flatMap((item) => item.expectedFileAreas)),
220
+ completionCriteria,
221
+ proofNeeded: unique(combined.flatMap((item) => item.proofNeeded)),
222
+ riskyOmissions: unique(combined.flatMap((item) => item.riskyOmissions)),
223
+ contractProfiles: unique(combined.flatMap((item) => item.contractProfiles)),
224
+ createdBy,
225
+ };
226
+ }
227
+ function fileMatchesArea(file, area) {
228
+ const normalizedFile = file.replaceAll("\\", "/");
229
+ const normalizedArea = area.replaceAll("\\", "/").trim();
230
+ if (!normalizedArea || normalizedArea === "**")
231
+ return true;
232
+ if (normalizedArea.endsWith("/**")) {
233
+ const prefix = normalizedArea.slice(0, -3);
234
+ return normalizedFile === prefix || normalizedFile.startsWith(`${prefix}/`);
235
+ }
236
+ // Handle **/*.ext — match any file whose name ends with the given suffix.
237
+ if (normalizedArea.startsWith("**/")) {
238
+ const suffix = normalizedArea.slice(3); // e.g. "*.test.ts"
239
+ if (suffix.startsWith("*")) {
240
+ return normalizedFile.endsWith(suffix.slice(1)); // e.g. ".test.ts"
241
+ }
242
+ const parts = normalizedFile.split("/");
243
+ return parts[parts.length - 1] === suffix;
244
+ }
245
+ if (normalizedArea.includes("*")) {
246
+ const compact = normalizedArea.replaceAll("*", "");
247
+ return compact.length === 0 || normalizedFile.includes(compact);
248
+ }
249
+ return normalizedFile === normalizedArea || normalizedFile.startsWith(`${normalizedArea}/`);
250
+ }
251
+ function mapFilesToExpectedSurfaces(changedFiles, expectedSurfaces) {
252
+ return changedFiles.map((file) => {
253
+ const lower = file.toLowerCase();
254
+ const mapped = [];
255
+ for (const surface of expectedSurfaces) {
256
+ if (surface === "mcp" && lower.includes("mcp"))
257
+ mapped.push(surface);
258
+ else if (surface === "rules_installation" && (lower.includes(".cursor/rules") || lower.endsWith("agentbridge.md")))
259
+ mapped.push(surface);
260
+ else if (surface === "configuration_flow" && (lower.includes("setup-mcp") || lower.includes("mcp-config") || lower.includes("config")))
261
+ mapped.push(surface);
262
+ else if (surface === "tests_or_manual_proof" && (lower.includes(".test.") || lower.includes(".spec.") || lower.includes("tests/")))
263
+ mapped.push(surface);
264
+ else if (surface === "auth" && lower.includes("auth"))
265
+ mapped.push(surface);
266
+ else if (surface === "session" && lower.includes("session"))
267
+ mapped.push(surface);
268
+ else if (surface === "database" && (lower.includes("prisma") || lower.includes("migration") || lower.includes("schema") || lower.includes("sql")))
269
+ mapped.push(surface);
270
+ else if (surface === "schema_migration" && (lower.includes("migration") || lower.includes("schema")))
271
+ mapped.push(surface);
272
+ else if (surface === "ui" && (lower.includes("component") || lower.includes("page") || lower.endsWith(".css") || lower.endsWith(".html")))
273
+ mapped.push(surface);
274
+ else if (surface === "api" && (lower.includes("route") || lower.includes("api") || lower.includes("controller")))
275
+ mapped.push(surface);
276
+ else if (surface === "endpoint_logic" && (lower.includes("route") || lower.includes("handler")))
277
+ mapped.push(surface);
278
+ else if (surface === "tests" && (lower.includes(".test.") || lower.includes(".spec.") || lower.includes("tests/")))
279
+ mapped.push(surface);
280
+ else if (surface === "payments" && (lower.includes("payment") || lower.includes("billing") || lower.includes("stripe")))
281
+ mapped.push(surface);
282
+ else if (surface === "webhooks" && lower.includes("webhook"))
283
+ mapped.push(surface);
284
+ else if (surface === "implementation_surface" && mapped.length === 0)
285
+ mapped.push(surface);
286
+ }
287
+ return { file, surfaces: mapped };
288
+ });
289
+ }
290
+ function criteriaStatusForMcpRules(criterion, changedFiles, proofRun, fileToSurface, parsedProof, diff) {
291
+ const coveredSurfaces = new Set(fileToSurface.flatMap((item) => item.surfaces));
292
+ if (criterion.startsWith("MCP configuration path triggers AgentBridge rule installation")) {
293
+ if (proofRun?.status === "passed") {
294
+ return {
295
+ criterion,
296
+ status: "evidence_found",
297
+ evidence: `Proof run passed (${proofRun.command}).`,
298
+ };
299
+ }
300
+ if (coveredSurfaces.has("mcp") && coveredSurfaces.has("configuration_flow")) {
301
+ return {
302
+ criterion,
303
+ status: "partial_evidence",
304
+ evidence: "Changed files touch MCP and configuration flow surfaces. Run agentbridge verify to record behavioral proof.",
305
+ };
306
+ }
307
+ if (coveredSurfaces.has("mcp") || coveredSurfaces.has("configuration_flow")) {
308
+ return {
309
+ criterion,
310
+ status: "partial_evidence",
311
+ evidence: "Only part of the expected MCP/configuration surfaces changed.",
312
+ };
313
+ }
314
+ return {
315
+ criterion,
316
+ status: "no_evidence",
317
+ evidence: "No MCP/configuration surface evidence found in changed files.",
318
+ };
319
+ }
320
+ if (criterion.startsWith("Rules are created or updated in the expected Cursor rules location")) {
321
+ const touchedRules = changedFiles.some((file) => file.endsWith("AGENTBRIDGE.md") || file.includes(".cursor/rules/"));
322
+ if (touchedRules) {
323
+ return {
324
+ criterion,
325
+ status: "partial_evidence",
326
+ evidence: "Rules file paths were directly changed. Run agentbridge verify to confirm created/updated behavior.",
327
+ };
328
+ }
329
+ return {
330
+ criterion,
331
+ status: "no_evidence",
332
+ evidence: "No direct evidence that AGENTBRIDGE.md or .cursor/rules files were created/updated.",
333
+ };
334
+ }
335
+ if (criterion.startsWith("Existing rules/config are preserved or safely updated")) {
336
+ if (proofRun?.status === "passed") {
337
+ return {
338
+ criterion,
339
+ status: "partial_evidence",
340
+ evidence: "Proof ran, but preservation safety needs explicit idempotency evidence.",
341
+ };
342
+ }
343
+ return {
344
+ criterion,
345
+ status: "cannot_verify",
346
+ evidence: "Cannot infer config preservation from file diff alone.",
347
+ };
348
+ }
349
+ if (criterion.startsWith("Re-running MCP configuration is idempotent")) {
350
+ const proofText = `${proofRun?.command ?? ""} ${proofRun?.stdoutExcerpt ?? ""}`.toLowerCase();
351
+ if (proofRun?.status === "passed" &&
352
+ (proofText.includes("idempotent") ||
353
+ proofText.includes("second run") ||
354
+ proofText.includes("re-run") ||
355
+ proofText.includes("again"))) {
356
+ return {
357
+ criterion,
358
+ status: "evidence_found",
359
+ evidence: "Proof output indicates repeated setup/idempotency behavior.",
360
+ };
361
+ }
362
+ if (proofRun?.status === "passed") {
363
+ return {
364
+ criterion,
365
+ status: "partial_evidence",
366
+ evidence: "Proof exists but does not explicitly evidence second-run/idempotency behavior.",
367
+ };
368
+ }
369
+ // Diff-based: guard clauses in MCP files are a structural signal for idempotency
370
+ const mcpGuards = diff?.files
371
+ .filter((f) => f.file.toLowerCase().includes("mcp"))
372
+ .reduce((s, f) => s + f.guard_clauses_added, 0) ?? 0;
373
+ if (mcpGuards > 0) {
374
+ return {
375
+ criterion,
376
+ status: "partial_evidence",
377
+ evidence: `${mcpGuards} guard clause(s) added in MCP files — likely idempotency checks. Run agentbridge verify to confirm.`,
378
+ };
379
+ }
380
+ return {
381
+ criterion,
382
+ status: "no_evidence",
383
+ evidence: "No idempotency evidence was recorded.",
384
+ };
385
+ }
386
+ if (criterion.startsWith("Proof exists that MCP configuration causes rules to be installed")) {
387
+ if (proofRun?.status === "passed") {
388
+ return {
389
+ criterion,
390
+ status: "evidence_found",
391
+ evidence: parsedProof
392
+ ? (0, proof_parser_1.proofEvidenceString)(parsedProof, "mcp")
393
+ : `Proof run passed (${proofRun.command}).`,
394
+ };
395
+ }
396
+ if (proofRun?.status === "failed") {
397
+ return {
398
+ criterion,
399
+ status: "no_evidence",
400
+ evidence: `Proof run failed (${proofRun.command}, exit ${proofRun.exitCode}).`,
401
+ };
402
+ }
403
+ return {
404
+ criterion,
405
+ status: "no_evidence",
406
+ evidence: "No proof run recorded.",
407
+ };
408
+ }
409
+ return {
410
+ criterion,
411
+ status: "cannot_verify",
412
+ evidence: "No deterministic rule available for this criterion.",
413
+ };
414
+ }
415
+ function evaluateCompletionCriteria(params) {
416
+ const fileToSurface = mapFilesToExpectedSurfaces(params.changedFiles, params.contract.expectedSurfaces);
417
+ const parsedProof = params.proofRun ? (0, proof_parser_1.parseProofOutput)(params.proofRun) : null;
418
+ const diff = params.diff ?? null;
419
+ return params.contract.completionCriteria.map((criterion) => {
420
+ if (params.contract.contractProfiles.includes(MCP_RULE_PROFILE)) {
421
+ return criteriaStatusForMcpRules(criterion, params.changedFiles, params.proofRun, fileToSurface, parsedProof, diff);
422
+ }
423
+ if (criterion.toLowerCase().includes("proof")) {
424
+ if (params.proofRun?.status === "passed" && parsedProof) {
425
+ const domain = params.contract.contractProfiles[0]?.split("_")[0] ?? null;
426
+ const hasCoverage = domain ? parsedProof.has_domain_coverage(domain) : true;
427
+ if (hasCoverage) {
428
+ return {
429
+ criterion,
430
+ status: "evidence_found",
431
+ evidence: (0, proof_parser_1.proofEvidenceString)(parsedProof, domain ?? undefined),
432
+ };
433
+ }
434
+ return {
435
+ criterion,
436
+ status: "partial_evidence",
437
+ evidence: (0, proof_parser_1.proofEvidenceString)(parsedProof, domain ?? undefined),
438
+ };
439
+ }
440
+ if (params.proofRun?.status === "failed") {
441
+ return {
442
+ criterion,
443
+ status: "no_evidence",
444
+ evidence: `Proof run failed (${params.proofRun.command}, exit ${params.proofRun.exitCode}).`,
445
+ };
446
+ }
447
+ return {
448
+ criterion,
449
+ status: "no_evidence",
450
+ evidence: "No proof run recorded.",
451
+ };
452
+ }
453
+ // Diff-aware evaluation: check for guard clauses, new exports, or specific symbol changes
454
+ if (diff && diff.files.length > 0) {
455
+ const totalGuards = diff.files.reduce((s, f) => s + f.guard_clauses_added, 0);
456
+ const allExportsAdded = diff.files.flatMap((f) => f.exports_added.concat(f.functions_added));
457
+ const diffSummary = (0, diff_reader_1.summariseDiff)(diff);
458
+ if (criterion.toLowerCase().includes("idempotent") && totalGuards > 0) {
459
+ return {
460
+ criterion,
461
+ status: "partial_evidence",
462
+ evidence: `${totalGuards} guard clause(s) added in changed files — possible idempotency checks. Run agentbridge verify to confirm. (${diffSummary})`,
463
+ };
464
+ }
465
+ if (criterion.toLowerCase().includes("boundar") && allExportsAdded.length > 0) {
466
+ return {
467
+ criterion,
468
+ status: "partial_evidence",
469
+ evidence: `New symbol(s) exported: ${allExportsAdded.slice(0, 3).join(", ")}. Confirm callers stay within the declared domain. (${diffSummary})`,
470
+ };
471
+ }
472
+ // Generic diff-grounded partial evidence
473
+ const hasAlignedFile = params.changedFiles.some((file) => params.contract.expectedFileAreas.some((area) => fileMatchesArea(file, area)));
474
+ if (hasAlignedFile && diffSummary) {
475
+ return {
476
+ criterion,
477
+ status: "partial_evidence",
478
+ evidence: `File changes detected: ${diffSummary}. Criterion not fully proven.`,
479
+ };
480
+ }
481
+ }
482
+ const hasAlignedFile = params.changedFiles.some((file) => params.contract.expectedFileAreas.some((area) => fileMatchesArea(file, area)));
483
+ if (hasAlignedFile) {
484
+ return {
485
+ criterion,
486
+ status: "partial_evidence",
487
+ evidence: "Changed files align with expected areas, but criterion is not fully proven.",
488
+ };
489
+ }
490
+ return {
491
+ criterion,
492
+ status: "no_evidence",
493
+ evidence: "No aligned changed-file evidence detected.",
494
+ };
495
+ });
496
+ }
497
+ function nextStepFromCriteria(evaluations, proofNeeded) {
498
+ const missing = evaluations.filter((item) => item.status === "no_evidence");
499
+ if (missing.length === 0) {
500
+ return "All tracked criteria are evidenced. Confirm final proof and commit when ready.";
501
+ }
502
+ // Prioritise idempotency if it is anywhere in the unproven list
503
+ const idempotency = missing.find((item) => item.criterion.toLowerCase().includes("idempotent"));
504
+ if (idempotency) {
505
+ return "Run setup/configuration twice in a clean repo and record output proving idempotency.";
506
+ }
507
+ const firstMissing = missing[0];
508
+ if (firstMissing && firstMissing.criterion.toLowerCase().includes("proof")) {
509
+ return proofNeeded[0] ?? "Run a relevant test/manual proof command and record output.";
510
+ }
511
+ return `${firstMissing?.criterion ?? "Missing evidence"} — capture concrete proof before closing this contract.`;
512
+ }
513
+ function renderLiveExpectationSummary(contract) {
514
+ const lines = ["AgentBridge expects evidence for:"];
515
+ for (const criterion of contract.completionCriteria.slice(0, 4)) {
516
+ lines.push(`- ${criterion}`);
517
+ }
518
+ return lines.join("\n");
519
+ }
520
+ function localSessionToContractIntelligence(session) {
521
+ if (session.completionCriteria?.length && session.contractProfiles?.length) {
522
+ return {
523
+ contractProfiles: session.contractProfiles,
524
+ completionCriteria: session.completionCriteria,
525
+ proofNeeded: session.proofNeeded ?? [],
526
+ riskyOmissions: session.riskyOmissions ?? [],
527
+ expectedSurfaces: session.expectedSurfaces ?? [],
528
+ expectedFileAreas: session.expectedFileAreas ?? [],
529
+ createdBy: session.createdBy ?? "agent_hello",
530
+ };
531
+ }
532
+ return buildContractIntelligence(session.intent, session.createdBy ?? "agent_hello");
533
+ }
534
+ function sessionNeedsContractBackfill(session) {
535
+ return !session.completionCriteria?.length || !session.contractProfiles?.length;
536
+ }
537
+ function ensureSessionContractIntelligence(session) {
538
+ if (!sessionNeedsContractBackfill(session)) {
539
+ return session;
540
+ }
541
+ const createdBy = session.createdBy ?? (session.agentId?.trim() === "local" ? "user_start" : "agent_hello");
542
+ const contract = buildContractIntelligence(session.intent?.trim(), createdBy);
543
+ const updated = {
544
+ ...session,
545
+ createdBy,
546
+ contractProfiles: contract.contractProfiles,
547
+ expectedSurfaces: contract.expectedSurfaces,
548
+ expectedFileAreas: contract.expectedFileAreas,
549
+ completionCriteria: contract.completionCriteria,
550
+ proofNeeded: contract.proofNeeded,
551
+ riskyOmissions: contract.riskyOmissions,
552
+ };
553
+ (0, session_state_1.writeSessionState)(updated);
554
+ return updated;
555
+ }
556
+ function buildProofRequiredCommands(contract) {
557
+ if (contract.contractProfiles.includes(MCP_RULE_PROFILE)) {
558
+ return [
559
+ "agentbridge verify npm run setup-mcp -- --local",
560
+ "agentbridge verify npm run setup-mcp -- --local (run again to confirm idempotency)",
561
+ ];
562
+ }
563
+ if (contract.contractProfiles.includes(DB_PROFILE)) {
564
+ return [
565
+ "agentbridge verify npx prisma migrate status",
566
+ "agentbridge verify npm test",
567
+ ];
568
+ }
569
+ if (contract.contractProfiles.includes(AUTH_PROFILE) ||
570
+ contract.contractProfiles.includes(API_PROFILE) ||
571
+ contract.contractProfiles.includes(UI_PROFILE) ||
572
+ contract.contractProfiles.includes(TEST_PROFILE) ||
573
+ contract.contractProfiles.includes(PAYMENTS_PROFILE)) {
574
+ return ["agentbridge verify npm test"];
575
+ }
576
+ // GENERIC profile or any unrecognized combination
577
+ return ["agentbridge verify npm test"];
578
+ }
579
+ function buildLocalHelloPayload(session) {
580
+ const contract = localSessionToContractIntelligence(session);
581
+ const scopeSource = session.createdBy ?? (session.agentId?.trim() === "local" ? "user_start" : "agent_hello");
582
+ return {
583
+ contract: {
584
+ id: session.id,
585
+ intent: session.intent?.trim() ?? null,
586
+ created_by: scopeSource,
587
+ },
588
+ done_checklist: contract.completionCriteria.slice(0, 4),
589
+ proof_required: buildProofRequiredCommands(contract),
590
+ warnings: contract.riskyOmissions.slice(0, 2),
591
+ };
592
+ }
593
+ function renderLiveContractBanner(session) {
594
+ const prepared = ensureSessionContractIntelligence(session);
595
+ const summary = renderLiveExpectationSummary(localSessionToContractIntelligence(prepared));
596
+ return `\nLIVE — ${prepared.intent ?? "(no intent)"}\n${summary}\n`;
597
+ }