@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
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MCP_SERVER_NAME = void 0;
|
|
4
|
+
exports.buildLocalMcpServerEntry = buildLocalMcpServerEntry;
|
|
5
|
+
exports.buildServerMcpServerEntry = buildServerMcpServerEntry;
|
|
6
|
+
exports.mergeAgentbridgeIntoCursorMcp = mergeAgentbridgeIntoCursorMcp;
|
|
7
|
+
exports.writeLocalMcpConfig = writeLocalMcpConfig;
|
|
8
|
+
exports.writeServerMcpConfig = writeServerMcpConfig;
|
|
9
|
+
const node_fs_1 = require("node:fs");
|
|
10
|
+
const node_path_1 = require("node:path");
|
|
11
|
+
const node_process_1 = require("node:process");
|
|
12
|
+
const mcp_runtime_1 = require("./mcp-runtime");
|
|
13
|
+
exports.MCP_SERVER_NAME = "agentbridge";
|
|
14
|
+
function buildLocalMcpServerEntry(cliDistDir) {
|
|
15
|
+
const runtime = (0, mcp_runtime_1.resolveMcpRuntime)(cliDistDir);
|
|
16
|
+
const repoRoot = (0, node_path_1.resolve)((0, node_process_1.cwd)());
|
|
17
|
+
const entry = runtime
|
|
18
|
+
? { command: runtime.command, args: runtime.args }
|
|
19
|
+
: { command: "agentbridge", args: ["mcp"] };
|
|
20
|
+
entry.env = { AGENTBRIDGE_REPO_ROOT: repoRoot };
|
|
21
|
+
return entry;
|
|
22
|
+
}
|
|
23
|
+
function buildServerMcpServerEntry(projectId, apiKey, apiBaseUrl) {
|
|
24
|
+
return {
|
|
25
|
+
command: "agentbridge",
|
|
26
|
+
args: ["mcp"],
|
|
27
|
+
env: {
|
|
28
|
+
AGENTBRIDGE_PROJECT_ID: projectId,
|
|
29
|
+
AGENTBRIDGE_API_KEY: apiKey,
|
|
30
|
+
AGENTBRIDGE_BASE_URL: apiBaseUrl,
|
|
31
|
+
AGENTBRIDGE_REPO_ROOT: (0, node_path_1.resolve)((0, node_process_1.cwd)()),
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/** Merge AgentBridge into project-level `.cursor/mcp.json` (preserves other servers). */
|
|
36
|
+
function mergeAgentbridgeIntoCursorMcp(entry) {
|
|
37
|
+
const cursorDir = (0, node_path_1.join)((0, node_process_1.cwd)(), ".cursor");
|
|
38
|
+
const mcpPath = (0, node_path_1.join)(cursorDir, "mcp.json");
|
|
39
|
+
let existing = {};
|
|
40
|
+
if ((0, node_fs_1.existsSync)(mcpPath)) {
|
|
41
|
+
try {
|
|
42
|
+
existing = JSON.parse((0, node_fs_1.readFileSync)(mcpPath, "utf8"));
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
// Malformed file — start fresh
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const mcpServers = existing.mcpServers && typeof existing.mcpServers === "object"
|
|
49
|
+
? existing.mcpServers
|
|
50
|
+
: {};
|
|
51
|
+
mcpServers[exports.MCP_SERVER_NAME] = entry;
|
|
52
|
+
const updated = { ...existing, mcpServers };
|
|
53
|
+
if (!(0, node_fs_1.existsSync)(cursorDir)) {
|
|
54
|
+
(0, node_fs_1.mkdirSync)(cursorDir, { recursive: true });
|
|
55
|
+
}
|
|
56
|
+
(0, node_fs_1.writeFileSync)(mcpPath, JSON.stringify(updated, null, 2) + "\n", "utf8");
|
|
57
|
+
return mcpPath;
|
|
58
|
+
}
|
|
59
|
+
function writeLocalMcpConfig(cliDistDir) {
|
|
60
|
+
return mergeAgentbridgeIntoCursorMcp(buildLocalMcpServerEntry(cliDistDir));
|
|
61
|
+
}
|
|
62
|
+
function writeServerMcpConfig(projectId, apiKey, apiBaseUrl) {
|
|
63
|
+
return mergeAgentbridgeIntoCursorMcp(buildServerMcpServerEntry(projectId, apiKey, apiBaseUrl));
|
|
64
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Proof output parser — reads proofRun.stdoutExcerpt for structured test results.
|
|
4
|
+
* Supports vitest, jest, mocha. All logic is deterministic regex.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.parseProofOutput = parseProofOutput;
|
|
8
|
+
exports.proofEvidenceString = proofEvidenceString;
|
|
9
|
+
const DOMAIN_TEST_KEYWORDS = {
|
|
10
|
+
auth: ["auth", "login", "logout", "session", "token", "password", "oauth", "signin", "signup"],
|
|
11
|
+
database: ["db", "database", "prisma", "migration", "schema", "query", "model"],
|
|
12
|
+
api: ["api", "route", "endpoint", "handler", "request", "response"],
|
|
13
|
+
payments: ["payment", "billing", "stripe", "invoice", "webhook", "subscription"],
|
|
14
|
+
mcp: ["mcp", "setup", "rules", "config", "agentbridge"],
|
|
15
|
+
ui: ["component", "render", "view", "style", "page", "layout"],
|
|
16
|
+
tests: ["test", "spec"],
|
|
17
|
+
};
|
|
18
|
+
// Vitest / Jest summary: "12 passed", "Tests: 5 passed", "5 failed"
|
|
19
|
+
const PASSED_RE = /(\d+)\s+passed/i;
|
|
20
|
+
const FAILED_RE = /(\d+)\s+failed/i;
|
|
21
|
+
const SKIPPED_RE = /(\d+)\s+skipped/i;
|
|
22
|
+
// Jest alternative: "Tests: 5 passed, 1 failed, 6 total"
|
|
23
|
+
const JEST_SUMMARY_RE = /Tests:\s+(?:(\d+)\s+skipped,\s*)?(?:(\d+)\s+passed)?(?:,\s*(\d+)\s+failed)?/i;
|
|
24
|
+
// Test name lines — vitest uses ✓/✗, jest uses ✓/×/✕, some use √
|
|
25
|
+
const PASS_NAME_RE = /^\s*[✓√]\s+(.+)/;
|
|
26
|
+
const FAIL_NAME_RE = /^\s*[✗×✕✘]\s+(.+)/;
|
|
27
|
+
// Vitest detection: output contains "Test Files" or "Duration" or vitest version header
|
|
28
|
+
const VITEST_DETECT_RE = /Test Files|vitest|v\d+\.\d+\.\d+.*vitest/i;
|
|
29
|
+
// Jest detection
|
|
30
|
+
const JEST_DETECT_RE = /PASS|FAIL|jest|Tests:/;
|
|
31
|
+
// Mocha
|
|
32
|
+
const MOCHA_DETECT_RE = /passing|failing|pending.*mocha/i;
|
|
33
|
+
function detectRunner(stdout) {
|
|
34
|
+
if (VITEST_DETECT_RE.test(stdout))
|
|
35
|
+
return "vitest";
|
|
36
|
+
if (JEST_DETECT_RE.test(stdout))
|
|
37
|
+
return "jest";
|
|
38
|
+
if (MOCHA_DETECT_RE.test(stdout))
|
|
39
|
+
return "mocha";
|
|
40
|
+
return "unknown";
|
|
41
|
+
}
|
|
42
|
+
function extractDomainKeywords(testNames) {
|
|
43
|
+
const found = new Set();
|
|
44
|
+
const lower = testNames.join(" ").toLowerCase();
|
|
45
|
+
for (const [domain, keywords] of Object.entries(DOMAIN_TEST_KEYWORDS)) {
|
|
46
|
+
if (keywords.some((kw) => lower.includes(kw)))
|
|
47
|
+
found.add(domain);
|
|
48
|
+
}
|
|
49
|
+
return [...found];
|
|
50
|
+
}
|
|
51
|
+
function hasDomainCoverage(domain, domainKeywordsInPassed) {
|
|
52
|
+
const aliases = {
|
|
53
|
+
auth: ["auth"],
|
|
54
|
+
database: ["database"],
|
|
55
|
+
api: ["api"],
|
|
56
|
+
payments: ["payments"],
|
|
57
|
+
mcp: ["mcp"],
|
|
58
|
+
ui: ["ui"],
|
|
59
|
+
};
|
|
60
|
+
const check = aliases[domain] ?? [domain];
|
|
61
|
+
return check.some((alias) => domainKeywordsInPassed.includes(alias));
|
|
62
|
+
}
|
|
63
|
+
function parseProofOutput(proofRun) {
|
|
64
|
+
const stdout = proofRun.stdoutExcerpt ?? "";
|
|
65
|
+
const lines = stdout.split("\n");
|
|
66
|
+
const runner = detectRunner(stdout);
|
|
67
|
+
let passed = 0;
|
|
68
|
+
let failed = 0;
|
|
69
|
+
let skipped = 0;
|
|
70
|
+
const test_names_passed = [];
|
|
71
|
+
const test_names_failed = [];
|
|
72
|
+
for (const line of lines) {
|
|
73
|
+
// Count summary
|
|
74
|
+
const pm = PASSED_RE.exec(line);
|
|
75
|
+
if (pm)
|
|
76
|
+
passed = Math.max(passed, parseInt(pm[1], 10));
|
|
77
|
+
const fm = FAILED_RE.exec(line);
|
|
78
|
+
if (fm)
|
|
79
|
+
failed = Math.max(failed, parseInt(fm[1], 10));
|
|
80
|
+
const sm = SKIPPED_RE.exec(line);
|
|
81
|
+
if (sm)
|
|
82
|
+
skipped = Math.max(skipped, parseInt(sm[1], 10));
|
|
83
|
+
// Named test lines
|
|
84
|
+
const passName = PASS_NAME_RE.exec(line);
|
|
85
|
+
if (passName)
|
|
86
|
+
test_names_passed.push(passName[1].trim());
|
|
87
|
+
const failName = FAIL_NAME_RE.exec(line);
|
|
88
|
+
if (failName)
|
|
89
|
+
test_names_failed.push(failName[1].trim());
|
|
90
|
+
}
|
|
91
|
+
const domain_keywords_in_passed = extractDomainKeywords(test_names_passed);
|
|
92
|
+
return {
|
|
93
|
+
runner,
|
|
94
|
+
passed,
|
|
95
|
+
failed,
|
|
96
|
+
skipped,
|
|
97
|
+
test_names_passed,
|
|
98
|
+
test_names_failed,
|
|
99
|
+
domain_keywords_in_passed,
|
|
100
|
+
has_domain_coverage: (domain) => hasDomainCoverage(domain, domain_keywords_in_passed),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Builds a human-readable evidence string from a parsed proof, for use in criterion evaluation.
|
|
105
|
+
*/
|
|
106
|
+
function proofEvidenceString(parsed, domain) {
|
|
107
|
+
const total = parsed.passed + parsed.failed + parsed.skipped;
|
|
108
|
+
const base = `Proof run ${parsed.failed > 0 ? "failed" : "passed"} (${parsed.passed}/${total} tests passed).`;
|
|
109
|
+
if (domain && parsed.passed > 0) {
|
|
110
|
+
if (parsed.has_domain_coverage(domain)) {
|
|
111
|
+
const covering = parsed.domain_keywords_in_passed.join(", ");
|
|
112
|
+
return `${base} Tests covering: ${covering}.`;
|
|
113
|
+
}
|
|
114
|
+
const keywords = DOMAIN_TEST_KEYWORDS[domain]?.slice(0, 3).join(", ") ?? domain;
|
|
115
|
+
return `${base} No passed tests mention ${keywords} — domain coverage unconfirmed.`;
|
|
116
|
+
}
|
|
117
|
+
return base;
|
|
118
|
+
}
|
package/dist/session-state.js
CHANGED
|
@@ -130,6 +130,21 @@ function isLocalSessionState(value) {
|
|
|
130
130
|
return false;
|
|
131
131
|
if (value.claimedPaths !== undefined && !isStringArray(value.claimedPaths))
|
|
132
132
|
return false;
|
|
133
|
+
if (value.createdBy !== undefined && value.createdBy !== "user_start" && value.createdBy !== "agent_hello") {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
if (value.contractProfiles !== undefined && !isStringArray(value.contractProfiles))
|
|
137
|
+
return false;
|
|
138
|
+
if (value.expectedSurfaces !== undefined && !isStringArray(value.expectedSurfaces))
|
|
139
|
+
return false;
|
|
140
|
+
if (value.expectedFileAreas !== undefined && !isStringArray(value.expectedFileAreas))
|
|
141
|
+
return false;
|
|
142
|
+
if (value.completionCriteria !== undefined && !isStringArray(value.completionCriteria))
|
|
143
|
+
return false;
|
|
144
|
+
if (value.proofNeeded !== undefined && !isStringArray(value.proofNeeded))
|
|
145
|
+
return false;
|
|
146
|
+
if (value.riskyOmissions !== undefined && !isStringArray(value.riskyOmissions))
|
|
147
|
+
return false;
|
|
133
148
|
if (value.lastLocalVerificationRun !== undefined &&
|
|
134
149
|
!(0, local_proof_1.isLocalVerificationRun)(value.lastLocalVerificationRun)) {
|
|
135
150
|
return false;
|
package/dist/session.js
CHANGED
|
@@ -6,6 +6,7 @@ exports.openLocalSession = openLocalSession;
|
|
|
6
6
|
exports.closeLocalSession = closeLocalSession;
|
|
7
7
|
const node_child_process_1 = require("node:child_process");
|
|
8
8
|
const http_1 = require("./http");
|
|
9
|
+
const contract_intelligence_1 = require("./contract-intelligence");
|
|
9
10
|
const session_state_1 = require("./session-state");
|
|
10
11
|
const watch_core_1 = require("./watch-core");
|
|
11
12
|
const file_fingerprints_1 = require("./file-fingerprints");
|
|
@@ -54,11 +55,20 @@ function openLocalSession(input) {
|
|
|
54
55
|
const claimedPaths = [...new Set((input.claimedPaths ?? []).map((path) => path.trim()).filter(Boolean))];
|
|
55
56
|
const baseline = captureLocalSessionBaseline();
|
|
56
57
|
const now = baseline.startedAt;
|
|
58
|
+
const createdBy = input.createdBy ?? (input.agentId?.trim() === "local" ? "user_start" : "agent_hello");
|
|
59
|
+
const contract = (0, contract_intelligence_1.buildContractIntelligence)(input.intent?.trim(), createdBy);
|
|
57
60
|
const state = {
|
|
58
61
|
id: `local_${Date.now().toString(36)}`,
|
|
59
62
|
agentId: input.agentId?.trim() || "local",
|
|
60
63
|
laneDomain: input.laneDomain,
|
|
61
64
|
intent: input.intent?.trim() || undefined,
|
|
65
|
+
createdBy,
|
|
66
|
+
contractProfiles: contract.contractProfiles,
|
|
67
|
+
expectedSurfaces: contract.expectedSurfaces,
|
|
68
|
+
expectedFileAreas: contract.expectedFileAreas,
|
|
69
|
+
completionCriteria: contract.completionCriteria,
|
|
70
|
+
proofNeeded: contract.proofNeeded,
|
|
71
|
+
riskyOmissions: contract.riskyOmissions,
|
|
62
72
|
mode: input.mode,
|
|
63
73
|
changeRequestId: input.changeRequestId,
|
|
64
74
|
claimedPaths,
|
package/dist/supervision.js
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.STALE_MCP_ACTIVITY_MINUTES = void 0;
|
|
4
|
+
exports.resolveWatchStartupSupervision = resolveWatchStartupSupervision;
|
|
5
|
+
exports.computeMcpActivityStaleMinutes = computeMcpActivityStaleMinutes;
|
|
6
|
+
exports.effectiveWatchSupervisionStatus = effectiveWatchSupervisionStatus;
|
|
7
|
+
exports.buildWatchSupervisionRenderContext = buildWatchSupervisionRenderContext;
|
|
3
8
|
exports.inferSupervisionWorkType = inferSupervisionWorkType;
|
|
4
9
|
exports.requiredProofHints = requiredProofHints;
|
|
5
10
|
exports.computeScopeDriftAlerts = computeScopeDriftAlerts;
|
|
@@ -8,10 +13,113 @@ exports.fallbackSupervisionSnapshot = fallbackSupervisionSnapshot;
|
|
|
8
13
|
exports.enrichSupervisionWithLocalState = enrichSupervisionWithLocalState;
|
|
9
14
|
exports.supervisionSignature = supervisionSignature;
|
|
10
15
|
exports.renderSupervisionSummary = renderSupervisionSummary;
|
|
16
|
+
const node_fs_1 = require("node:fs");
|
|
17
|
+
const node_path_1 = require("node:path");
|
|
11
18
|
const claimed_paths_1 = require("./claimed-paths");
|
|
12
19
|
const domain_resolution_1 = require("./domain-resolution");
|
|
13
20
|
const preflight_changed_files_1 = require("./preflight-changed-files");
|
|
14
21
|
const memory_context_render_1 = require("./memory-context-render");
|
|
22
|
+
/** Minutes without MCP activity before watch downgrades READY → DEGRADED. */
|
|
23
|
+
exports.STALE_MCP_ACTIVITY_MINUTES = 15;
|
|
24
|
+
function readWatchMcpConfig(workspaceRoot) {
|
|
25
|
+
const mcpConfigPath = (0, node_path_1.resolve)(workspaceRoot, ".cursor", "mcp.json");
|
|
26
|
+
if (!(0, node_fs_1.existsSync)(mcpConfigPath)) {
|
|
27
|
+
return { configured: false, projectId: null };
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
const parsed = JSON.parse((0, node_fs_1.readFileSync)(mcpConfigPath, "utf8"));
|
|
31
|
+
const entry = parsed?.mcpServers?.["agentbridge"] ?? parsed?.mcpServers?.["agentbridge-mcp"];
|
|
32
|
+
return {
|
|
33
|
+
configured: Boolean(entry),
|
|
34
|
+
projectId: entry?.env?.AGENTBRIDGE_PROJECT_ID ?? null,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return { configured: false, projectId: null };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function watchRulesInstalled(workspaceRoot) {
|
|
42
|
+
return ((0, node_fs_1.existsSync)((0, node_path_1.resolve)(workspaceRoot, "AGENTBRIDGE.md")) ||
|
|
43
|
+
(0, node_fs_1.existsSync)((0, node_path_1.resolve)(workspaceRoot, ".cursor", "rules", "agentbridge.mdc")));
|
|
44
|
+
}
|
|
45
|
+
/** Startup decision tree for watch's four-state supervision model. */
|
|
46
|
+
function resolveWatchStartupSupervision(input) {
|
|
47
|
+
const mcp = readWatchMcpConfig(input.workspaceRoot);
|
|
48
|
+
const rulesInstalled = watchRulesInstalled(input.workspaceRoot);
|
|
49
|
+
const credentialMismatch = mcp.configured && mcp.projectId !== null && input.cliProjectId !== undefined
|
|
50
|
+
? mcp.projectId !== input.cliProjectId
|
|
51
|
+
: false;
|
|
52
|
+
const base = {
|
|
53
|
+
mcpConfigured: mcp.configured,
|
|
54
|
+
rulesInstalled,
|
|
55
|
+
mcpProjectId: mcp.projectId,
|
|
56
|
+
credentialMismatch,
|
|
57
|
+
};
|
|
58
|
+
if (credentialMismatch) {
|
|
59
|
+
return {
|
|
60
|
+
...base,
|
|
61
|
+
supervisionStatus: "broken",
|
|
62
|
+
warningBanner: "\n⚠ SUPERVISION BROKEN: CLI and MCP are using different project identities.\n" +
|
|
63
|
+
` CLI project id: ${input.cliProjectId ?? "?"}\n` +
|
|
64
|
+
` MCP project id: ${mcp.projectId ?? "?"}\n` +
|
|
65
|
+
" watch cannot reliably correlate CLI sessions with MCP evidence.\n" +
|
|
66
|
+
" Fix: run `agentbridge connect` to re-write the MCP config with the correct credentials.\n",
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
if (!mcp.configured && !rulesInstalled) {
|
|
70
|
+
return {
|
|
71
|
+
...base,
|
|
72
|
+
supervisionStatus: "blind",
|
|
73
|
+
warningBanner: "\n⚠ SUPERVISION BLIND: MCP is not configured and no AgentBridge rules are installed.\n" +
|
|
74
|
+
" watch can observe filesystem changes only — agent actions inside Cursor are invisible.\n" +
|
|
75
|
+
" Fix: run `agentbridge connect` to configure MCP and install rules automatically.\n",
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
if (!mcp.configured) {
|
|
79
|
+
return {
|
|
80
|
+
...base,
|
|
81
|
+
supervisionStatus: "degraded",
|
|
82
|
+
warningBanner: "\n⚠ SUPERVISION DEGRADED: MCP is not configured.\n" +
|
|
83
|
+
" watch cannot see MCP-recorded evidence, implementation packets, or agent tool usage.\n" +
|
|
84
|
+
" Fix: run `agentbridge connect` or `agentbridge setup-mcp` then restart Cursor.\n",
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
if (!rulesInstalled) {
|
|
88
|
+
return {
|
|
89
|
+
...base,
|
|
90
|
+
supervisionStatus: "degraded",
|
|
91
|
+
warningBanner: "\n⚠ SUPERVISION DEGRADED: AgentBridge rules are not installed in this project.\n" +
|
|
92
|
+
" The agent is not operating under AgentBridge protocol — no task discipline enforced.\n" +
|
|
93
|
+
" Fix: run `agentbridge install-rules` to add rules to this project.\n",
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
return { ...base, supervisionStatus: "ready" };
|
|
97
|
+
}
|
|
98
|
+
function computeMcpActivityStaleMinutes(updatedAt, nowMs = Date.now()) {
|
|
99
|
+
if (updatedAt == null)
|
|
100
|
+
return undefined;
|
|
101
|
+
const updatedMs = new Date(updatedAt).getTime();
|
|
102
|
+
if (Number.isNaN(updatedMs))
|
|
103
|
+
return undefined;
|
|
104
|
+
return Math.floor((nowMs - updatedMs) / 60_000);
|
|
105
|
+
}
|
|
106
|
+
function effectiveWatchSupervisionStatus(baseStatus, mcpConfigured, mcpActivityStaleMinutes) {
|
|
107
|
+
if (baseStatus === "ready" &&
|
|
108
|
+
mcpConfigured &&
|
|
109
|
+
mcpActivityStaleMinutes != null &&
|
|
110
|
+
mcpActivityStaleMinutes > exports.STALE_MCP_ACTIVITY_MINUTES) {
|
|
111
|
+
return "degraded";
|
|
112
|
+
}
|
|
113
|
+
return baseStatus;
|
|
114
|
+
}
|
|
115
|
+
function buildWatchSupervisionRenderContext(input) {
|
|
116
|
+
const mcpActivityStaleMinutes = computeMcpActivityStaleMinutes(input.agentDeclaredUpdatedAt, input.nowMs);
|
|
117
|
+
return {
|
|
118
|
+
supervisionStatus: effectiveWatchSupervisionStatus(input.baseStatus, input.mcpConfigured, mcpActivityStaleMinutes),
|
|
119
|
+
mcpConfigured: input.mcpConfigured,
|
|
120
|
+
mcpActivityStaleMinutes,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
15
123
|
const CLI_GIT_REQUIRED_PROOF = [
|
|
16
124
|
"tracked modified file handled",
|
|
17
125
|
"untracked new file handled",
|
|
@@ -28,6 +136,20 @@ function inferSupervisionWorkType(changedFiles) {
|
|
|
28
136
|
if (changedFiles.length === 0)
|
|
29
137
|
return "unknown";
|
|
30
138
|
const lower = changedFiles.map((file) => file.toLowerCase());
|
|
139
|
+
const isUiSurfaceFile = (file) => {
|
|
140
|
+
if (file.endsWith(".html") ||
|
|
141
|
+
file.endsWith(".css") ||
|
|
142
|
+
file.endsWith(".scss") ||
|
|
143
|
+
file.endsWith(".sass") ||
|
|
144
|
+
file.endsWith(".less") ||
|
|
145
|
+
file.endsWith(".tsx") ||
|
|
146
|
+
file.endsWith(".jsx")) {
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
// Keep this narrow: only treat plain .ts/.js as UI when clearly in public assets.
|
|
150
|
+
return ((file.endsWith(".ts") || file.endsWith(".js")) &&
|
|
151
|
+
(file.startsWith("src/public/") || file.includes("/public/")));
|
|
152
|
+
};
|
|
31
153
|
const cliSignal = lower.some((file) => file.includes("cli/src/") &&
|
|
32
154
|
(file.includes("watch") || file.includes("precommit") || file.includes("revert-crossing")));
|
|
33
155
|
if (cliSignal)
|
|
@@ -35,6 +157,9 @@ function inferSupervisionWorkType(changedFiles) {
|
|
|
35
157
|
const docsOnly = lower.every((file) => file.startsWith("docs/") || file.endsWith(".md") || file.endsWith(".mdx") || file.includes("/docs/"));
|
|
36
158
|
if (docsOnly)
|
|
37
159
|
return "documentation";
|
|
160
|
+
const uiSurfaceOnly = lower.every(isUiSurfaceFile);
|
|
161
|
+
if (uiSurfaceOnly)
|
|
162
|
+
return "ui_copy";
|
|
38
163
|
return "general";
|
|
39
164
|
}
|
|
40
165
|
function requiredProofHints(workType) {
|
|
@@ -42,6 +167,12 @@ function requiredProofHints(workType) {
|
|
|
42
167
|
return [...CLI_GIT_REQUIRED_PROOF];
|
|
43
168
|
if (workType === "documentation")
|
|
44
169
|
return ["explicit verification evidence for changed docs/files"];
|
|
170
|
+
if (workType === "ui_copy") {
|
|
171
|
+
return [
|
|
172
|
+
"visual confirmation/screenshot of updated UI copy",
|
|
173
|
+
"optional quick smoke command: agentbridge verify -- npm run build",
|
|
174
|
+
];
|
|
175
|
+
}
|
|
45
176
|
return [
|
|
46
177
|
"explicit verification evidence for changed files",
|
|
47
178
|
"run agentbridge verify, then rerun agentbridge watch",
|
|
@@ -210,7 +341,7 @@ function enrichSupervisionWithLocalState(state, supervision) {
|
|
|
210
341
|
: supervision.scopeStatus,
|
|
211
342
|
};
|
|
212
343
|
}
|
|
213
|
-
function supervisionSignature(snapshot) {
|
|
344
|
+
function supervisionSignature(snapshot, renderContext) {
|
|
214
345
|
return JSON.stringify({
|
|
215
346
|
workSessionId: snapshot.workSessionId,
|
|
216
347
|
changedFiles: snapshot.changedFiles,
|
|
@@ -225,52 +356,81 @@ function supervisionSignature(snapshot) {
|
|
|
225
356
|
? `${snapshot.agentDeclared.intent ?? ""}:${snapshot.agentDeclared.lastEvent ?? ""}`
|
|
226
357
|
: "",
|
|
227
358
|
declaredDrift: snapshot.declaredDriftFiles?.join(",") ?? "",
|
|
359
|
+
supervisionStatus: renderContext?.supervisionStatus ?? "",
|
|
360
|
+
mcpConfigured: renderContext?.mcpConfigured ?? false,
|
|
361
|
+
mcpActivityStale: renderContext?.mcpActivityStaleMinutes != null
|
|
362
|
+
? renderContext.mcpActivityStaleMinutes > exports.STALE_MCP_ACTIVITY_MINUTES
|
|
363
|
+
: false,
|
|
228
364
|
});
|
|
229
365
|
}
|
|
366
|
+
const SUPERVISION_STATUS_LABEL = {
|
|
367
|
+
ready: "✓ READY",
|
|
368
|
+
degraded: "⚠ DEGRADED",
|
|
369
|
+
blind: "⚠ BLIND",
|
|
370
|
+
broken: "✗ BROKEN",
|
|
371
|
+
};
|
|
230
372
|
function renderSupervisionSummary(snapshot, options) {
|
|
231
373
|
const lines = [];
|
|
232
|
-
lines.push(
|
|
374
|
+
lines.push("AgentBridge watch:");
|
|
375
|
+
lines.push("");
|
|
376
|
+
lines.push("1) Actions performed");
|
|
377
|
+
lines.push(`- Reviewed ${snapshot.changedFiles.length} changed file(s)`);
|
|
378
|
+
lines.push(`- Work type detected: ${snapshot.workType}`);
|
|
379
|
+
lines.push(`- Scope status: ${snapshot.scopeStatus}`);
|
|
380
|
+
lines.push(`- Boundary status: ${snapshot.boundaryStatus}`);
|
|
381
|
+
lines.push(`- Files (disk): ${snapshot.diskObserved?.postBaselineFiles.length ?? snapshot.changedFiles.length} changed since session start`);
|
|
233
382
|
if (!options?.compact) {
|
|
234
|
-
lines.push(
|
|
383
|
+
lines.push(`- Active run: ${snapshot.workSessionId}${snapshot.changeRequestId ? ` (task ${snapshot.changeRequestId})` : ""}`);
|
|
384
|
+
}
|
|
385
|
+
if (options?.supervisionStatus) {
|
|
386
|
+
lines.push(`- Supervision: ${SUPERVISION_STATUS_LABEL[options.supervisionStatus] ?? options.supervisionStatus}`);
|
|
235
387
|
}
|
|
236
388
|
const declared = snapshot.agentDeclared;
|
|
237
389
|
if (declared) {
|
|
238
390
|
const intentLabel = declared.intent?.trim() || declared.summary?.trim() || "(no intent text)";
|
|
239
|
-
|
|
240
|
-
? ` [${declared.lastEvent} @ ${formatMcpEventTime(declared.updatedAt)}]`
|
|
241
|
-
: declared.lastEvent
|
|
242
|
-
? ` [${declared.lastEvent}]`
|
|
243
|
-
: "";
|
|
244
|
-
lines.push(`Agent (MCP): ${intentLabel}${eventSuffix}`);
|
|
391
|
+
lines.push(`- Agent (MCP): ${intentLabel}`);
|
|
245
392
|
if (declared.claimedPaths.length > 0) {
|
|
246
393
|
const scopePreview = declared.claimedPaths.length > 6
|
|
247
394
|
? `${declared.claimedPaths.slice(0, 6).join(", ")} (+${declared.claimedPaths.length - 6} more)`
|
|
248
395
|
: declared.claimedPaths.join(", ");
|
|
249
|
-
lines.push(
|
|
250
|
-
}
|
|
251
|
-
else {
|
|
252
|
-
lines.push("Declared scope: (none yet)");
|
|
396
|
+
lines.push(`- Declared scope: ${scopePreview}`);
|
|
253
397
|
}
|
|
254
398
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
399
|
+
else if (options?.mcpConfigured) {
|
|
400
|
+
lines.push("- Agent (MCP): not yet active");
|
|
401
|
+
}
|
|
402
|
+
lines.push(`- Changed files: ${snapshot.changedFiles.length}`);
|
|
403
|
+
lines.push("");
|
|
404
|
+
lines.push("2) Proof present / missing");
|
|
405
|
+
if (snapshot.decision === "accepted") {
|
|
406
|
+
lines.push("- Present: proof accepted for current changes");
|
|
407
|
+
}
|
|
408
|
+
else if (snapshot.serverAcceptanceUnavailable) {
|
|
409
|
+
lines.push(`- Unknown: proof check unavailable (${snapshot.serverAcceptanceUnavailable})`);
|
|
410
|
+
}
|
|
411
|
+
else {
|
|
412
|
+
lines.push(`- Missing/blocked: watch result is ${snapshot.decision}`);
|
|
262
413
|
}
|
|
263
|
-
lines.push(`Changed files: ${snapshot.changedFiles.length}`);
|
|
264
|
-
lines.push(`Detected work type: ${snapshot.workType}`);
|
|
265
|
-
lines.push(`Scope: ${snapshot.scopeStatus}`);
|
|
266
|
-
lines.push(`Boundary: ${snapshot.boundaryStatus}`);
|
|
267
|
-
lines.push(`Watch result: ${snapshot.decision}`);
|
|
268
414
|
if (snapshot.requiredProof.length > 0) {
|
|
269
|
-
lines.push("");
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
415
|
+
lines.push(`- Still needed: ${snapshot.requiredProof.slice(0, 3).join("; ")}`);
|
|
416
|
+
}
|
|
417
|
+
if (snapshot.declaredVsObservedDrift === "warn" && snapshot.declaredDriftFiles?.length) {
|
|
418
|
+
lines.push(`- Scope warning: ${snapshot.declaredDriftFiles.length} file(s) appear outside declared scope`);
|
|
419
|
+
lines.push("- Note: warn only, not blocked");
|
|
420
|
+
}
|
|
421
|
+
if (snapshot.serverAcceptanceUnavailable) {
|
|
422
|
+
lines.push(`- Watch result unavailable: ${snapshot.serverAcceptanceUnavailable}`);
|
|
423
|
+
}
|
|
424
|
+
lines.push("");
|
|
425
|
+
lines.push("3) Next move");
|
|
426
|
+
lines.push(`- ${snapshot.nextAction}`);
|
|
427
|
+
if (snapshot.driftAlerts.length > 0) {
|
|
428
|
+
lines.push("- Resolve out-of-scope files or explicitly update scope before continuing");
|
|
429
|
+
}
|
|
430
|
+
if (options?.mcpActivityStaleMinutes != null &&
|
|
431
|
+
options.mcpActivityStaleMinutes > exports.STALE_MCP_ACTIVITY_MINUTES) {
|
|
432
|
+
lines.push("- Reconnect or wake MCP activity so supervision returns to ready state");
|
|
433
|
+
lines.push(`- MCP activity stale — last update ${options.mcpActivityStaleMinutes}m ago`);
|
|
274
434
|
}
|
|
275
435
|
if (snapshot.knownTraps.length > 0) {
|
|
276
436
|
lines.push("");
|
|
@@ -282,25 +442,8 @@ function renderSupervisionSummary(snapshot, options) {
|
|
|
282
442
|
const watchMemoryLines = (0, memory_context_render_1.renderWatchMemoryContext)(snapshot.memoryContext);
|
|
283
443
|
if (watchMemoryLines.length > 0) {
|
|
284
444
|
lines.push("");
|
|
445
|
+
lines.push("Context:");
|
|
285
446
|
lines.push(...watchMemoryLines);
|
|
286
447
|
}
|
|
287
|
-
if (snapshot.driftAlerts.length > 0) {
|
|
288
|
-
for (const alert of snapshot.driftAlerts) {
|
|
289
|
-
lines.push("");
|
|
290
|
-
lines.push("Files outside scope detected:");
|
|
291
|
-
lines.push(`- file: ${alert.file}`);
|
|
292
|
-
lines.push(`- claimed paths: [${alert.claimedPaths.join(", ")}]`);
|
|
293
|
-
lines.push(`- likely domain: ${alert.likelyDomain}`);
|
|
294
|
-
lines.push(`- severity: ${alert.severity}`);
|
|
295
|
-
lines.push("- suggested action: update scope or confirm this belongs to the current work");
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
if (snapshot.serverAcceptanceUnavailable) {
|
|
299
|
-
lines.push("");
|
|
300
|
-
lines.push(`Watch result unavailable: ${snapshot.serverAcceptanceUnavailable}`);
|
|
301
|
-
}
|
|
302
|
-
lines.push("");
|
|
303
|
-
lines.push("Before saying done:");
|
|
304
|
-
lines.push(`- ${snapshot.nextAction}`);
|
|
305
448
|
return lines.join("\n");
|
|
306
449
|
}
|