@agentbridge1/cli 0.0.6 → 0.0.8

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