@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.
@@ -39,6 +39,8 @@ const test_runner_1 = require("../test-runner");
39
39
  const revert_crossing_1 = require("../revert-crossing");
40
40
  const local_proof_1 = require("../local-proof");
41
41
  const supervision_1 = require("../supervision");
42
+ const contract_intelligence_1 = require("../contract-intelligence");
43
+ const contract_verdict_1 = require("../contract-verdict");
42
44
  const preflight_changed_files_1 = require("../preflight-changed-files");
43
45
  const http_1 = require("../http");
44
46
  const error_catalog_1 = require("../error-catalog");
@@ -50,6 +52,10 @@ const gates_1 = require("../gates");
50
52
  const git_status_1 = require("../git-status");
51
53
  Object.defineProperty(exports, "getDirtyWorkingTreeFiles", { enumerable: true, get: function () { return git_status_1.getDirtyWorkingTreeFiles; } });
52
54
  const IDLE_CLOSE_MS = 5_000;
55
+ const CONTRACT_POLL_MS = 2_000;
56
+ function isLocalFirstMode(cfg) {
57
+ return !cfg.projectId?.trim();
58
+ }
53
59
  const WATCH_STARTUP_TIMEOUT_MS = 12_000;
54
60
  const WATCH_START_TASK_TIMEOUT_MS = 60_000;
55
61
  function acceptanceRolloutOptionsFromGates(gates) {
@@ -117,8 +123,8 @@ function renderCrossingContext(input) {
117
123
  "- required action: approve, handoff, limit, deny, abandon, or revert",
118
124
  ].join("\n");
119
125
  }
