@agentbridge1/cli 0.0.9 → 0.0.11

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.
@@ -1,6 +1,6 @@
1
1
  {
2
- "builtAt": "2026-06-20T12:33:18.940Z",
3
- "gitHead": "b4d6915",
4
- "sourceLatestMtime": "2026-06-20T12:02:16.784Z",
5
- "sourceLatestFile": "src/commands/start.ts"
2
+ "builtAt": "2026-06-21T02:05:47.707Z",
3
+ "gitHead": "09300d7",
4
+ "sourceLatestMtime": "2026-06-21T01:45:03.790Z",
5
+ "sourceLatestFile": "src/commands/watch.ts"
6
6
  }
@@ -8,6 +8,8 @@ exports.finalizeWatchSupervision = finalizeWatchSupervision;
8
8
  exports.syncAndBuildSupervisionSnapshot = syncAndBuildSupervisionSnapshot;
9
9
  exports.renderWatchTaskScopeUsage = renderWatchTaskScopeUsage;
10
10
  exports.renderWatchStartupHeader = renderWatchStartupHeader;
11
+ exports.renderLocalWatchWaiting = renderLocalWatchWaiting;
12
+ exports.renderLocalWatchReady = renderLocalWatchReady;
11
13
  exports.buildWatchBlockingIssue = buildWatchBlockingIssue;
12
14
  exports.renderWatchBlockingIssue = renderWatchBlockingIssue;
13
15
  exports.overlayLocalChangedFilesOnSupervision = overlayLocalChangedFilesOnSupervision;
@@ -30,6 +32,7 @@ const domain_resolution_1 = require("../domain-resolution");
30
32
  const briefing_1 = require("../briefing");
31
33
  const session_1 = require("../session");
32
34
  const session_state_1 = require("../session-state");
35
+ const git_status_1 = require("../git-status");
33
36
  const watch_core_1 = require("../watch-core");
34
37
  const watcher_1 = require("../watcher");
35
38
  const server_sync_1 = require("../server-sync");
@@ -49,8 +52,8 @@ const session_state_2 = require("../session-state");
49
52
  const file_fingerprints_1 = require("../file-fingerprints");
50
53
  const start_1 = require("./start");
51
54
  const gates_1 = require("../gates");
52
- const git_status_1 = require("../git-status");
53
- Object.defineProperty(exports, "getDirtyWorkingTreeFiles", { enumerable: true, get: function () { return git_status_1.getDirtyWorkingTreeFiles; } });
55
+ const git_status_2 = require("../git-status");
56
+ Object.defineProperty(exports, "getDirtyWorkingTreeFiles", { enumerable: true, get: function () { return git_status_2.getDirtyWorkingTreeFiles; } });
54
57
  const IDLE_CLOSE_MS = 5_000;
55
58
  const CONTRACT_POLL_MS = 2_000;
56
59
  function isLocalFirstMode(cfg) {
@@ -576,6 +579,19 @@ function renderWatchStartupHeader(input) {
576
579
  "",
577
580
  ].join("\n");
578
581
  }
