@agentbridge1/cli 0.0.7 → 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.
- package/dist/build-info.json +4 -4
- package/dist/commands/connect.js +58 -122
- package/dist/commands/doctor.js +46 -8
- package/dist/commands/setup-mcp.js +54 -44
- package/dist/commands/start.js +85 -22
- package/dist/commands/watch.js +661 -92
- package/dist/contract-verdict.js +186 -0
- package/dist/error-catalog.js +29 -0
- package/dist/git-status.js +6 -2
- package/dist/index.js +11 -5
- package/dist/intent-validation.js +37 -0
- package/dist/local-proof.js +12 -4
- package/dist/mcp/agentbridge-mcp.js +602 -23
- package/dist/mcp/agentbridge-mcp.js.map +4 -4
- package/dist/mcp-config.js +64 -0
- package/dist/supervision.js +191 -48
- package/dist/test-runner.js +201 -15
- package/package.json +1 -1
package/dist/commands/watch.js
CHANGED
|
@@ -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
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
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
|
-
"
|
|
488
|
-
"
|
|
489
|
-
"
|
|
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
|
|
519
|
-
const
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
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: ${
|
|
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: ${
|
|
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
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
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
|
|
593
|
-
const
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
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
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
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: ${
|
|
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
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
"
|
|
644
|
-
"
|
|
645
|
-
issue.
|
|
646
|
-
|
|
647
|
-
|
|
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 =
|
|
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 =
|
|
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.
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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.
|
|
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.
|
|
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
|
|
1966
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1996
|
-
|
|
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
|
});
|