120
- function shouldRenderSupervisionSummary(lastSignature, snapshot) {
121
- const nextSignature = (0, supervision_1.supervisionSignature)(snapshot);
126
+ function shouldRenderSupervisionSummary(lastSignature, snapshot, renderContext) {
127
+ const nextSignature = (0, supervision_1.supervisionSignature)(snapshot, renderContext);
122
128
  return {
123
129
  shouldRender: nextSignature !== lastSignature,
124
130
  nextSignature,
@@ -461,6 +467,82 @@ function detectInferredDomainDrift(files, domains) {
461
467
  };
462
468
  return { issue, blocking: highRisk };
463
469
  }
470
+ function assessWatchLaneClaim(input) {
471
+ if (input.files.length === 0) {
472
+ return {
473
+ confidence: "high",
474
+ source: "scope_pattern",
475
+ laneDomain: input.activeLaneDomain,
476
+ };
477
+ }
478
+ if (input.domains.length === 0) {
479
+ return {
480
+ confidence: "high",
481
+ source: "unresolved",
482
+ laneDomain: input.activeLaneDomain,
483
+ };
484
+ }
485
+ if ((input.claimedPaths ?? []).length > 0) {
486
+ return {
487
+ confidence: "high",
488
+ source: "active_session_lane",
489
+ laneDomain: input.activeLaneDomain,
490
+ };
491
+ }
492
+ const inferred = (0, domain_resolution_1.inferLaneFromFiles)(input.files, input.domains).laneDomain ?? null;
493
+ if (inferred) {
494
+ return { confidence: "high", source: "scope_pattern", laneDomain: inferred };
495
+ }
496
+ if (input.activeLaneDomain) {
497
+ return {
498
+ confidence: "high",
499
+ source: "active_session_lane",
500
+ laneDomain: input.activeLaneDomain,
501
+ };
502
+ }
503
+ const ownedDomains = input.domains.filter((domain) => domain.ownerAgentId === input.activeAgentId);
504
+ if (ownedDomains.length === 1) {
505
+ return {
506
+ confidence: "medium",
507
+ source: "active_agent_owner_domain",
508
+ laneDomain: ownedDomains[0]?.domain ?? null,
509
+ };
510
+ }
511
+ return { confidence: "low", source: "unresolved", laneDomain: null };
512
+ }
513
+ function buildWatchLaneClaimIssue(input) {
514
+ const files = [...new Set(input.files)];
515
+ const summarizedFiles = files.length > 5 ? `${files.slice(0, 5).join(", ")} (+${files.length - 5} more)` : files.join(", ");
516
+ if (input.assessment.confidence === "low") {
517
+ return {
518
+ errorCode: "LANE_CLAIM_LOW_CONFIDENCE",
519
+ whatHappened: "Lane/domain claim is unresolved for current changed files.",
520
+ whyItMatters: "Without a deterministic lane claim, scope and ownership enforcement are unreliable.",
521
+ files,
522
+ suggestedPrompt: [
523
+ "AgentBridge could not confidently map these changes to a single domain lane.",
524
+ "Pass an explicit lane/domain context, then rerun watch.",
525
+ `Files: ${summarizedFiles || "none listed"}`,
526
+ ].join("\n"),
527
+ nextAction: "Run with explicit scope/domain context (for example: `agentbridge start --summary \"...\" --scope \"...\" --domain \"...\"`), then rerun watch.",
528
+ };
529
+ }
530
+ if (input.assessment.confidence === "medium" && !input.confirmDomain) {
531
+ return {
532
+ errorCode: "LANE_CLAIM_CONFIRM_REQUIRED",
533
+ whatHappened: `Lane/domain claim is medium confidence (${input.assessment.source}).`,
534
+ whyItMatters: "Medium-confidence claims require explicit acknowledgment before watch can proceed safely.",
535
+ files,
536
+ suggestedPrompt: [
537
+ `Lane candidate: ${input.assessment.laneDomain ?? "unknown"}`,
538
+ "Re-run watch with explicit confirmation to proceed on this lane.",
539
+ `Files: ${summarizedFiles || "none listed"}`,
540
+ ].join("\n"),
541
+ nextAction: "Re-run with `agentbridge watch --confirm-domain` (or set explicit domain in start).",
542
+ };
543
+ }
544
+ return null;
545
+ }
464
546
  async function resolveCurrentSessionTaskSummary(state) {
465
547
  if (!state.changeRequestId)
466
548
  return null;
@@ -473,20 +555,24 @@ async function resolveCurrentSessionTaskSummary(state) {
473
555
  }
474
556
  }
475
557
  function renderWatchStartupHeader(input) {
476
- const modeLine = input.mode === "explicit"
477
- ? "Mode: coding/implementation (scoped policy active)"
478
- : "Mode: inferred (brainstorming/planning or coding/implementation)";
479
558
  return [
480
- "AgentBridge is watching",
481
- modeLine,
482
- `Task: ${input.task}`,
483
- ...(input.scope ? [`Scope: ${input.scope}`] : []),
484
- `Current status: ${input.currentStatus}`,
485
- `Next guidance: ${input.nextGuidance}`,
559
+ "AgentBridge watch:",
560
+ "",
561
+ "1) Actions performed",
562
+ `- Watch started for task: ${input.task}`,
563
+ ...(input.scope ? [`- Scope: ${input.scope}`] : []),
564
+ `- Mode: ${input.mode === "explicit" ? "strict scoped" : "inferred"}`,
486
565
  "",
487
- "Live supervision: keep this watch session running beside Cursor.",
488
- "Task checkpoint/close: agentbridge start",
489
- "Record proof: agentbridge verify -- <test command>",
566
+ "2) Proof present / missing",
567
+ "- Present: live monitoring is active",
568
+ "- Missing: proof not evaluated until changes are checked",
569
+ "",
570
+ "3) Next move",
571
+ `- ${input.nextGuidance}`,
572
+ "- Live supervision: keep this watch session running beside Cursor.",
573
+ "- Task checkpoint/close: agentbridge start",
574
+ "- Keep `agentbridge watch` running while the agent works",
575
+ "- Record proof with `agentbridge verify -- <test command>`",
490
576
  "",
491
577
  ].join("\n");
492
578
  }
@@ -515,16 +601,115 @@ async function flushStdout() {
515
601
  }
516
602
  await new Promise((resolveTick) => setImmediate(resolveTick));
517
603
  }
518
- function buildWatchBlockingIssue(report, options) {
519
- const summarizePromptFiles = (files) => {
520
- const unique = [...new Set(files)];
521
- if (unique.length === 0)
522
- return "current files";
523
- const visible = unique.slice(0, 3);
524
- if (unique.length <= 3)
525
- return visible.join(", ");
526
- return `${visible.join(", ")} (+${unique.length - visible.length} more)`;
604
+ function summarizeWatchPromptFiles(files) {
605
+ const unique = [...new Set(files)];
606
+ if (unique.length === 0)
607
+ return "current files";
608
+ const visible = unique.slice(0, 3);
609
+ if (unique.length <= 3)
610
+ return visible.join(", ");
611
+ return `${visible.join(", ")} (+${unique.length - visible.length} more)`;
612
+ }
613
+ function isBlockingWatchIssue(issue) {
614
+ return issue.errorCode !== "AUTO_VERIFIED";
615
+ }
616
+ async function buildAutoVerifyIssueForFiles(files, options) {
617
+ const uniqueFiles = (0, local_proof_1.normalizeProofMatchingFileSet)(files);
618
+ if (uniqueFiles.length === 0)
619
+ return null;
620
+ const testResult = await (0, test_runner_1.runDetectedTests)({
621
+ relatedFiles: uniqueFiles,
622
+ timeoutMs: 3 * 60 * 1000,
623
+ });
624
+ if (testResult !== null) {
625
+ const coherence = (0, test_runner_1.analyzeCoherence)({
626
+ intent: options.intent ?? null,
627
+ changedFiles: uniqueFiles,
628
+ claimedPaths: options.claimedPaths ?? [],
629
+ testResult,
630
+ });
631
+ const driftWarning = coherence.verdict === "drift" && coherence.suspiciousFiles.length > 0
632
+ ? `\nNote: ${coherence.suspiciousFiles.length} file(s) may not match your declared intent ("${options.intent}"). Check: ${coherence.suspiciousFiles.slice(0, 3).join(", ")}`
633
+ : "";
634
+ if (testResult.passed) {
635
+ return {
636
+ errorCode: "AUTO_VERIFIED",
637
+ whatHappened: `AgentBridge ran tests and they passed (${testResult.command}).` + driftWarning,
638
+ whyItMatters: coherence.verdict === "drift"
639
+ ? "Tests passed, but some changed files look unrelated to your stated intent — review the scope."
640
+ : "Automated verification confirms the changes are safe.",
641
+ files: uniqueFiles,
642
+ suggestedPrompt: "",
643
+ nextAction: coherence.verdict === "drift"
644
+ ? `Review suspicious files: ${coherence.suspiciousFiles.slice(0, 3).join(", ")}`
645
+ : "No action required — changes are verified.",
646
+ testResult,
647
+ intent: options.intent,
648
+ coherence,
649
+ };
650
+ }
651
+ const failedNames = (0, test_runner_1.extractFailedTestNames)(testResult.stdout + "\n" + testResult.stderr);
652
+ const failureSummary = failedNames.length > 0
653
+ ? `Failing: ${failedNames.slice(0, 3).join(", ")}${failedNames.length > 3 ? ` (+${failedNames.length - 3} more)` : ""}`
654
+ : "Check test output for details.";
655
+ return {
656
+ errorCode: "TEST_FAILED",
657
+ whatHappened: `AgentBridge ran \`${testResult.command}\` and ${testResult.timedOut ? "it timed out" : "tests failed"}.` +
658
+ driftWarning,
659
+ whyItMatters: "These test failures must be fixed before the changes can be trusted.",
660
+ files: uniqueFiles,
661
+ suggestedPrompt: [
662
+ "AgentBridge ran tests and found failures.",
663
+ failureSummary,
664
+ options.intent ? `Your declared intent: "${options.intent}"` : "",
665
+ "Fix the failing tests, then rerun AgentBridge watch.",
666
+ `Files changed: ${summarizeWatchPromptFiles(uniqueFiles)}`,
667
+ ]
668
+ .filter(Boolean)
669
+ .join("\n"),
670
+ nextAction: "Fix the failing tests listed above, then rerun: agentbridge watch",
671
+ testResult,
672
+ intent: options.intent,
673
+ coherence,
674
+ };
675
+ }
676
+ const inferredWorkType = options.detectedWorkType &&
677
+ options.detectedWorkType !== "general" &&
678
+ options.detectedWorkType !== "unknown"
679
+ ? options.detectedWorkType
680
+ : (0, supervision_1.inferSupervisionWorkType)(uniqueFiles);
681
+ const tailoredHints = (0, supervision_1.requiredProofHints)(inferredWorkType).slice(0, 2);
682
+ const hintLine = tailoredHints.length > 0
683
+ ? `Suggested proof: ${tailoredHints.join("; ")}`
684
+ : "Suggested proof: run a verification command for changed files.";
685
+ const isUiCopy = inferredWorkType === "ui_copy";
686
+ return {
687
+ errorCode: "PROOF_MISSING",
688
+ whatHappened: proofMissingReason(uniqueFiles, inferredWorkType),
689
+ whyItMatters: proofMissingWhyItMatters(inferredWorkType),
690
+ files: uniqueFiles,
691
+ suggestedPrompt: [
692
+ `AgentBridge found ${uniqueFiles.length} changed files without proof.`,
693
+ hintLine,
694
+ "Run verification for the changed files, then rerun AgentBridge watch.",
695
+ `Files: ${summarizeWatchPromptFiles(uniqueFiles)}`,
696
+ ].join("\n"),
697
+ nextAction: isUiCopy
698
+ ? "Capture visual confirmation (screenshot) for the UI copy change, then rerun watch."
699
+ : "Run a verification command for the changed files.",
700
+ intent: options.intent,
527
701
  };
702
+ }
703
+ async function buildLocalProofWatchIssue(evaluation, options) {
704
+ const staticIssue = (0, local_proof_1.buildLocalProofBlockingIssue)(evaluation);
705
+ if (!staticIssue)
706
+ return null;
707
+ if (staticIssue.errorCode === "PROOF_MISSING") {
708
+ return buildAutoVerifyIssueForFiles(evaluation.changedFiles, options);
709
+ }
710
+ return staticIssue;
711
+ }
712
+ async function buildWatchBlockingIssue(report, options) {
528
713
  if (options.strictScope && (report.out_of_scope_files ?? []).length > 0) {
529
714
  const files = [...new Set(report.out_of_scope_files ?? [])];
530
715
  return {
@@ -535,7 +720,7 @@ function buildWatchBlockingIssue(report, options) {
535
720
  suggestedPrompt: [
536
721
  "You changed files outside the allowed scope.",
537
722
  "Revert them, justify why they are required and ask to expand scope, or split them into a separate task.",
538
- `Out-of-scope files: ${summarizePromptFiles(files)}`,
723
+ `Out-of-scope files: ${summarizeWatchPromptFiles(files)}`,
539
724
  ].join("\n"),
540
725
  nextAction: "Revert out-of-scope files, then rerun: agentbridge watch",
541
726
  };
@@ -550,7 +735,7 @@ function buildWatchBlockingIssue(report, options) {
550
735
  suggestedPrompt: [
551
736
  "Your proof is stale because files changed after verification.",
552
737
  "Rerun verification after your final edit, then rerun AgentBridge watch.",
553
- `Files: ${summarizePromptFiles(staleFiles)}`,
738
+ `Files: ${summarizeWatchPromptFiles(staleFiles)}`,
554
739
  ].join("\n"),
555
740
  nextAction: "Run a verification command for the final changed files.",
556
741
  };
@@ -573,45 +758,49 @@ function buildWatchBlockingIssue(report, options) {
573
758
  return (0, proof_obligations_1.buildWatchBlockingIssueFromObligation)(report, blockingObligation);
574
759
  }
575
760
  if (report.decision === "needs_proof") {
576
- const files = [...new Set(report.changed_files)];
577
- return {
578
- errorCode: "PROOF_MISSING",
579
- whatHappened: "No valid verification proof is attached to the current work.",
580
- whyItMatters: "Coding changes need AgentBridge-recorded proof before they are safe to trust.",
581
- files,
582
- suggestedPrompt: [
583
- `AgentBridge found ${files.length} changed files without proof.`,
584
- "Run verification for the changed files, then rerun AgentBridge watch.",
585
- `Files: ${summarizePromptFiles(files)}`,
586
- ].join("\n"),
587
- nextAction: "Run a verification command for the changed files.",
588
- };
761
+ return buildAutoVerifyIssueForFiles([...new Set(report.changed_files)], {
762
+ intent: options.intent ?? null,
763
+ claimedPaths: options.claimedPaths ?? [],
764
+ detectedWorkType: report.detected_work_type,
765
+ });
589
766
  }
590
767
  return null;
591
768
  }
592
- function buildFallbackWatchBlockingIssue(supervision) {
593
- const summarizePromptFiles = (files) => {
594
- const unique = [...new Set(files)];
595
- if (unique.length === 0)
596
- return "current files";
597
- const visible = unique.slice(0, 3);
598
- if (unique.length <= 3)
599
- return visible.join(", ");
600
- return `${visible.join(", ")} (+${unique.length - visible.length} more)`;
601
- };
769
+ function proofMissingReason(files, workType) {
770
+ const fileList = files.slice(0, 3).join(", ") + (files.length > 3 ? ` (+${files.length - 3} more)` : "");
771
+ if (workType === "ui_copy") {
772
+ return `${files.length} UI file${files.length === 1 ? "" : "s"} changed (${fileList}) with no visual confirmation recorded.`;
773
+ }
774
+ if (workType === "documentation") {
775
+ return `${files.length} doc file${files.length === 1 ? "" : "s"} changed (${fileList}) with no verification recorded.`;
776
+ }
777
+ if (workType === "cli_git_enforcement") {
778
+ return `${files.length} CLI enforcement file${files.length === 1 ? "" : "s"} changed (${fileList}) — these need passing tests before they can be trusted.`;
779
+ }
780
+ return `${files.length} file${files.length === 1 ? "" : "s"} changed (${fileList}) with no verification run recorded for this session.`;
781
+ }
782
+ function proofMissingWhyItMatters(workType) {
783
+ if (workType === "ui_copy") {
784
+ return "Without a screenshot or visual check, there's no record that the UI looks correct.";
785
+ }
786
+ if (workType === "documentation") {
787
+ return "Without a check, there's no record that the docs are accurate and complete.";
788
+ }
789
+ if (workType === "cli_git_enforcement") {
790
+ return "CLI enforcement logic is critical — untested changes here can silently break agent supervision.";
791
+ }
792
+ return "Without a verification run, there's no record that the changes work correctly.";
793
+ }
794
+ async function buildFallbackWatchBlockingIssue(supervision, options) {
602
795
  if (supervision.decision === "needs_proof") {
603
- return {
604
- errorCode: "PROOF_MISSING",
605
- whatHappened: "No valid verification proof is attached to the current work.",
606
- whyItMatters: "Coding changes need AgentBridge-recorded proof before they are safe to trust.",
607
- files: [],
608
- suggestedPrompt: [
609
- `AgentBridge found ${supervision.changedFiles.length} changed files without proof.`,
610
- "Run verification for the changed files, then rerun AgentBridge watch.",
611
- `Files: ${summarizePromptFiles(supervision.changedFiles)}`,
612
- ].join("\n"),
613
- nextAction: "Run a verification command for the changed files.",
614
- };
796
+ const meaningfulFiles = (0, local_proof_1.normalizeProofMatchingFileSet)(supervision.changedFiles);
797
+ if (meaningfulFiles.length === 0)
798
+ return null;
799
+ return buildAutoVerifyIssueForFiles(meaningfulFiles, {
800
+ intent: options.intent ?? null,
801
+ claimedPaths: options.claimedPaths ?? [],
802
+ detectedWorkType: supervision.workType,
803
+ });
615
804
  }
616
805
  if (supervision.decision === "failed") {
617
806
  return {
@@ -621,7 +810,7 @@ function buildFallbackWatchBlockingIssue(supervision) {
621
810
  files: supervision.changedFiles,
622
811
  suggestedPrompt: [
623
812
  "Resolve the verification or scope problem, then rerun AgentBridge watch.",
624
- `Files in current blocked state: ${summarizePromptFiles(supervision.changedFiles)}`,
813
+ `Files in current blocked state: ${summarizeWatchPromptFiles(supervision.changedFiles)}`,
625
814
  ].join("\n"),
626
815
  nextAction: "Resolve proof/scope problems, then rerun watch.",
627
816
  };
@@ -629,22 +818,161 @@ function buildFallbackWatchBlockingIssue(supervision) {
629
818
  return null;
630
819
  }
631
820
  function renderWatchBlockingIssue(issue) {
821
+ const sep = "─".repeat(52);
822
+ // ── Auto-verified: tests passed ──────────────────────────────────────────
823
+ if (issue.errorCode === "AUTO_VERIFIED" && issue.testResult) {
824
+ const dur = (issue.testResult.durationMs / 1000).toFixed(1);
825
+ const hasDrift = issue.coherence?.verdict === "drift";
826
+ const fileList = issue.files.slice(0, 8).join("\n ");
827
+ const moreFiles = issue.files.length > 8 ? `\n (+${issue.files.length - 8} more)` : "";
828
+ const suspiciousLines = hasDrift && issue.coherence
829
+ ? issue.coherence.suspiciousFiles.map((f) => ` ⚠ ${f}`).join("\n")
830
+ : "";
831
+ return [
832
+ sep,
833
+ " AgentBridge Verification Report",
834
+ sep,
835
+ "",
836
+ "📋 YOUR SCOPE",
837
+ ` Intent : ${issue.intent ?? "(no intent declared)"}`,
838
+ ` Files : ${issue.files.length} changed`,
839
+ ` ${fileList}${moreFiles}`,
840
+ ...(hasDrift ? ["", ` ⚠ Scope drift — these files may not match your intent:`] : []),
841
+ ...(suspiciousLines ? [suspiciousLines] : []),
842
+ "",
843
+ "🔬 WHAT WE RAN",
844
+ ` Command : ${issue.testResult.command}`,
845
+ ` Duration: ${dur}s`,
846
+ ` Outcome : ✓ All tests passed`,
847
+ "",
848
+ "✅ WHAT WE FOUND",
849
+ hasDrift
850
+ ? ` Tests passed, but ${issue.coherence.suspiciousFiles.length} file(s) look outside your stated intent.`
851
+ : " All changed files are consistent with your intent. No issues found.",
852
+ "",
853
+ "➡ NEXT STEPS",
854
+ hasDrift
855
+ ? ` 1. Review suspicious files — are they intentional?\n 2. Update your intent if scope has expanded.\n 3. Re-run: agentbridge watch`
856
+ : " You're good. Share this result with your team or merge when ready.",
857
+ "",
858
+ sep,
859
+ ].join("\n");
860
+ }
861
+ // ── Test run failed ───────────────────────────────────────────────────────
862
+ if (issue.errorCode === "TEST_FAILED" && issue.testResult) {
863
+ const dur = (issue.testResult.durationMs / 1000).toFixed(1);
864
+ const failedNames = (0, test_runner_1.extractFailedTestNames)(issue.testResult.stdout + "\n" + issue.testResult.stderr);
865
+ const failureLines = failedNames.length > 0
866
+ ? failedNames.slice(0, 5).map((n) => ` ✗ ${n}`).join("\n")
867
+ : " (check test output above for details)";
868
+ const moreFailures = failedNames.length > 5 ? `\n (+${failedNames.length - 5} more)` : "";
869
+ const hasDrift = issue.coherence?.verdict === "drift";
870
+ const fileList = issue.files.slice(0, 8).join("\n ");
871
+ const moreFiles = issue.files.length > 8 ? `\n (+${issue.files.length - 8} more)` : "";
872
+ const timedOutNote = issue.testResult.timedOut
873
+ ? `\n ⚠ Timed out after ${(issue.testResult.durationMs / 1000).toFixed(0)}s — increase verify.timeoutMs in .agentbridge.json`
874
+ : "";
875
+ return [
876
+ sep,
877
+ " AgentBridge Verification Report",
878
+ sep,
879
+ "",
880
+ "📋 YOUR SCOPE",
881
+ ` Intent : ${issue.intent ?? "(no intent declared)"}`,
882
+ ` Files : ${issue.files.length} changed`,
883
+ ` ${fileList}${moreFiles}`,
884
+ ...(hasDrift
885
+ ? [
886
+ "",
887
+ ` ⚠ Scope drift — ${issue.coherence.suspiciousFiles.length} file(s) may be outside your intent:`,
888
+ ...issue.coherence.suspiciousFiles.slice(0, 3).map((f) => ` ${f}`),
889
+ ]
890
+ : []),
891
+ "",
892
+ "🔬 WHAT WE RAN",
893
+ ` Command : ${issue.testResult.command}`,
894
+ ` Duration: ${dur}s${timedOutNote}`,
895
+ ` Outcome : ✗ Tests failed`,
896
+ "",
897
+ "❌ WHAT WE FOUND",
898
+ failureLines + moreFailures,
899
+ ...(hasDrift ? ["", ` Scope note: ${issue.whyItMatters}`] : []),
900
+ "",
901
+ "➡ NEXT STEPS",
902
+ " 1. Fix the failing tests listed above.",
903
+ ...(hasDrift ? [" 2. Review the out-of-scope files — are they intentional?"] : []),
904
+ ` ${hasDrift ? "3" : "2"}. Re-run: agentbridge watch`,
905
+ "",
906
+ ...(issue.suggestedPrompt
907
+ ? [
908
+ " ── Agent prompt ──",
909
+ ` ${issue.suggestedPrompt.replaceAll("\n", "\n ")}`,
910
+ "",
911
+ ]
912
+ : []),
913
+ sep,
914
+ ].join("\n");
915
+ }
916
+ // ── Proof missing (auto-verify could not run tests) ───────────────────────
917
+ if (issue.errorCode === "PROOF_MISSING") {
918
+ const sep = "─".repeat(52);
919
+ const fileList = issue.files.slice(0, 8).join("\n ");
920
+ const moreFiles = issue.files.length > 8 ? `\n (+${issue.files.length - 8} more)` : "";
921
+ return [
922
+ sep,
923
+ " AgentBridge Verification Report",
924
+ sep,
925
+ "",
926
+ "📋 YOUR SCOPE",
927
+ ` Intent : ${issue.intent ?? "(no intent declared)"}`,
928
+ ` Files : ${issue.files.length} changed`,
929
+ ` ${fileList}${moreFiles}`,
930
+ "",
931
+ "🔬 WHAT WE RAN",
932
+ " Command : (none — no test command detected for these files)",
933
+ " Outcome : ⚠ Verification not run automatically",
934
+ "",
935
+ "❌ WHAT WE FOUND",
936
+ ` ${issue.whatHappened}`,
937
+ ` ${issue.whyItMatters}`,
938
+ "",
939
+ "➡ NEXT STEPS",
940
+ ` 1. ${issue.nextAction}`,
941
+ " 2. Or run: agentbridge verify -- <your test command>",
942
+ " 3. Re-run: agentbridge watch",
943
+ "",
944
+ ...(issue.suggestedPrompt
945
+ ? [
946
+ " ── Agent prompt ──",
947
+ ` ${issue.suggestedPrompt.replaceAll("\n", "\n ")}`,
948
+ "",
949
+ ]
950
+ : []),
951
+ sep,
952
+ ].join("\n");
953
+ }
954
+ // ── Default blocking-issue format ─────────────────────────────────────────
632
955
  const files = [...new Set(issue.files)];
633
956
  const visibleFiles = files.slice(0, 10);
634
957
  const hiddenCount = Math.max(0, files.length - visibleFiles.length);
635
958
  const filesLine = visibleFiles.length > 0 ? visibleFiles.join(", ") : "none listed";
636
959
  return [
637
- "AgentBridge caught an issue",
638
- `What happened: ${issue.whatHappened}`,
639
- `Why it matters: ${issue.whyItMatters}`,
640
- `Files: ${filesLine}`,
641
- ...(hiddenCount > 0 ? [`+ ${hiddenCount} more files hidden (run \`agentbridge watch --details\`).`] : []),
642
- `Error code: ${issue.errorCode}`,
643
- "Suggested prompt to send back to agent:",
644
- "```text",
645
- issue.suggestedPrompt,
646
- "```",
647
- `Next action: ${issue.nextAction}`,
960
+ "AgentBridge watch:",
961
+ "",
962
+ "1) Actions performed",
963
+ "- Evaluated changed files against scope and proof rules",
964
+ `- Files checked: ${filesLine}`,
965
+ ...(hiddenCount > 0 ? [`- + ${hiddenCount} more files hidden (use \`agentbridge watch --details\`)`] : []),
966
+ "",
967
+ "2) Proof present / missing",
968
+ `- Missing/blocked: ${issue.whatHappened}`,
969
+ `- Why it matters: ${issue.whyItMatters}`,
970
+ `- Error code: ${issue.errorCode}`,
971
+ "",
972
+ "3) Next move",
973
+ `- ${issue.nextAction}`,
974
+ "- Optional agent prompt:",
975
+ ` ${issue.suggestedPrompt.replaceAll("\n", " ")}`,
648
976
  "",
649
977
  ].join("\n");
650
978
  }
@@ -652,7 +980,7 @@ function overlayLocalChangedFilesOnSupervision(supervision, localChangedFiles) {
652
980
  if (supervision.changedFiles.length > 0 || localChangedFiles.length === 0) {
653
981
  return supervision;
654
982
  }
655
- const nextChangedFiles = [...new Set(localChangedFiles)].sort();
983
+ const nextChangedFiles = (0, local_proof_1.normalizeProofMatchingFileSet)(localChangedFiles);
656
984
  const nextWorkType = supervision.workType === "unknown" ? (0, supervision_1.inferSupervisionWorkType)(nextChangedFiles) : supervision.workType;
657
985
  return {
658
986
  ...supervision,
@@ -805,6 +1133,8 @@ function normalizeDirtyWorkingTreeFiles(files) {
805
1133
  continue;
806
1134
  if (flat === ".agentbridge" || flat.startsWith(".agentbridge/"))
807
1135
  continue;
1136
+ if ((0, local_proof_1.isProofNoiseFile)(flat))
1137
+ continue;
808
1138
  if (seen.has(flat))
809
1139
  continue;
810
1140
  seen.add(flat);
@@ -844,7 +1174,7 @@ function fileWithinScopedPaths(file, scopedPaths) {
844
1174
  });
845
1175
  }
846
1176
  function startupDirtyClassification(input) {
847
- const workspaceDirtySnapshot = [...new Set(input.dirtyFiles)].sort();
1177
+ const workspaceDirtySnapshot = (0, local_proof_1.normalizeProofMatchingFileSet)(input.dirtyFiles);
848
1178
  const localChanged = new Set(input.localState?.changedFiles ?? []);
849
1179
  const localClaimedPaths = input.serverReportedEmptyScope
850
1180
  ? []
@@ -1003,7 +1333,7 @@ function currentGitHead() {
1003
1333
  * the local session is left active for subsequent verify/follow-up.
1004
1334
  */
1005
1335
  async function runWatchOnceFastPath(input) {
1006
- const { cfg, activeAgentId, task, scope, changeRequestId, allowDirty, timing } = input;
1336
+ const { cfg, activeAgentId, task, scope, changeRequestId, allowDirty, timing, confirmDomain } = input;
1007
1337
  const ignoredDirtyFiles = input.ignoredDirtyFiles ?? new Set();
1008
1338
  let ctx;
1009
1339
  try {
@@ -1092,6 +1422,28 @@ async function runWatchOnceFastPath(input) {
1092
1422
  const lane = (0, domain_resolution_1.inferLaneFromFiles)(classification.acceptanceCandidateFiles.length > 0
1093
1423
  ? classification.acceptanceCandidateFiles
1094
1424
  : changedFiles, cfg.domains ?? []);
1425
+ const laneAssessment = assessWatchLaneClaim({
1426
+ files: classification.acceptanceCandidateFiles.length > 0
1427
+ ? classification.acceptanceCandidateFiles
1428
+ : changedFiles,
1429
+ domains: cfg.domains ?? [],
1430
+ activeLaneDomain: lane.laneDomain ?? null,
1431
+ activeAgentId,
1432
+ claimedPaths,
1433
+ });
1434
+ const laneIssue = buildWatchLaneClaimIssue({
1435
+ assessment: laneAssessment,
1436
+ files: classification.acceptanceCandidateFiles.length > 0
1437
+ ? classification.acceptanceCandidateFiles
1438
+ : changedFiles,
1439
+ confirmDomain: confirmDomain === true,
1440
+ });
1441
+ if (laneIssue) {
1442
+ timing.trackSync("render_output", () => {
1443
+ process.stdout.write(renderWatchBlockingIssue(laneIssue));
1444
+ });
1445
+ return { handled: true, hadBlockingIssue: true };
1446
+ }
1095
1447
  const persistedClaimed = result.claimed_paths.length > 0 ? result.claimed_paths : claimedPaths;
1096
1448
  (0, session_1.openLocalSession)({
1097
1449
  agentId: activeAgentId,
@@ -1114,15 +1466,30 @@ async function runWatchOnceFastPath(input) {
1114
1466
  const supervision = persistedState
1115
1467
  ? finalizeWatchSupervision(persistedState, (0, supervision_1.supervisionFromAcceptance)(acceptance, cfg.domains ?? []))
1116
1468
  : (0, supervision_1.supervisionFromAcceptance)(acceptance, cfg.domains ?? []);
1469
+ const startupSupervision = (0, supervision_1.resolveWatchStartupSupervision)({
1470
+ workspaceRoot: (0, node_process_1.cwd)(),
1471
+ cliProjectId: cfg.projectId,
1472
+ });
1473
+ const onceRenderContext = (0, supervision_1.buildWatchSupervisionRenderContext)({
1474
+ baseStatus: startupSupervision.supervisionStatus,
1475
+ mcpConfigured: startupSupervision.mcpConfigured,
1476
+ agentDeclaredUpdatedAt: supervision.agentDeclared?.updatedAt,
1477
+ });
1117
1478
  timing.trackSync("render_output", () => {
1118
- process.stdout.write(`${(0, supervision_1.renderSupervisionSummary)(supervision)}\n`);
1479
+ process.stdout.write(`${(0, supervision_1.renderSupervisionSummary)(supervision, onceRenderContext)}\n`);
1119
1480
  });
1120
- const blockingIssue = timing.trackSync("proof_fingerprint_comparison", () => buildWatchBlockingIssue(acceptance, { strictScope: true }));
1481
+ const blockingIssue = await timing.trackAsync("proof_fingerprint_comparison", () => buildWatchBlockingIssue(acceptance, {
1482
+ strictScope: true,
1483
+ intent: persistedState?.intent ?? null,
1484
+ claimedPaths: persistedClaimed,
1485
+ }));
1121
1486
  if (blockingIssue) {
1122
1487
  timing.trackSync("render_output", () => {
1123
1488
  process.stdout.write(renderWatchBlockingIssue(blockingIssue));
1124
1489
  });
1125
- hadBlockingIssue = true;
1490
+ if (isBlockingWatchIssue(blockingIssue)) {
1491
+ hadBlockingIssue = true;
1492
+ }
1126
1493
  }
1127
1494
  // Surface scope drift from out-of-scope startup dirt unless the acceptance
1128
1495
  // report already raised the same scope-drift issue (avoid double render).
@@ -1144,6 +1511,7 @@ async function runWatchOnceFastPath(input) {
1144
1511
  async function runWatch(options = {}) {
1145
1512
  const timing = createWatchTimingTracker();
1146
1513
  const cfg = timing.trackSync("config_load", () => (0, config_1.readConfig)());
1514
+ const localFirstMode = isLocalFirstMode(cfg);
1147
1515
  const repoRoot = (0, node_process_1.cwd)();
1148
1516
  const gitignoreSessionEntry = (0, gates_1.ensureGitignoreSessionEntry)(repoRoot);
1149
1517
  const ignoredDirtyFiles = new Set();
@@ -1222,14 +1590,30 @@ async function runWatch(options = {}) {
1222
1590
  });
1223
1591
  }
1224
1592
  });
1225
- if (!explicitStrictMode && !cfg.activeAgentId) {
1593
+ if (!explicitStrictMode && !cfg.activeAgentId && !localFirstMode) {
1226
1594
  process.stdout.write("Startup note: no active internal agent selected. Inferred mode will continue using room-level connection and local supervision.\n");
1227
1595
  await flushStdout();
1228
1596
  }
1229
- if (!hasRecoveredDomainMap) {
1597
+ if (!hasRecoveredDomainMap && !localFirstMode) {
1230
1598
  process.stdout.write("Startup note: domain map is not fully recovered yet. AgentBridge will keep watching with best-effort drift checks.\n");
1231
1599
  await flushStdout();
1232
1600
  }
1601
+ // Non-blocking supervision context check: warn if MCP or rules are missing or mismatched.
1602
+ // Hoisted so every render call (live loop, --once handoff) shares the same startup decision.
1603
+ let watchMcpConfigured = false;
1604
+ let watchSupervisionStatus = "blind";
1605
+ {
1606
+ const startup = (0, supervision_1.resolveWatchStartupSupervision)({
1607
+ workspaceRoot: (0, node_process_1.cwd)(),
1608
+ cliProjectId: cfg.projectId,
1609
+ });
1610
+ watchMcpConfigured = startup.mcpConfigured;
1611
+ watchSupervisionStatus = startup.supervisionStatus;
1612
+ if (startup.warningBanner && !localFirstMode) {
1613
+ process.stdout.write(`${startup.warningBanner}\n`);
1614
+ await flushStdout();
1615
+ }
1616
+ }
1233
1617
  let activeAgentId = cfg.activeAgentId ?? "inferred-room-connection";
1234
1618
  if (options.changeRequestId || options.executionSurfaceId) {
1235
1619
  (0, config_1.updateConfig)({
@@ -1287,6 +1671,7 @@ async function runWatch(options = {}) {
1287
1671
  ? options.changeRequestId ?? cfg.activeChangeRequestId ?? null
1288
1672
  : options.changeRequestId ?? null,
1289
1673
  allowDirty: Boolean(options.allowDirty) || Boolean(options.daemon),
1674
+ confirmDomain: options.confirmDomain === true,
1290
1675
  timing,
1291
1676
  ignoredDirtyFiles,
1292
1677
  });
@@ -1310,6 +1695,7 @@ async function runWatch(options = {}) {
1310
1695
  await timing.trackAsync("start_or_resume", async () => withTimeout((0, start_1.runStart)({
1311
1696
  summary: taskScopeInput.task,
1312
1697
  scope: taskScopeInput.scope,
1698
+ ...(options.confirmDomain === true ? { confirmDomain: true } : {}),
1313
1699
  }), WATCH_START_TASK_TIMEOUT_MS, () => new errors_1.SafeCliError({
1314
1700
  code: "WATCH_STARTUP_TIMEOUT",
1315
1701
  category: "WATCH_ERROR",
@@ -1397,8 +1783,61 @@ async function runWatch(options = {}) {
1397
1783
  let lastSupervisionSignature = null;
1398
1784
  let lastBlockingIssueSignature = null;
1399
1785
  let lastDriftIssueSignature = null;
1786
+ let lastLaneClaimIssueSignature = null;
1400
1787
  let hadBlockingIssue = false;
1788
+ let onceModeVerificationIssue = null;
1789
+ let lastAutoVerifyTestResult = null;
1790
+ let verdictInProgress = false;
1791
+ const waitForContract = async () => {
1792
+ 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");
1795
+ await flushStdout();
1796
+ }
1797
+ while (!(0, session_state_1.isActiveLocalSession)(session)) {
1798
+ await new Promise((resolve) => setTimeout(resolve, CONTRACT_POLL_MS));
1799
+ session = (0, session_state_1.readSessionState)();
1800
+ }
1801
+ process.stdout.write((0, contract_intelligence_1.renderLiveContractBanner)(session));
1802
+ await flushStdout();
1803
+ return session;
1804
+ };
1805
+ const printVerdictAndReset = async (opts) => {
1806
+ if (verdictInProgress)
1807
+ return;
1808
+ verdictInProgress = true;
1809
+ try {
1810
+ const session = (0, session_state_1.readSessionState)();
1811
+ if (!session || session.id === "none")
1812
+ return;
1813
+ const changedFiles = (0, preflight_changed_files_1.computeCurrentWorkFiles)(session);
1814
+ const proofRun = session.lastLocalVerificationRun ?? null;
1815
+ process.stdout.write(`${(0, contract_verdict_1.renderContractVerdict)(session, changedFiles, proofRun, cfg.domains ?? [])}\n`);
1816
+ await flushStdout();
1817
+ if (session.status !== "closed") {
1818
+ (0, session_1.closeLocalSession)(session);
1819
+ }
1820
+ (0, session_state_2.clearSessionState)();
1821
+ if (localFirstMode && !options.once && opts?.waitForNext !== false) {
1822
+ process.stdout.write("\nAgentBridge watching — waiting for next contract...\n");
1823
+ await flushStdout();
1824
+ await waitForContract();
1825
+ }
1826
+ }
1827
+ finally {
1828
+ verdictInProgress = false;
1829
+ }
1830
+ };
1831
+ const rememberAutoVerifyTestResult = (issue) => {
1832
+ if (issue?.testResult) {
1833
+ lastAutoVerifyTestResult = issue.testResult;
1834
+ }
1835
+ };
1401
1836
  const closeIdleSession = async () => {
1837
+ if (localFirstMode) {
1838
+ await printVerdictAndReset();
1839
+ return;
1840
+ }
1402
1841
  const state = (0, session_state_1.readSessionState)();
1403
1842
  if (!state || state.status === "closed" || state.id === "none")
1404
1843
  return;
@@ -1459,7 +1898,9 @@ async function runWatch(options = {}) {
1459
1898
  process.stdout.write(`${renderIdleCloseFailure(err)}\n`);
1460
1899
  }
1461
1900
  }
1462
- const testRun = await (0, test_runner_1.runDetectedTests)();
1901
+ const testRun = lastAutoVerifyTestResult ??
1902
+ onceModeVerificationIssue?.testResult ??
1903
+ (await (0, test_runner_1.runDetectedTests)());
1463
1904
  process.stdout.write(`${(0, briefing_1.renderSessionBriefing)({ state, testRun })}\n`);
1464
1905
  };
1465
1906
  const resetIdle = () => {
@@ -1474,12 +1915,26 @@ async function runWatch(options = {}) {
1474
1915
  const file = (0, node_path_1.relative)((0, node_process_1.cwd)(), absolutePath).replaceAll("\\", "/");
1475
1916
  if (!file || file.startsWith(".."))
1476
1917
  return;
1918
+ if ((0, local_proof_1.isProofNoiseFile)(file))
1919
+ return;
1477
1920
  // Fix D: Don't reset the idle timer in --once mode. Only accept/abandon/done
1478
1921
  // should close the session; --once should leave it open for follow-up commands.
1479
1922
  if (!options.once)
1480
1923
  resetIdle();
1481
1924
  let state = (0, session_state_1.readSessionState)();
1925
+ if (localFirstMode) {
1926
+ if (state?.status === "closed" && state.id !== "none") {
1927
+ await printVerdictAndReset();
1928
+ return;
1929
+ }
1930
+ if (!(0, session_state_1.isActiveLocalSession)(state)) {
1931
+ return;
1932
+ }
1933
+ }
1482
1934
  if (!state || state.status === "closed" || state.id === "none") {
1935
+ if (localFirstMode) {
1936
+ return;
1937
+ }
1483
1938
  const lane = (0, domain_resolution_1.inferLaneFromFiles)([file], cfg.domains ?? []);
1484
1939
  let serverSessionId;
1485
1940
  const changeRequestId = explicitStrictMode
@@ -1535,6 +1990,32 @@ async function runWatch(options = {}) {
1535
1990
  sessionBannerLines.push("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━", "");
1536
1991
  process.stdout.write(sessionBannerLines.join("\n"));
1537
1992
  }
1993
+ const laneAssessment = assessWatchLaneClaim({
1994
+ files: [...state.changedFiles, file],
1995
+ domains: cfg.domains ?? [],
1996
+ activeLaneDomain: state.laneDomain ?? null,
1997
+ activeAgentId,
1998
+ claimedPaths: state.claimedPaths,
1999
+ });
2000
+ const laneIssue = buildWatchLaneClaimIssue({
2001
+ assessment: laneAssessment,
2002
+ files: [...state.changedFiles, file],
2003
+ confirmDomain: options.confirmDomain === true,
2004
+ });
2005
+ const laneIssueSignature = laneIssue ? JSON.stringify(laneIssue) : null;
2006
+ if (laneIssue && laneIssueSignature && laneIssueSignature !== lastLaneClaimIssueSignature) {
2007
+ timing.trackSync("render_output", () => {
2008
+ process.stdout.write(renderWatchBlockingIssue(laneIssue));
2009
+ });
2010
+ lastLaneClaimIssueSignature = laneIssueSignature;
2011
+ }
2012
+ else if (!laneIssueSignature) {
2013
+ lastLaneClaimIssueSignature = null;
2014
+ }
2015
+ if (laneIssue) {
2016
+ hadBlockingIssue = true;
2017
+ return;
2018
+ }
1538
2019
  const outcome = (0, watch_core_1.applyFileChange)(state, file);
1539
2020
  (0, session_state_1.writeSessionState)(state);
1540
2021
  const unresolvedProtectedCrossing = state.crossings.some((crossing) => crossing.status === "unresolved" &&
@@ -1563,29 +2044,38 @@ async function runWatch(options = {}) {
1563
2044
  process.stdout.write(`Observed-diff sync failed: ${supervisionResult.syncError}\n`);
1564
2045
  }
1565
2046
  const supervision = supervisionResult.supervision;
1566
- const nextSummary = shouldRenderSupervisionSummary(lastSupervisionSignature, supervision);
2047
+ const watchRenderContext = (0, supervision_1.buildWatchSupervisionRenderContext)({
2048
+ baseStatus: watchSupervisionStatus,
2049
+ mcpConfigured: watchMcpConfigured,
2050
+ agentDeclaredUpdatedAt: supervision.agentDeclared?.updatedAt,
2051
+ });
2052
+ const nextSummary = shouldRenderSupervisionSummary(lastSupervisionSignature, supervision, watchRenderContext);
1567
2053
  if (nextSummary.shouldRender) {
1568
2054
  timing.trackSync("render_output", () => {
1569
2055
  process.stdout.write(`${(0, supervision_1.renderSupervisionSummary)(supervision, {
1570
2056
  compact: !explicitStrictMode && !options.details,
2057
+ ...watchRenderContext,
1571
2058
  })}\n`);
1572
2059
  });
1573
2060
  lastSupervisionSignature = nextSummary.nextSignature;
1574
2061
  }
1575
2062
  const renderSupervisionProofIssue = explicitStrictMode || !options.once;
1576
2063
  if (supervisionResult.acceptanceReport) {
1577
- const blockingIssue = timing.trackSync("proof_fingerprint_comparison", () => buildWatchBlockingIssue(supervisionResult.acceptanceReport, {
2064
+ const blockingIssue = await timing.trackAsync("proof_fingerprint_comparison", () => buildWatchBlockingIssue(supervisionResult.acceptanceReport, {
1578
2065
  strictScope: explicitStrictMode,
2066
+ intent: state?.intent ?? null,
2067
+ claimedPaths: state?.claimedPaths ?? [],
1579
2068
  }));
1580
2069
  const issueSignature = blockingIssue ? JSON.stringify(blockingIssue) : null;
1581
2070
  if (blockingIssue &&
1582
2071
  issueSignature &&
1583
2072
  issueSignature !== lastBlockingIssueSignature &&
1584
2073
  renderSupervisionProofIssue) {
2074
+ rememberAutoVerifyTestResult(blockingIssue);
1585
2075
  timing.trackSync("render_output", () => {
1586
2076
  process.stdout.write(renderWatchBlockingIssue(blockingIssue));
1587
2077
  });
1588
- if (explicitStrictMode) {
2078
+ if (explicitStrictMode || isBlockingWatchIssue(blockingIssue)) {
1589
2079
  hadBlockingIssue = true;
1590
2080
  }
1591
2081
  lastBlockingIssueSignature = issueSignature;
@@ -1595,16 +2085,20 @@ async function runWatch(options = {}) {
1595
2085
  }
1596
2086
  }
1597
2087
  else {
1598
- const blockingIssue = timing.trackSync("proof_fingerprint_comparison", () => buildFallbackWatchBlockingIssue(supervision));
2088
+ const blockingIssue = await timing.trackAsync("proof_fingerprint_comparison", () => buildFallbackWatchBlockingIssue(supervision, {
2089
+ intent: state?.intent ?? null,
2090
+ claimedPaths: state?.claimedPaths ?? [],
2091
+ }));
1599
2092
  const issueSignature = blockingIssue ? JSON.stringify(blockingIssue) : null;
1600
2093
  if (blockingIssue &&
1601
2094
  issueSignature &&
1602
2095
  issueSignature !== lastBlockingIssueSignature &&
1603
2096
  renderSupervisionProofIssue) {
2097
+ rememberAutoVerifyTestResult(blockingIssue);
1604
2098
  timing.trackSync("render_output", () => {
1605
2099
  process.stdout.write(renderWatchBlockingIssue(blockingIssue));
1606
2100
  });
1607
- if (explicitStrictMode) {
2101
+ if (explicitStrictMode || isBlockingWatchIssue(blockingIssue)) {
1608
2102
  hadBlockingIssue = true;
1609
2103
  }
1610
2104
  lastBlockingIssueSignature = issueSignature;
@@ -1881,6 +2375,29 @@ async function runWatch(options = {}) {
1881
2375
  inferredMode: !explicitStrictMode,
1882
2376
  serverReportedEmptyScope,
1883
2377
  }));
2378
+ const startupLaneAssessment = assessWatchLaneClaim({
2379
+ files: startupClassification.acceptanceCandidateFiles.length > 0
2380
+ ? startupClassification.acceptanceCandidateFiles
2381
+ : startupClassification.workspaceDirtySnapshot,
2382
+ domains: cfg.domains ?? [],
2383
+ activeLaneDomain: startupState?.laneDomain ?? null,
2384
+ activeAgentId,
2385
+ claimedPaths: startupClaimedPaths,
2386
+ });
2387
+ const startupLaneIssue = buildWatchLaneClaimIssue({
2388
+ assessment: startupLaneAssessment,
2389
+ files: startupClassification.acceptanceCandidateFiles.length > 0
2390
+ ? startupClassification.acceptanceCandidateFiles
2391
+ : startupClassification.workspaceDirtySnapshot,
2392
+ confirmDomain: options.confirmDomain === true,
2393
+ });
2394
+ if (startupLaneIssue) {
2395
+ timing.trackSync("render_output", () => {
2396
+ process.stdout.write(renderWatchBlockingIssue(startupLaneIssue));
2397
+ });
2398
+ hadBlockingIssue = true;
2399
+ return;
2400
+ }
1884
2401
  if (startupClassification.workspaceDirtySnapshot.length === 0 && !explicitStrictMode) {
1885
2402
  process.stdout.write(renderBrainstormingStatus());
1886
2403
  }
@@ -1962,13 +2479,21 @@ async function runWatch(options = {}) {
1962
2479
  }
1963
2480
  }
1964
2481
  if (options.once && !explicitStrictMode && startupClassification.acceptanceCandidateFiles.length > 0) {
1965
- const localProofEvaluation = timing.trackSync("local_proof_evaluation", () => (0, local_proof_1.evaluateLocalProof)(startupClassification.acceptanceCandidateFiles, (0, session_state_1.readSessionState)()?.lastLocalVerificationRun));
1966
- const localProofIssue = (0, local_proof_1.buildLocalProofBlockingIssue)(localProofEvaluation);
2482
+ const sessionForProof = (0, session_state_1.readSessionState)();
2483
+ const localProofEvaluation = timing.trackSync("local_proof_evaluation", () => (0, local_proof_1.evaluateLocalProof)(startupClassification.acceptanceCandidateFiles, sessionForProof?.lastLocalVerificationRun));
2484
+ const localProofIssue = await timing.trackAsync("local_proof_auto_verify", () => buildLocalProofWatchIssue(localProofEvaluation, {
2485
+ intent: sessionForProof?.intent ?? null,
2486
+ claimedPaths: startupClaimedPaths,
2487
+ }));
2488
+ onceModeVerificationIssue = localProofIssue;
1967
2489
  if (localProofIssue) {
2490
+ rememberAutoVerifyTestResult(localProofIssue);
1968
2491
  timing.trackSync("render_output", () => {
1969
2492
  process.stdout.write(renderWatchBlockingIssue(localProofIssue));
1970
2493
  });
1971
- hadBlockingIssue = true;
2494
+ if (isBlockingWatchIssue(localProofIssue)) {
2495
+ hadBlockingIssue = true;
2496
+ }
1972
2497
  }
1973
2498
  const domainDrift = detectInferredDomainDrift(startupClassification.acceptanceCandidateFiles, cfg.domains ?? []);
1974
2499
  if (domainDrift) {
@@ -1984,7 +2509,7 @@ async function runWatch(options = {}) {
1984
2509
  const finalState = (0, session_state_1.readSessionState)();
1985
2510
  if (finalState && (0, session_state_1.isActiveLocalSession)(finalState)) {
1986
2511
  const postBaseline = timing.trackSync("baseline_classification", () => (0, preflight_changed_files_1.computeCurrentWorkFiles)(finalState));
1987
- const supervision = finalizeWatchSupervision(finalState, (0, supervision_1.fallbackSupervisionSnapshot)({
2512
+ let supervision = finalizeWatchSupervision(finalState, (0, supervision_1.fallbackSupervisionSnapshot)({
1988
2513
  workSessionId: finalState.serverSessionId ?? finalState.id,
1989
2514
  changeRequestId: finalState.changeRequestId ?? changeRequestId ?? null,
1990
2515
  changedFiles: postBaseline.length > 0 ? postBaseline : [...(finalState.changedFiles ?? [])],
@@ -1992,9 +2517,35 @@ async function runWatch(options = {}) {
1992
2517
  unresolvedProtectedCrossing: false,
1993
2518
  blocked: finalState.status === "blocked",
1994
2519
  }));
1995
- timing.trackSync("render_output", () => {
1996
- process.stdout.write(`${(0, supervision_1.renderSupervisionSummary)(supervision, { compact: true })}\n`);
2520
+ if (onceModeVerificationIssue?.errorCode === "AUTO_VERIFIED" &&
2521
+ onceModeVerificationIssue.testResult?.passed) {
2522
+ supervision = {
2523
+ ...supervision,
2524
+ decision: "accepted",
2525
+ requiredProof: [],
2526
+ nextAction: onceModeVerificationIssue.nextAction,
2527
+ };
2528
+ }
2529
+ else if (onceModeVerificationIssue?.errorCode === "TEST_FAILED") {
2530
+ supervision = {
2531
+ ...supervision,
2532
+ decision: "failed",
2533
+ nextAction: onceModeVerificationIssue.nextAction,
2534
+ };
2535
+ }
2536
+ const handoffRenderContext = (0, supervision_1.buildWatchSupervisionRenderContext)({
2537
+ baseStatus: watchSupervisionStatus,
2538
+ mcpConfigured: watchMcpConfigured,
2539
+ agentDeclaredUpdatedAt: supervision.agentDeclared?.updatedAt,
1997
2540
  });
2541
+ if (!onceModeVerificationIssue) {
2542
+ timing.trackSync("render_output", () => {
2543
+ process.stdout.write(`${(0, supervision_1.renderSupervisionSummary)(supervision, {
2544
+ compact: true,
2545
+ ...handoffRenderContext,
2546
+ })}\n`);
2547
+ });
2548
+ }
1998
2549
  }
1999
2550
  }
2000
2551
  };
@@ -2015,6 +2566,18 @@ async function runWatch(options = {}) {
2015
2566
  await processStartupDirty();
2016
2567
  process.stdout.write("[startup] ready: watch runtime is live.\n");
2017
2568
  await flushStdout();
2569
+ if (localFirstMode && !options.once) {
2570
+ if (!(0, session_state_1.isActiveLocalSession)((0, session_state_1.readSessionState)())) {
2571
+ await waitForContract();
2572
+ }
2573
+ 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
+ }
2579
+ }
2580
+ }
2018
2581
  if (options.once) {
2019
2582
  if (idleTimer)
2020
2583
  clearTimeout(idleTimer);
@@ -2027,6 +2590,13 @@ async function runWatch(options = {}) {
2027
2590
  if (idleTimer)
2028
2591
  clearTimeout(idleTimer);
2029
2592
  stop?.();
2593
+ if (localFirstMode) {
2594
+ void printVerdictAndReset({ waitForNext: false }).finally(() => {
2595
+ process.stdout.write("\nagentbridge watch stopped.\n");
2596
+ process.exit(0);
2597
+ });
2598
+ return;
2599
+ }
2030
2600
  process.stdout.write("\nagentbridge watch stopped.\n");
2031
2601
  process.exit(0);
2032
2602
  });