582
+ function renderLocalWatchWaiting() {
583
+ return [
584
+ "AgentBridge watch — live. No contract yet.",
585
+ "Waiting for: agentbridge start \"...\" or agent_hello({ intent: \"...\" })",
586
+ "",
587
+ ].join("\n");
588
+ }
589
+ function renderLocalWatchReady(session) {
590
+ if (!session.intent?.trim()) {
591
+ return renderLocalWatchWaiting();
592
+ }
593
+ return (0, contract_intelligence_1.renderLiveContractBanner)(session).replace(/^\n/, "");
594
+ }
579
595
  function renderStartupPhase(step, status) {
580
596
  const verb = status === "starting" ? "starting" : status === "done" ? "done" : "skipped";
581
597
  return `[startup] ${step}: ${verb}\n`;
@@ -632,9 +648,12 @@ async function buildAutoVerifyIssueForFiles(files, options) {
632
648
  ? `\nNote: ${coherence.suspiciousFiles.length} file(s) may not match your declared intent ("${options.intent}"). Check: ${coherence.suspiciousFiles.slice(0, 3).join(", ")}`
633
649
  : "";
634
650
  if (testResult.passed) {
651
+ const fallbackNote = testResult.fallbackUsed
652
+ ? "\nScoped test selection could not run cleanly; AgentBridge fell back to the full verify command."
653
+ : "";
635
654
  return {
636
655
  errorCode: "AUTO_VERIFIED",
637
- whatHappened: `AgentBridge ran tests and they passed (${testResult.command}).` + driftWarning,
656
+ whatHappened: `AgentBridge ran tests and they passed (${testResult.command}).` + fallbackNote + driftWarning,
638
657
  whyItMatters: coherence.verdict === "drift"
639
658
  ? "Tests passed, but some changed files look unrelated to your stated intent — review the scope."
640
659
  : "Automated verification confirms the changes are safe.",
@@ -648,18 +667,94 @@ async function buildAutoVerifyIssueForFiles(files, options) {
648
667
  coherence,
649
668
  };
650
669
  }
670
+ const failureType = testResult.failureType ?? "test_failures";
651
671
  const failedNames = (0, test_runner_1.extractFailedTestNames)(testResult.stdout + "\n" + testResult.stderr);
652
672
  const failureSummary = failedNames.length > 0
653
673
  ? `Failing: ${failedNames.slice(0, 3).join(", ")}${failedNames.length > 3 ? ` (+${failedNames.length - 3} more)` : ""}`
654
- : "Check test output for details.";
674
+ : "Check verification output for details.";
675
+ const firstErrorLine = (testResult.stderr.split("\n").find((line) => line.trim().length > 0) ??
676
+ testResult.stdout.split("\n").find((line) => line.trim().length > 0) ??
677
+ "No diagnostic output captured.").trim();
678
+ const fallbackHint = testResult.fallbackUsed
679
+ ? "AgentBridge retried with the broader verify command after scoped verification failed."
680
+ : "";
681
+ if (failureType === "command_invalid") {
682
+ return {
683
+ errorCode: "TEST_COMMAND_INVALID",
684
+ whatHappened: `AgentBridge could not run verification command due to an invalid flag/argument (${testResult.command}).` +
685
+ driftWarning,
686
+ whyItMatters: "This is a verification command configuration issue, so test pass/fail status is unknown.",
687
+ files: uniqueFiles,
688
+ suggestedPrompt: [
689
+ "AgentBridge could not execute verification because the command arguments are invalid.",
690
+ `Diagnostic: ${firstErrorLine}`,
691
+ fallbackHint,
692
+ options.intent ? `Your declared intent: "${options.intent}"` : "",
693
+ "Fix the verification command/config and rerun AgentBridge watch.",
694
+ `Files changed: ${summarizeWatchPromptFiles(uniqueFiles)}`,
695
+ ]
696
+ .filter(Boolean)
697
+ .join("\n"),
698
+ nextAction: "Fix verification command flags/options, then rerun: agentbridge watch",
699
+ testResult,
700
+ intent: options.intent,
701
+ coherence,
702
+ };
703
+ }
704
+ if (failureType === "tooling_missing") {
705
+ return {
706
+ errorCode: "TEST_TOOLING_MISSING",
707
+ whatHappened: `AgentBridge could not run verification because required test tooling is missing (${testResult.command}).` +
708
+ driftWarning,
709
+ whyItMatters: "Without test tooling, AgentBridge cannot produce reliable verification evidence for this change.",
710
+ files: uniqueFiles,
711
+ suggestedPrompt: [
712
+ "AgentBridge could not execute verification because test tooling is missing.",
713
+ `Diagnostic: ${firstErrorLine}`,
714
+ fallbackHint,
715
+ options.intent ? `Your declared intent: "${options.intent}"` : "",
716
+ "Install/restore required tooling and rerun AgentBridge watch.",
717
+ `Files changed: ${summarizeWatchPromptFiles(uniqueFiles)}`,
718
+ ]
719
+ .filter(Boolean)
720
+ .join("\n"),
721
+ nextAction: "Install missing test tooling/dependencies, then rerun: agentbridge watch",
722
+ testResult,
723
+ intent: options.intent,
724
+ coherence,
725
+ };
726
+ }
727
+ if (failureType === "timeout") {
728
+ return {
729
+ errorCode: "TEST_TIMEOUT",
730
+ whatHappened: `AgentBridge verification timed out (${testResult.command}).` + driftWarning,
731
+ whyItMatters: "Timed-out verification provides no reliable proof for these changes.",
732
+ files: uniqueFiles,
733
+ suggestedPrompt: [
734
+ "AgentBridge verification timed out before tests completed.",
735
+ `Diagnostic: ${firstErrorLine}`,
736
+ fallbackHint,
737
+ options.intent ? `Your declared intent: "${options.intent}"` : "",
738
+ "Narrow the test scope or increase verify timeout, then rerun AgentBridge watch.",
739
+ `Files changed: ${summarizeWatchPromptFiles(uniqueFiles)}`,
740
+ ]
741
+ .filter(Boolean)
742
+ .join("\n"),
743
+ nextAction: "Reduce scope or increase verify timeout, then rerun: agentbridge watch",
744
+ testResult,
745
+ intent: options.intent,
746
+ coherence,
747
+ };
748
+ }
655
749
  return {
656
750
  errorCode: "TEST_FAILED",
657
- whatHappened: `AgentBridge ran \`${testResult.command}\` and ${testResult.timedOut ? "it timed out" : "tests failed"}.` +
751
+ whatHappened: `AgentBridge ran \`${testResult.command}\` and tests failed.` +
658
752
  driftWarning,
659
753
  whyItMatters: "These test failures must be fixed before the changes can be trusted.",
660
754
  files: uniqueFiles,
661
755
  suggestedPrompt: [
662
756
  "AgentBridge ran tests and found failures.",
757
+ fallbackHint,
663
758
  failureSummary,
664
759
  options.intent ? `Your declared intent: "${options.intent}"` : "",
665
760
  "Fix the failing tests, then rerun AgentBridge watch.",
@@ -859,19 +954,44 @@ function renderWatchBlockingIssue(issue) {
859
954
  ].join("\n");
860
955
  }
861
956
  // ── Test run failed ───────────────────────────────────────────────────────
862
- if (issue.errorCode === "TEST_FAILED" && issue.testResult) {
957
+ if ((issue.errorCode === "TEST_FAILED" ||
958
+ issue.errorCode === "TEST_TIMEOUT" ||
959
+ issue.errorCode === "TEST_COMMAND_INVALID" ||
960
+ issue.errorCode === "TEST_TOOLING_MISSING") &&
961
+ issue.testResult) {
863
962
  const dur = (issue.testResult.durationMs / 1000).toFixed(1);
864
963
  const failedNames = (0, test_runner_1.extractFailedTestNames)(issue.testResult.stdout + "\n" + issue.testResult.stderr);
964
+ const firstDiagnosticLine = (issue.testResult.stderr.split("\n").find((line) => line.trim().length > 0) ??
965
+ issue.testResult.stdout.split("\n").find((line) => line.trim().length > 0) ??
966
+ "No diagnostic output captured.").trim();
865
967
  const failureLines = failedNames.length > 0
866
968
  ? failedNames.slice(0, 5).map((n) => ` ✗ ${n}`).join("\n")
867
- : " (check test output above for details)";
969
+ : issue.errorCode === "TEST_COMMAND_INVALID" ||
970
+ issue.errorCode === "TEST_TOOLING_MISSING" ||
971
+ issue.errorCode === "TEST_TIMEOUT"
972
+ ? ` ${firstDiagnosticLine}`
973
+ : " (check test output above for details)";
868
974
  const moreFailures = failedNames.length > 5 ? `\n (+${failedNames.length - 5} more)` : "";
869
975
  const hasDrift = issue.coherence?.verdict === "drift";
870
976
  const fileList = issue.files.slice(0, 8).join("\n ");
871
977
  const moreFiles = issue.files.length > 8 ? `\n (+${issue.files.length - 8} more)` : "";
872
- const timedOutNote = issue.testResult.timedOut
978
+ const timedOutNote = issue.testResult.failureType === "timeout"
873
979
  ? `\n ⚠ Timed out after ${(issue.testResult.durationMs / 1000).toFixed(0)}s — increase verify.timeoutMs in .agentbridge.json`
874
980
  : "";
981
+ const outcomeLabel = issue.errorCode === "TEST_COMMAND_INVALID"
982
+ ? "⚠ Verification command invalid"
983
+ : issue.errorCode === "TEST_TOOLING_MISSING"
984
+ ? "⚠ Verification tooling missing"
985
+ : issue.errorCode === "TEST_TIMEOUT"
986
+ ? "✗ Verification timed out"
987
+ : "✗ Tests failed";
988
+ const stepOne = issue.errorCode === "TEST_COMMAND_INVALID"
989
+ ? " 1. Fix the verification command flags/options shown above."
990
+ : issue.errorCode === "TEST_TOOLING_MISSING"
991
+ ? " 1. Install/fix required test tooling for the verification command."
992
+ : issue.errorCode === "TEST_TIMEOUT"
993
+ ? " 1. Narrow test scope or increase verification timeout."
994
+ : " 1. Fix the failing tests listed above.";
875
995
  return [
876
996
  sep,
877
997
  " AgentBridge Verification Report",
@@ -892,14 +1012,14 @@ function renderWatchBlockingIssue(issue) {
892
1012
  "🔬 WHAT WE RAN",
893
1013
  ` Command : ${issue.testResult.command}`,
894
1014
  ` Duration: ${dur}s${timedOutNote}`,
895
- ` Outcome : ✗ Tests failed`,
1015
+ ` Outcome : ${outcomeLabel}`,
896
1016
  "",
897
1017
  "❌ WHAT WE FOUND",
898
1018
  failureLines + moreFailures,
899
1019
  ...(hasDrift ? ["", ` Scope note: ${issue.whyItMatters}`] : []),
900
1020
  "",
901
1021
  "➡ NEXT STEPS",
902
- " 1. Fix the failing tests listed above.",
1022
+ stepOne,
903
1023
  ...(hasDrift ? [" 2. Review the out-of-scope files — are they intentional?"] : []),
904
1024
  ` ${hasDrift ? "3" : "2"}. Re-run: agentbridge watch`,
905
1025
  "",
@@ -1124,14 +1244,14 @@ function normalizeDirtyWorkingTreeFiles(files) {
1124
1244
  const normalized = normalizeDirtyFilePath(raw);
1125
1245
  if (!normalized || normalized.startsWith(".."))
1126
1246
  continue;
1127
- if (normalized === ".agentbridge" || normalized.startsWith(".agentbridge/"))
1247
+ if ((0, git_status_1.isAgentbridgeLocalPath)(normalized))
1128
1248
  continue;
1129
1249
  const expanded = expandDirectoryToFiles(normalized);
1130
1250
  for (const candidate of expanded) {
1131
1251
  const flat = normalizeDirtyFilePath(candidate);
1132
1252
  if (!flat || flat.startsWith(".."))
1133
1253
  continue;
1134
- if (flat === ".agentbridge" || flat.startsWith(".agentbridge/"))
1254
+ if ((0, git_status_1.isAgentbridgeLocalPath)(flat))
1135
1255
  continue;
1136
1256
  if ((0, local_proof_1.isProofNoiseFile)(flat))
1137
1257
  continue;
@@ -1284,7 +1404,7 @@ function buildStartupScopeDriftIssue(input) {
1284
1404
  async function ensureWatchRepoClean(allowDirty = false, ignoredDirtyFiles = new Set()) {
1285
1405
  if (allowDirty)
1286
1406
  return;
1287
- const dirtyFiles = filterIgnoredDirtyFiles((0, git_status_1.getDirtyWorkingTreeFiles)(), ignoredDirtyFiles);
1407
+ const dirtyFiles = normalizeDirtyWorkingTreeFiles(filterIgnoredDirtyFiles((0, git_status_2.getDirtyWorkingTreeFiles)(), ignoredDirtyFiles));
1288
1408
  if (dirtyFiles.length === 0)
1289
1409
  return;
1290
1410
  const rl = (0, promises_1.createInterface)({ input: process.stdin, output: process.stdout });
@@ -1343,7 +1463,7 @@ async function runWatchOnceFastPath(input) {
1343
1463
  // No server context (offline / unconfigured) — defer to legacy local flow.
1344
1464
  return { handled: false, hadBlockingIssue: false };
1345
1465
  }
1346
- const dirtyFiles = timing.trackSync("git_snapshot", () => normalizeDirtyWorkingTreeFiles(filterIgnoredDirtyFiles((0, git_status_1.getDirtyWorkingTreeFiles)(), ignoredDirtyFiles)));
1466
+ const dirtyFiles = timing.trackSync("git_snapshot", () => normalizeDirtyWorkingTreeFiles(filterIgnoredDirtyFiles((0, git_status_2.getDirtyWorkingTreeFiles)(), ignoredDirtyFiles)));
1347
1467
  const localState = (0, session_state_1.readSessionState)();
1348
1468
  const claimedPaths = [
1349
1469
  ...new Set([scope, ...(localState?.claimedPaths ?? [])].map((path) => path.trim()).filter(Boolean)),
@@ -1536,6 +1656,7 @@ async function runWatch(options = {}) {
1536
1656
  (0, session_state_1.writeSessionState)(localSession);
1537
1657
  }
1538
1658
  const explicitStrictMode = Boolean(taskScopeInput.task && taskScopeInput.scope);
1659
+ const localInferredMode = localFirstMode && !explicitStrictMode;
1539
1660
  if (hasActiveLocalSession && explicitStrictMode) {
1540
1661
  const activeLocalSession = localSession;
1541
1662
  if (!activeLocalSession)
@@ -1560,24 +1681,26 @@ async function runWatch(options = {}) {
1560
1681
  return;
1561
1682
  }
1562
1683
  }
1563
- const startupScope = explicitStrictMode
1564
- ? taskScopeInput.scope ?? localSession?.claimedPaths?.[0] ?? "(scope unavailable)"
1565
- : taskScopeInput.scope;
1566
- const startupTask = explicitStrictMode
1567
- ? taskScopeInput.task ?? "Current AgentBridge task"
1568
- : taskScopeInput.task ?? "Inferred from changed files/current work";
1569
- process.stdout.write(renderWatchStartupHeader({
1570
- mode: explicitStrictMode ? "explicit" : "inferred",
1571
- task: startupTask,
1572
- scope: startupScope,
1573
- currentStatus: "Starting watch runtime.",
1574
- nextGuidance: explicitStrictMode
1575
- ? "AgentBridge will enforce explicit scope and proof checks."
1576
- : "AgentBridge will classify brainstorming vs coding and infer task/scope from repo activity.",
1577
- }));
1578
- await flushStdout();
1579
- process.stdout.write(renderStartupPhase("resolving project", "starting"));
1580
- await flushStdout();
1684
+ if (!localInferredMode) {
1685
+ const startupScope = explicitStrictMode
1686
+ ? taskScopeInput.scope ?? localSession?.claimedPaths?.[0] ?? "(scope unavailable)"
1687
+ : taskScopeInput.scope;
1688
+ const startupTask = explicitStrictMode
1689
+ ? taskScopeInput.task ?? "Current AgentBridge task"
1690
+ : taskScopeInput.task ?? "Inferred from changed files/current work";
1691
+ process.stdout.write(renderWatchStartupHeader({
1692
+ mode: explicitStrictMode ? "explicit" : "inferred",
1693
+ task: startupTask,
1694
+ scope: startupScope,
1695
+ currentStatus: "Starting watch runtime.",
1696
+ nextGuidance: explicitStrictMode
1697
+ ? "AgentBridge will enforce explicit scope and proof checks."
1698
+ : "AgentBridge will classify brainstorming vs coding and infer task/scope from repo activity.",
1699
+ }));
1700
+ await flushStdout();
1701
+ process.stdout.write(renderStartupPhase("resolving project", "starting"));
1702
+ await flushStdout();
1703
+ }
1581
1704
  const hasRecoveredDomainMap = Boolean(cfg.domains && cfg.domains.length > 0);
1582
1705
  timing.trackSync("identity_resolution", () => {
1583
1706
  if (explicitStrictMode && !cfg.activeAgentId) {
@@ -1621,7 +1744,7 @@ async function runWatch(options = {}) {
1621
1744
  ...(options.executionSurfaceId ? { executionSurfaceId: options.executionSurfaceId } : {}),
1622
1745
  });
1623
1746
  }
1624
- if (!options.once) {
1747
+ if (!localInferredMode && !options.once) {
1625
1748
  try {
1626
1749
  await timing.trackAsync("project_access_check", async () => withTimeout((async () => {
1627
1750
  const requestedCr = options.changeRequestId ?? cfg.activeChangeRequestId ?? null;
@@ -1648,8 +1771,10 @@ async function runWatch(options = {}) {
1648
1771
  // If network context is unavailable, continue in local-only mode.
1649
1772
  }
1650
1773
  }
1651
- process.stdout.write(renderStartupPhase("resolving project", "done"));
1652
- await flushStdout();
1774
+ if (!localInferredMode) {
1775
+ process.stdout.write(renderStartupPhase("resolving project", "done"));
1776
+ await flushStdout();
1777
+ }
1653
1778
  // Fast path: collapse the one-shot review into a single bounded server call.
1654
1779
  // Falls through to the legacy multi-call flow when the server predates the
1655
1780
  // consolidated endpoint or no server context is available. `startingTaskAnnounced`
@@ -1745,16 +1870,18 @@ async function runWatch(options = {}) {
1745
1870
  await flushStdout();
1746
1871
  }
1747
1872
  }
1748
- else {
1873
+ else if (!localInferredMode) {
1749
1874
  process.stdout.write("[startup] session: inferred mode (will start tracking when coding begins)\n");
1750
1875
  await flushStdout();
1751
1876
  }
1752
1877
  // In daemon mode, skip interactive dirty-file prompt (treat as --allow-dirty).
1753
1878
  await timing.trackAsync("repo_clean_check", async () => ensureWatchRepoClean(Boolean(options.allowDirty) || Boolean(options.daemon), ignoredDirtyFiles));
1754
- process.stdout.write(renderStartupPhase("checking workspace", "done"));
1755
- await flushStdout();
1879
+ if (!localInferredMode) {
1880
+ process.stdout.write(renderStartupPhase("checking workspace", "done"));
1881
+ await flushStdout();
1882
+ }
1756
1883
  const domainPacketsLoaded = new Set();
1757
- if (!options.once) {
1884
+ if (!options.once && !localInferredMode) {
1758
1885
  try {
1759
1886
  const packetCtx = (0, config_1.contextFromConfig)();
1760
1887
  const projectPacket = await timing.trackAsync("project_packet_fetch", async () => withTimeout((0, server_sync_1.fetchProjectPacket)(packetCtx), WATCH_STARTUP_TIMEOUT_MS, () => new errors_1.SafeCliError({
@@ -1790,11 +1917,11 @@ async function runWatch(options = {}) {
1790
1917
  let verdictInProgress = false;
1791
1918
  const waitForContract = async () => {
1792
1919
  let session = (0, session_state_1.readSessionState)();
1793
- if (!(0, session_state_1.isActiveLocalSession)(session)) {
1794
- process.stdout.write("AgentBridge watching — waiting for contract...\n");
1920
+ if (!(0, session_state_1.hasActiveContract)(session)) {
1921
+ process.stdout.write(renderLocalWatchWaiting());
1795
1922
  await flushStdout();
1796
1923
  }
1797
- while (!(0, session_state_1.isActiveLocalSession)(session)) {
1924
+ while (!(0, session_state_1.hasActiveContract)(session)) {
1798
1925
  await new Promise((resolve) => setTimeout(resolve, CONTRACT_POLL_MS));
1799
1926
  session = (0, session_state_1.readSessionState)();
1800
1927
  }
@@ -1927,7 +2054,7 @@ async function runWatch(options = {}) {
1927
2054
  await printVerdictAndReset();
1928
2055
  return;
1929
2056
  }
1930
- if (!(0, session_state_1.isActiveLocalSession)(state)) {
2057
+ if (!(0, session_state_1.hasActiveContract)(state)) {
1931
2058
  return;
1932
2059
  }
1933
2060
  }
@@ -2304,7 +2431,10 @@ async function runWatch(options = {}) {
2304
2431
  await handling;
2305
2432
  };
2306
2433
  const processStartupDirty = async () => {
2307
- const startupDirtyFiles = timing.trackSync("git_snapshot", () => normalizeDirtyWorkingTreeFiles(filterIgnoredDirtyFiles((0, git_status_1.getDirtyWorkingTreeFiles)(), ignoredDirtyFiles)));
2434
+ if (localInferredMode) {
2435
+ return;
2436
+ }
2437
+ const startupDirtyFiles = timing.trackSync("git_snapshot", () => normalizeDirtyWorkingTreeFiles(filterIgnoredDirtyFiles((0, git_status_2.getDirtyWorkingTreeFiles)(), ignoredDirtyFiles)));
2308
2438
  if (startupDirtyFiles.length === 0) {
2309
2439
  if (!explicitStrictMode) {
2310
2440
  process.stdout.write(`${renderBrainstormingStatus()}\n`);
@@ -2526,7 +2656,10 @@ async function runWatch(options = {}) {
2526
2656
  nextAction: onceModeVerificationIssue.nextAction,
2527
2657
  };
2528
2658
  }
2529
- else if (onceModeVerificationIssue?.errorCode === "TEST_FAILED") {
2659
+ else if (onceModeVerificationIssue?.errorCode === "TEST_FAILED" ||
2660
+ onceModeVerificationIssue?.errorCode === "TEST_TIMEOUT" ||
2661
+ onceModeVerificationIssue?.errorCode === "TEST_COMMAND_INVALID" ||
2662
+ onceModeVerificationIssue?.errorCode === "TEST_TOOLING_MISSING") {
2530
2663
  supervision = {
2531
2664
  ...supervision,
2532
2665
  decision: "failed",
@@ -2551,32 +2684,39 @@ async function runWatch(options = {}) {
2551
2684
  };
2552
2685
  let stop = null;
2553
2686
  if (!options.once) {
2554
- process.stdout.write(renderStartupPhase("starting watcher", "starting"));
2555
- await flushStdout();
2687
+ if (!localInferredMode) {
2688
+ process.stdout.write(renderStartupPhase("starting watcher", "starting"));
2689
+ await flushStdout();
2690
+ }
2556
2691
  stop = timing.trackSync("watcher_startup", () => (0, watcher_1.startWatcher)((0, node_process_1.cwd)(), (absolutePath) => {
2557
2692
  void onAbsolutePathChange(absolutePath);
2558
2693
  }));
2559
- process.stdout.write(renderStartupPhase("starting watcher", "done"));
2560
- await flushStdout();
2694
+ if (!localInferredMode) {
2695
+ process.stdout.write(renderStartupPhase("starting watcher", "done"));
2696
+ await flushStdout();
2697
+ }
2561
2698
  }
2562
- else {
2699
+ else if (!localInferredMode) {
2563
2700
  process.stdout.write(renderStartupPhase("starting watcher", "skipped"));
2564
2701
  await flushStdout();
2565
2702
  }
2566
2703
  await processStartupDirty();
2567
- process.stdout.write("[startup] ready: watch runtime is live.\n");
2568
- await flushStdout();
2569
- if (localFirstMode && !options.once) {
2570
- if (!(0, session_state_1.isActiveLocalSession)((0, session_state_1.readSessionState)())) {
2571
- await waitForContract();
2704
+ if (localInferredMode && !options.once) {
2705
+ const readySession = (0, session_state_1.readSessionState)();
2706
+ if ((0, session_state_1.hasActiveContract)(readySession)) {
2707
+ process.stdout.write(`${renderLocalWatchReady(readySession)}\n`);
2572
2708
  }
2573
2709
  else {
2574
- const active = (0, session_state_1.readSessionState)();
2575
- if (active) {
2576
- process.stdout.write((0, contract_intelligence_1.renderLiveContractBanner)(active));
2577
- await flushStdout();
2578
- }
2710
+ process.stdout.write(renderLocalWatchWaiting());
2579
2711
  }
2712
+ await flushStdout();
2713
+ }
2714
+ else {
2715
+ process.stdout.write("[startup] ready: watch runtime is live.\n");
2716
+ await flushStdout();
2717
+ }
2718
+ if (localFirstMode && !options.once && !(0, session_state_1.hasActiveContract)((0, session_state_1.readSessionState)())) {
2719
+ await waitForContract();
2580
2720
  }
2581
2721
  if (options.once) {
2582
2722
  if (idleTimer)
@@ -15,6 +15,7 @@ const intent_parser_1 = require("./intent-parser");
15
15
  const proof_parser_1 = require("./proof-parser");
16
16
  const diff_reader_1 = require("./diff-reader");
17
17
  const session_state_1 = require("./session-state");
18
+ const domain_keywords_1 = require("./domain-keywords");
18
19
  const MCP_RULE_PROFILE = "mcp_rules_config_install";
19
20
  const AUTH_PROFILE = "auth_login_session";
20
21
  const DB_PROFILE = "database_schema_migration";
@@ -26,32 +27,30 @@ const GENERIC_PROFILE = "generic";
26
27
  function unique(values) {
27
28
  return [...new Set(values.map((value) => value.trim()).filter(Boolean))];
28
29
  }
29
- function matchIntent(intent, keywords) {
30
- const lower = intent.toLowerCase();
31
- return keywords.some((keyword) => lower.includes(keyword));
32
- }
30
+ const MCP_ENTITY_KEYWORDS = ["mcp", "agentbridge", "cursor"];
31
+ const MCP_ACTION_KEYWORDS = ["rule", "rules", "install", "setup", "config", "configuration"];
33
32
  function detectProfiles(intent) {
34
33
  const profiles = [];
35
- if (matchIntent(intent, ["mcp"]) &&
36
- matchIntent(intent, ["rule", "rules", "install", "setup", "config", "configuration"])) {
34
+ if ((0, domain_keywords_1.matchesAnyKeywordBoundary)(intent, MCP_ENTITY_KEYWORDS) &&
35
+ (0, domain_keywords_1.matchesAnyKeywordBoundary)(intent, MCP_ACTION_KEYWORDS)) {
37
36
  profiles.push(MCP_RULE_PROFILE);
38
37
  }
39
- if (matchIntent(intent, ["auth", "login", "session", "token", "oauth"])) {
38
+ if ((0, domain_keywords_1.matchesDomainKeywords)(intent, "auth")) {
40
39
  profiles.push(AUTH_PROFILE);
41
40
  }
42
- if (matchIntent(intent, ["database", "db", "schema", "migration", "prisma", "sql"])) {
41
+ if ((0, domain_keywords_1.matchesDomainKeywords)(intent, "database")) {
43
42
  profiles.push(DB_PROFILE);
44
43
  }
45
- if (matchIntent(intent, ["ui", "style", "css", "copy", "layout", "component"])) {
44
+ if ((0, domain_keywords_1.matchesDomainKeywords)(intent, "ui")) {
46
45
  profiles.push(UI_PROFILE);
47
46
  }
48
- if (matchIntent(intent, ["api", "endpoint", "route", "handler", "controller"])) {
47
+ if ((0, domain_keywords_1.matchesDomainKeywords)(intent, "api")) {
49
48
  profiles.push(API_PROFILE);
50
49
  }
51
- if (matchIntent(intent, ["test", "tests", "vitest", "jest", "proof"])) {
50
+ if ((0, domain_keywords_1.matchesDomainKeywords)(intent, "tests")) {
52
51
  profiles.push(TEST_PROFILE);
53
52
  }
54
- if (matchIntent(intent, ["payment", "payments", "billing", "stripe", "webhook"])) {
53
+ if ((0, domain_keywords_1.matchesDomainKeywords)(intent, "payments")) {
55
54
  profiles.push(PAYMENTS_PROFILE);
56
55
  }
57
56
  return profiles.length > 0 ? profiles : [GENERIC_PROFILE];
@@ -229,6 +228,31 @@ function fileMatchesArea(file, area) {
229
228
  const normalizedArea = area.replaceAll("\\", "/").trim();
230
229
  if (!normalizedArea || normalizedArea === "**")
231
230
  return true;
231
+ // Infix glob: src/**/auth/** — prefix path + directory segment(s), not substring hacks
232
+ if (normalizedArea.includes("**/")) {
233
+ const segments = normalizedArea.split("/**/");
234
+ if (segments.length >= 2) {
235
+ const prefix = segments[0].replace(/\/$/, "");
236
+ if (prefix && normalizedFile !== prefix && !normalizedFile.startsWith(`${prefix}/`)) {
237
+ return false;
238
+ }
239
+ const infixPattern = segments
240
+ .slice(1)
241
+ .join("/")
242
+ .replace(/\/\*\*$/, "")
243
+ .replace(/^\*\//, "");
244
+ if (!infixPattern || infixPattern === "*")
245
+ return true;
246
+ const infixParts = infixPattern.split("/").filter((part) => part && part !== "*");
247
+ for (const part of infixParts) {
248
+ const asDir = `/${part}/`;
249
+ const atEnd = normalizedFile.endsWith(`/${part}`);
250
+ if (!normalizedFile.includes(asDir) && !atEnd)
251
+ return false;
252
+ }
253
+ return true;
254
+ }
255
+ }
232
256
  if (normalizedArea.endsWith("/**")) {
233
257
  const prefix = normalizedArea.slice(0, -3);
234
258
  return normalizedFile === prefix || normalizedFile.startsWith(`${prefix}/`);
@@ -495,20 +519,23 @@ function evaluateCompletionCriteria(params) {
495
519
  });
496
520
  }
497
521
  function nextStepFromCriteria(evaluations, proofNeeded) {
498
- const missing = evaluations.filter((item) => item.status === "no_evidence");
499
- if (missing.length === 0) {
522
+ const unproven = evaluations.filter((item) => item.status === "no_evidence" ||
523
+ item.status === "partial_evidence" ||
524
+ item.status === "cannot_verify");
525
+ if (unproven.length === 0) {
500
526
  return "All tracked criteria are evidenced. Confirm final proof and commit when ready.";
501
527
  }
502
528
  // Prioritise idempotency if it is anywhere in the unproven list
503
- const idempotency = missing.find((item) => item.criterion.toLowerCase().includes("idempotent"));
529
+ const idempotency = unproven.find((item) => item.criterion.toLowerCase().includes("idempotent"));
504
530
  if (idempotency) {
505
531
  return "Run setup/configuration twice in a clean repo and record output proving idempotency.";
506
532
  }
507
- const firstMissing = missing[0];
508
- if (firstMissing && firstMissing.criterion.toLowerCase().includes("proof")) {
533
+ const proofGap = unproven.find((item) => item.criterion.toLowerCase().includes("proof"));
534
+ if (proofGap) {
509
535
  return proofNeeded[0] ?? "Run a relevant test/manual proof command and record output.";
510
536
  }
511
- return `${firstMissing?.criterion ?? "Missing evidence"} — capture concrete proof before closing this contract.`;
537
+ const firstUnproven = unproven[0];
538
+ return `${firstUnproven?.criterion ?? "Missing evidence"} — capture concrete proof before closing this contract.`;
512
539
  }
513
540
  function renderLiveExpectationSummary(contract) {
514
541
  const lines = ["AgentBridge expects evidence for:"];
@@ -207,7 +207,9 @@ function renderContractVerdict(session, changedFiles, proofRun, domains = []) {
207
207
  }
208
208
  }
209
209
  const stillUnproven = criteriaEvals
210
- ? criteriaEvals.filter((e) => e.status === "no_evidence")
210
+ ? criteriaEvals.filter((e) => e.status === "no_evidence" ||
211
+ e.status === "partial_evidence" ||
212
+ e.status === "cannot_verify")
211
213
  : [];
212
214
  lines.push("", "Here's what is still unproven:");
213
215
  if (stillUnproven.length === 0 && (!criteriaEvals || criteriaEvals.length === 0)) {
@@ -218,7 +220,12 @@ function renderContractVerdict(session, changedFiles, proofRun, domains = []) {
218
220
  }
219
221
  else {
220
222
  for (const e of stillUnproven) {
221
- lines.push(` - ${e.criterion}`);
223
+ const prefix = e.status === "partial_evidence"
224
+ ? "~"
225
+ : e.status === "cannot_verify"
226
+ ? "?"
227
+ : "✗";
228
+ lines.push(` ${prefix} [${e.status}] ${e.criterion}`);
222
229
  lines.push(` ${e.evidence}`);
223
230
  }
224
231
  }