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