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