@agentbridge1/cli 0.0.5 → 0.0.7

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.
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveMcpRuntime = resolveMcpRuntime;
4
+ exports.isMcpRuntimeAvailable = isMcpRuntimeAvailable;
5
+ const node_fs_1 = require("node:fs");
6
+ const node_path_1 = require("node:path");
7
+ /** Resolve MCP stdio entrypoint relative to `cli/dist` (index.js directory). */
8
+ function resolveMcpRuntime(cliDistDir) {
9
+ const bundled = (0, node_path_1.resolve)(cliDistDir, "mcp", "agentbridge-mcp.js");
10
+ if ((0, node_fs_1.existsSync)(bundled)) {
11
+ return {
12
+ mode: "bundled",
13
+ path: bundled,
14
+ command: process.execPath,
15
+ args: [bundled],
16
+ };
17
+ }
18
+ const devTs = (0, node_path_1.resolve)(cliDistDir, "..", "..", "mcp", "agentbridge-mcp.ts");
19
+ if ((0, node_fs_1.existsSync)(devTs)) {
20
+ return {
21
+ mode: "dev",
22
+ path: devTs,
23
+ command: "npx",
24
+ args: ["tsx", devTs],
25
+ };
26
+ }
27
+ return null;
28
+ }
29
+ function isMcpRuntimeAvailable(cliDistDir) {
30
+ return resolveMcpRuntime(cliDistDir) !== null;
31
+ }
@@ -1,5 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isPostStartWorkFile = isPostStartWorkFile;
4
+ exports.computeCurrentWorkFiles = computeCurrentWorkFiles;
3
5
  exports.repoDirtySnapshot = repoDirtySnapshot;
4
6
  exports.preflightSyncChangedFiles = preflightSyncChangedFiles;
5
7
  const claimed_paths_1 = require("./claimed-paths");
@@ -7,6 +9,26 @@ const file_fingerprints_1 = require("./file-fingerprints");
7
9
  const git_status_1 = require("./git-status");
8
10
  const session_state_1 = require("./session-state");
9
11
  const server_sync_1 = require("./server-sync");
12
+ /** True when a git-dirty file is genuinely new work within this session (not baseline-only). */
13
+ function isPostStartWorkFile(file, dirtyAtStart, fingerprintsAtStart) {
14
+ if (!dirtyAtStart.has(file))
15
+ return true;
16
+ const fps = (0, file_fingerprints_1.computeFileFingerprints)([file]);
17
+ const current = fps[0];
18
+ if (!current?.exists) {
19
+ return fingerprintsAtStart[file] !== undefined;
20
+ }
21
+ const currentFp = current.sha256 ?? current.mtimeMs?.toString() ?? "";
22
+ const baseFp = fingerprintsAtStart[file];
23
+ return baseFp === undefined || currentFp !== baseFp;
24
+ }
25
+ /** Files dirty now that count as supervised work after session baseline. */
26
+ function computeCurrentWorkFiles(state, dirtyNow) {
27
+ const dirty = normalizeChangedFiles(dirtyNow ?? repoDirtySnapshot());
28
+ const dirtyAtStart = new Set(state.dirtyFilesAtStart ?? []);
29
+ const fingerprintsAtStart = state.fingerprintsAtStart ?? {};
30
+ return dirty.filter((file) => isPostStartWorkFile(file, dirtyAtStart, fingerprintsAtStart));
31
+ }
10
32
  function normalizeChangedFiles(files) {
11
33
  return [...new Set(files.map((file) => file.trim()).filter((file) => file.length > 0))].sort();
12
34
  }
@@ -49,21 +71,6 @@ async function preflightSyncChangedFiles(ctx) {
49
71
  // acquire new content within the session.
50
72
  const dirtyAtStart = new Set(state.dirtyFilesAtStart ?? []);
51
73
  const startFingerprints = state.fingerprintsAtStart ?? {};
52
- /** True when a git-dirty file is genuinely new work within this session. */
53
- function isNewWorkFile(file) {
54
- if (!dirtyAtStart.has(file))
55
- return true; // wasn't dirty at start → always new work
56
- // Was dirty at start – only include if content changed since start
57
- const fps = (0, file_fingerprints_1.computeFileFingerprints)([file]);
58
- const current = fps[0];
59
- if (!current?.exists) {
60
- // File now missing. Treat as new work only when it previously existed at start.
61
- return startFingerprints[file] !== undefined;
62
- }
63
- const currentFp = current.sha256 ?? current.mtimeMs?.toString() ?? "";
64
- const baseFp = startFingerprints[file];
65
- return baseFp === undefined || currentFp !== baseFp;
66
- }
67
74
  const watchOnlyFiles = watchFiles.filter((file) => !gitFileSet.has(file));
68
75
  const watchOnlyFingerprints = (0, file_fingerprints_1.computeFileFingerprints)(watchOnlyFiles);
69
76
  const activeWatchOnlyFiles = watchOnlyFingerprints
@@ -72,9 +79,9 @@ async function preflightSyncChangedFiles(ctx) {
72
79
  const merged = normalizeChangedFiles([
73
80
  ...watchFiles.filter((file) => gitFileSet.has(file)),
74
81
  ...activeWatchOnlyFiles,
75
- ...gitFiles.filter((file) => isNewWorkFile(file) &&
82
+ ...gitFiles.filter((file) => isPostStartWorkFile(file, dirtyAtStart, startFingerprints) &&
76
83
  (claimedPaths.length === 0 || (0, claimed_paths_1.fileWithinClaimedPaths)(file, claimedPaths))),
77
- ...gitFiles.filter((file) => isNewWorkFile(file) &&
84
+ ...gitFiles.filter((file) => isPostStartWorkFile(file, dirtyAtStart, startFingerprints) &&
78
85
  claimedPaths.length > 0 &&
79
86
  !(0, claimed_paths_1.fileWithinClaimedPaths)(file, claimedPaths)),
80
87
  ]);
@@ -0,0 +1,155 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.obligationBlocksAtRollout = obligationBlocksAtRollout;
4
+ exports.getBlockingObligations = getBlockingObligations;
5
+ exports.primaryBlockingObligation = primaryBlockingObligation;
6
+ exports.resolveProofBlockingErrorCode = resolveProofBlockingErrorCode;
7
+ exports.minimumCommandsForReport = minimumCommandsForReport;
8
+ exports.buildObligationSuggestedPrompt = buildObligationSuggestedPrompt;
9
+ exports.renderProofObligationSection = renderProofObligationSection;
10
+ exports.renderProofObligationErrorLines = renderProofObligationErrorLines;
11
+ exports.buildWatchBlockingIssueFromObligation = buildWatchBlockingIssueFromObligation;
12
+ const error_catalog_1 = require("./error-catalog");
13
+ const OBLIGATION_CODE_PRIORITY = [
14
+ "PROOF_TOO_WEAK",
15
+ "PROOF_NOT_RELEVANT",
16
+ "PROOF_IMPACT_COVERAGE_GAP",
17
+ ];
18
+ function obligationBlocksAtRollout(obligation) {
19
+ return (obligation.status === "unsatisfied" &&
20
+ obligation.rollout_stage !== undefined &&
21
+ obligation.rollout_stage !== "warn_only");
22
+ }
23
+ function getBlockingObligations(report) {
24
+ return (report.obligation_set ?? []).filter(obligationBlocksAtRollout);
25
+ }
26
+ function primaryBlockingObligation(report) {
27
+ const blocking = getBlockingObligations(report);
28
+ if (blocking.length === 0)
29
+ return null;
30
+ for (const code of OBLIGATION_CODE_PRIORITY) {
31
+ const match = blocking.find((obligation) => obligation.code === code);
32
+ if (match)
33
+ return match;
34
+ }
35
+ return blocking[0] ?? null;
36
+ }
37
+ function resolveProofBlockingErrorCode(report) {
38
+ const primary = primaryBlockingObligation(report);
39
+ if (primary?.code)
40
+ return primary.code;
41
+ if (report.decision === "needs_proof")
42
+ return "PROOF_MISSING";
43
+ if (report.decision === "stale_evidence")
44
+ return "PROOF_STALE_AFTER_CHANGE";
45
+ return null;
46
+ }
47
+ function uniqueCommands(values) {
48
+ return [...new Set(values.map((value) => value.trim()).filter((value) => value.length > 0))];
49
+ }
50
+ function minimumCommandsForReport(report, obligation) {
51
+ const fromObligation = obligation?.recommended_commands ?? [];
52
+ const fromGuidance = report.proof_guidance?.minimum_commands ?? [];
53
+ const fromBlocking = getBlockingObligations(report).flatMap((item) => item.recommended_commands ?? []);
54
+ return uniqueCommands([...fromObligation, ...fromGuidance, ...fromBlocking]);
55
+ }
56
+ function buildObligationSuggestedPrompt(input) {
57
+ const { obligation, report } = input;
58
+ const files = obligation.applies_to_files.length > 0 ? obligation.applies_to_files : report.changed_files;
59
+ const commands = minimumCommandsForReport(report, obligation);
60
+ const unacceptable = report.proof_guidance?.unacceptable_commands ?? [];
61
+ const lines = [
62
+ obligation.requirement ??
63
+ `Proof obligation ${obligation.code ?? obligation.source} is unsatisfied.`,
64
+ obligation.code === "PROOF_TOO_WEAK" && unacceptable.length > 0
65
+ ? `These commands are too weak for this change: ${unacceptable.join(", ")}`
66
+ : "",
67
+ commands.length > 0
68
+ ? `Run one of these minimum commands:\n${commands.map((command) => `- ${command}`).join("\n")}`
69
+ : "Run a stronger verification command that covers the changed files.",
70
+ files.length > 0 ? `Files: ${files.join(", ")}` : "",
71
+ ].filter((line) => line.length > 0);
72
+ return lines.join("\n");
73
+ }
74
+ function renderProofObligationSection(report) {
75
+ const obligations = report.obligation_set ?? [];
76
+ if (obligations.length === 0 && !report.proof_guidance)
77
+ return [];
78
+ const lines = ["Proof obligations:"];
79
+ if (obligations.length === 0) {
80
+ lines.push(" - none");
81
+ }
82
+ else {
83
+ for (const obligation of obligations) {
84
+ const label = obligation.code ?? obligation.source;
85
+ const rollout = obligation.rollout_stage ? ` (${obligation.rollout_stage})` : "";
86
+ lines.push(` - [${obligation.status}] ${label}${rollout}`);
87
+ if (obligation.requirement) {
88
+ lines.push(` requirement: ${obligation.requirement}`);
89
+ }
90
+ if (obligation.recommended_commands && obligation.recommended_commands.length > 0) {
91
+ for (const command of obligation.recommended_commands) {
92
+ lines.push(` run: ${command}`);
93
+ }
94
+ }
95
+ }
96
+ }
97
+ if (report.proof_guidance) {
98
+ lines.push(`Proof guidance: ${report.proof_guidance.reason}`);
99
+ if (report.proof_guidance.minimum_commands.length > 0) {
100
+ lines.push("Minimum commands:");
101
+ for (const command of report.proof_guidance.minimum_commands) {
102
+ lines.push(` - ${command}`);
103
+ }
104
+ }
105
+ if (report.proof_guidance.unacceptable_commands.length > 0) {
106
+ lines.push("Unacceptable (too weak):");
107
+ for (const command of report.proof_guidance.unacceptable_commands) {
108
+ lines.push(` - ${command}`);
109
+ }
110
+ }
111
+ }
112
+ return lines;
113
+ }
114
+ function renderProofObligationErrorLines(report) {
115
+ const code = resolveProofBlockingErrorCode(report);
116
+ if (!code || code === "PROOF_MISSING" || code === "PROOF_STALE_AFTER_CHANGE") {
117
+ return [];
118
+ }
119
+ const obligation = primaryBlockingObligation(report);
120
+ const entry = (0, error_catalog_1.catalogEntryForCode)(code);
121
+ const commands = minimumCommandsForReport(report, obligation);
122
+ const lines = [`✗ Error code: ${code}`];
123
+ lines.push(` What happened: ${entry?.what ?? "A proof obligation is unsatisfied."}`);
124
+ lines.push(` Why it matters: ${entry?.why ?? "Acceptance requires proof that matches this risk profile."}`);
125
+ if (obligation?.requirement) {
126
+ lines.push(` Obligation: ${obligation.requirement}`);
127
+ }
128
+ if (commands.length > 0) {
129
+ lines.push(" Minimum commands:");
130
+ for (const command of commands) {
131
+ lines.push(` - ${command}`);
132
+ }
133
+ }
134
+ else if (entry?.next) {
135
+ lines.push(` Next action: ${entry.next}`);
136
+ }
137
+ return lines;
138
+ }
139
+ function buildWatchBlockingIssueFromObligation(report, obligation) {
140
+ const code = obligation.code ?? "PROOF_MISSING";
141
+ const entry = (0, error_catalog_1.catalogEntryForCode)(code);
142
+ const files = [
143
+ ...new Set(obligation.applies_to_files.length > 0 ? obligation.applies_to_files : report.changed_files),
144
+ ];
145
+ const commands = minimumCommandsForReport(report, obligation);
146
+ const nextAction = commands[0] ?? entry?.next ?? "Run a stronger verification command for the changed files.";
147
+ return {
148
+ errorCode: code,
149
+ whatHappened: entry?.what ?? obligation.requirement ?? "A proof obligation is unsatisfied.",
150
+ whyItMatters: entry?.why ?? "Acceptance requires proof that matches this change profile.",
151
+ files,
152
+ suggestedPrompt: buildObligationSuggestedPrompt({ obligation, report }),
153
+ nextAction,
154
+ };
155
+ }
@@ -0,0 +1,183 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.normalizeDomainKey = normalizeDomainKey;
4
+ exports.isGenericGeneralDomain = isGenericGeneralDomain;
5
+ exports.activeDomainsFromPacket = activeDomainsFromPacket;
6
+ exports.packetDomainsToRefs = packetDomainsToRefs;
7
+ exports.isLoneGenericGeneral = isLoneGenericGeneral;
8
+ exports.hasMeaningfulDomainMap = hasMeaningfulDomainMap;
9
+ exports.assessRecoveryQuality = assessRecoveryQuality;
10
+ exports.buildReconcilePlan = buildReconcilePlan;
11
+ exports.mergeBootstrapDomainEntries = mergeBootstrapDomainEntries;
12
+ exports.recoveryQualityLabel = recoveryQualityLabel;
13
+ exports.recoveryBaselineRequired = recoveryBaselineRequired;
14
+ exports.recoveryIsBasicPacket = recoveryIsBasicPacket;
15
+ function normalizeDomainKey(name) {
16
+ return name
17
+ .trim()
18
+ .toLowerCase()
19
+ .replace(/[\s-]+/g, "_")
20
+ .replace(/[^a-z0-9_]/g, "");
21
+ }
22
+ function isGenericGeneralDomain(name) {
23
+ const key = normalizeDomainKey(name);
24
+ return key === "general" || key === "misc";
25
+ }
26
+ function activeDomainsFromPacket(packet) {
27
+ return (packet?.domains_summary ?? []).filter((d) => d.state !== "retired");
28
+ }
29
+ function packetDomainsToRefs(packet, configDomains = []) {
30
+ const active = activeDomainsFromPacket(packet);
31
+ const configByKey = new Map(configDomains.map((d) => [normalizeDomainKey(d.domain), d.pathPatterns]));
32
+ return active.map((d) => ({
33
+ name: d.domain_name,
34
+ pathPatterns: configByKey.get(normalizeDomainKey(d.domain_name)) ?? [],
35
+ }));
36
+ }
37
+ function isLoneGenericGeneral(domains) {
38
+ if (domains.length !== 1)
39
+ return false;
40
+ return isGenericGeneralDomain(domains[0]?.name ?? "");
41
+ }
42
+ function hasMeaningfulDomainMap(domains) {
43
+ const nonGeneric = domains.filter((d) => !isGenericGeneralDomain(d.name));
44
+ return nonGeneric.length >= 2 || (nonGeneric.length === 1 && domains.length >= 1 && !isLoneGenericGeneral(domains));
45
+ }
46
+ function domainMapsEqual(a, b) {
47
+ const keysA = new Set(a.map((d) => normalizeDomainKey(d.name)));
48
+ const keysB = new Set(b.map((d) => normalizeDomainKey(d.name)));
49
+ if (keysA.size !== keysB.size)
50
+ return false;
51
+ for (const key of keysA) {
52
+ if (!keysB.has(key))
53
+ return false;
54
+ }
55
+ return true;
56
+ }
57
+ function assessRecoveryQuality(input) {
58
+ const { packet, existingActive, candidates, rulesInstalled } = input;
59
+ const status = packet?.recovery_status ?? null;
60
+ if (!packet ||
61
+ status === "baseline_required" ||
62
+ status === "pending" ||
63
+ status === null ||
64
+ existingActive.length === 0) {
65
+ return "missing";
66
+ }
67
+ if (isLoneGenericGeneral(existingActive)) {
68
+ const scanHasSpecific = candidates.some((d) => !isGenericGeneralDomain(d.name));
69
+ if (scanHasSpecific) {
70
+ return "stale";
71
+ }
72
+ return "basic";
73
+ }
74
+ const candidateNonGeneric = candidates.filter((d) => !isGenericGeneralDomain(d.name));
75
+ const existingNonGeneric = existingActive.filter((d) => !isGenericGeneralDomain(d.name));
76
+ if (existingNonGeneric.length === 0 && candidateNonGeneric.length > 0) {
77
+ return "stale";
78
+ }
79
+ const candidateKeys = new Set(candidates.map((d) => normalizeDomainKey(d.name)));
80
+ const existingKeys = new Set(existingActive.map((d) => normalizeDomainKey(d.name)));
81
+ const missingFromStored = candidateNonGeneric.filter((c) => !existingKeys.has(normalizeDomainKey(c.name)));
82
+ const orphanStored = existingNonGeneric.filter((e) => !candidateKeys.has(normalizeDomainKey(e.name)));
83
+ if (missingFromStored.length > 0 && existingNonGeneric.length > 0) {
84
+ return orphanStored.length > 0 ? "mismatch" : "stale";
85
+ }
86
+ if (!hasMeaningfulDomainMap(existingActive)) {
87
+ return "basic";
88
+ }
89
+ const hasCharter = Boolean(packet.charter_summary?.purpose?.trim());
90
+ if (!hasCharter || rulesInstalled === false) {
91
+ return "needs_review";
92
+ }
93
+ if (domainMapsEqual(existingActive, candidates)) {
94
+ return "complete";
95
+ }
96
+ if (missingFromStored.length > 0) {
97
+ return "stale";
98
+ }
99
+ return "complete";
100
+ }
101
+ function buildReconcilePlan(input) {
102
+ const { existingActive, candidates, mode, packet, rulesInstalled } = input;
103
+ const existingByKey = new Map(existingActive.map((d) => [normalizeDomainKey(d.name), d]));
104
+ const candidateByKey = new Map(candidates.map((d) => [normalizeDomainKey(d.name), d]));
105
+ const toAdd = candidates.filter((c) => !existingByKey.has(normalizeDomainKey(c.name)));
106
+ const unchanged = existingActive.filter((e) => candidateByKey.has(normalizeDomainKey(e.name)));
107
+ let toRetire = [];
108
+ let toPreserve = [];
109
+ if (mode === "force") {
110
+ toRetire = existingActive.filter((e) => !candidateByKey.has(normalizeDomainKey(e.name)));
111
+ toPreserve = [];
112
+ }
113
+ else {
114
+ const upgradingFromGeneral = isLoneGenericGeneral(existingActive) &&
115
+ candidates.some((c) => !isGenericGeneralDomain(c.name));
116
+ if (upgradingFromGeneral) {
117
+ toRetire = existingActive.filter((e) => isGenericGeneralDomain(e.name));
118
+ toPreserve = existingActive.filter((e) => !isGenericGeneralDomain(e.name));
119
+ }
120
+ else {
121
+ toPreserve = existingActive.filter((e) => !toAdd.some((a) => normalizeDomainKey(a.name) === normalizeDomainKey(e.name)));
122
+ }
123
+ }
124
+ const hasChanges = toAdd.length > 0 ||
125
+ toRetire.length > 0 ||
126
+ (mode === "force" && !domainMapsEqual(existingActive, candidates));
127
+ const quality = assessRecoveryQuality({
128
+ packet,
129
+ existingActive,
130
+ candidates,
131
+ rulesInstalled,
132
+ });
133
+ return {
134
+ mode,
135
+ candidates,
136
+ existingActive,
137
+ toAdd,
138
+ toPreserve,
139
+ toRetire,
140
+ unchanged,
141
+ hasChanges,
142
+ quality,
143
+ };
144
+ }
145
+ function mergeBootstrapDomainEntries(plan, candidateEntries, existingEntries) {
146
+ const byKey = new Map();
147
+ const addEntry = (entry) => {
148
+ const name = typeof entry.name === "string" ? entry.name : "";
149
+ if (!name)
150
+ return;
151
+ byKey.set(normalizeDomainKey(name), entry);
152
+ };
153
+ if (plan.mode === "force") {
154
+ for (const entry of candidateEntries)
155
+ addEntry(entry);
156
+ return Array.from(byKey.values());
157
+ }
158
+ for (const entry of existingEntries)
159
+ addEntry(entry);
160
+ for (const entry of candidateEntries)
161
+ addEntry(entry);
162
+ if (plan.toRetire.some((d) => isGenericGeneralDomain(d.name)) &&
163
+ plan.toAdd.some((d) => !isGenericGeneralDomain(d.name))) {
164
+ byKey.delete("general");
165
+ byKey.delete("misc");
166
+ }
167
+ return Array.from(byKey.values());
168
+ }
169
+ function recoveryQualityLabel(quality) {
170
+ return quality;
171
+ }
172
+ function recoveryBaselineRequired(packet) {
173
+ if (!packet || packet.project_mode !== "recovery")
174
+ return false;
175
+ const status = packet.recovery_status ?? null;
176
+ return status === "baseline_required" || status === "pending" || status === null;
177
+ }
178
+ function recoveryIsBasicPacket(packet) {
179
+ const active = packetDomainsToRefs(packet);
180
+ if (active.length === 0)
181
+ return false;
182
+ return isLoneGenericGeneral(active) || !hasMeaningfulDomainMap(active);
183
+ }
@@ -0,0 +1,138 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PROTOCOL_RULES_MARKER = exports.LOCAL_RULES_HEADER = void 0;
4
+ exports.rulesArtifactPaths = rulesArtifactPaths;
5
+ exports.extractProjectIdFromRulesText = extractProjectIdFromRulesText;
6
+ exports.classifyRulesFormat = classifyRulesFormat;
7
+ exports.analyzeRulesArtifacts = analyzeRulesArtifacts;
8
+ exports.fetchProjectRules = fetchProjectRules;
9
+ exports.writeRulesFiles = writeRulesFiles;
10
+ exports.markProjectRulesInstalled = markProjectRulesInstalled;
11
+ exports.extractProjectNameFromRulesResponse = extractProjectNameFromRulesResponse;
12
+ const node_fs_1 = require("node:fs");
13
+ const node_path_1 = require("node:path");
14
+ const http_1 = require("./http");
15
+ exports.LOCAL_RULES_HEADER = "# AgentBridge Local Rules";
16
+ exports.PROTOCOL_RULES_MARKER = "This repo is coordinated through AgentBridge";
17
+ function rulesArtifactPaths(workspaceRoot) {
18
+ return {
19
+ rulesMarkdownPath: (0, node_path_1.resolve)(workspaceRoot, "AGENTBRIDGE.md"),
20
+ rulesMdcPath: (0, node_path_1.resolve)(workspaceRoot, ".cursor", "rules", "agentbridge.mdc"),
21
+ };
22
+ }
23
+ function extractProjectIdFromRulesText(content) {
24
+ const markdownMatch = content.match(/\*\*Project ID:\*\*\s*`([^`]+)`/);
25
+ if (markdownMatch?.[1]) {
26
+ return markdownMatch[1].trim();
27
+ }
28
+ const inlineMatch = content.match(/project ID:\s*`([^`]+)`/i);
29
+ if (inlineMatch?.[1]) {
30
+ return inlineMatch[1].trim();
31
+ }
32
+ return null;
33
+ }
34
+ function classifyRulesFormat(markdownContent, mdcContent) {
35
+ const md = markdownContent ?? "";
36
+ const mdc = mdcContent ?? "";
37
+ if (!md.trim() && !mdc.trim()) {
38
+ return "missing";
39
+ }
40
+ if (md.startsWith(exports.LOCAL_RULES_HEADER) || mdc.includes("Recovered domains:")) {
41
+ return "local";
42
+ }
43
+ if (md.includes(exports.PROTOCOL_RULES_MARKER) ||
44
+ mdc.includes("AgentBridge Protocol Rules") ||
45
+ mdc.includes("AgentBridge protocol rules")) {
46
+ return "protocol";
47
+ }
48
+ return "unknown";
49
+ }
50
+ function readOptionalFile(path) {
51
+ if (!(0, node_fs_1.existsSync)(path))
52
+ return null;
53
+ return (0, node_fs_1.readFileSync)(path, "utf8");
54
+ }
55
+ function analyzeRulesArtifacts(workspaceRoot, expectedProjectId) {
56
+ const paths = rulesArtifactPaths(workspaceRoot);
57
+ const markdownContent = readOptionalFile(paths.rulesMarkdownPath);
58
+ const mdcContent = readOptionalFile(paths.rulesMdcPath);
59
+ const filesPresent = markdownContent !== null && mdcContent !== null;
60
+ const format = classifyRulesFormat(markdownContent, mdcContent);
61
+ const projectIds = new Set();
62
+ if (markdownContent) {
63
+ const id = extractProjectIdFromRulesText(markdownContent);
64
+ if (id)
65
+ projectIds.add(id);
66
+ }
67
+ if (mdcContent) {
68
+ const id = extractProjectIdFromRulesText(mdcContent);
69
+ if (id)
70
+ projectIds.add(id);
71
+ }
72
+ const issues = [];
73
+ let projectIdInRepo = null;
74
+ if (projectIds.size === 1) {
75
+ projectIdInRepo = [...projectIds][0] ?? null;
76
+ }
77
+ else if (projectIds.size > 1) {
78
+ issues.push("AGENTBRIDGE.md and .cursor/rules/agentbridge.mdc reference different project IDs.");
79
+ projectIdInRepo = [...projectIds][0] ?? null;
80
+ }
81
+ else if (filesPresent) {
82
+ issues.push("Rules files are present but no project ID could be parsed.");
83
+ }
84
+ let projectIdMatchesConfig = null;
85
+ if (expectedProjectId) {
86
+ if (projectIdInRepo) {
87
+ projectIdMatchesConfig = projectIdInRepo === expectedProjectId;
88
+ if (!projectIdMatchesConfig) {
89
+ issues.push(`Rules reference project ${projectIdInRepo}, but configured project is ${expectedProjectId}.`);
90
+ }
91
+ }
92
+ else if (filesPresent) {
93
+ projectIdMatchesConfig = false;
94
+ }
95
+ }
96
+ if (!filesPresent) {
97
+ issues.push("Missing AGENTBRIDGE.md or .cursor/rules/agentbridge.mdc.");
98
+ }
99
+ else if (format === "local") {
100
+ issues.push("Rules are in local watch format (from agentbridge init), not server protocol rules.");
101
+ }
102
+ else if (format === "unknown") {
103
+ issues.push("Rules files exist but do not match the expected AgentBridge protocol format.");
104
+ }
105
+ const protocolRulesValid = filesPresent &&
106
+ format === "protocol" &&
107
+ (projectIdMatchesConfig ?? true) &&
108
+ issues.length === 0;
109
+ return {
110
+ paths,
111
+ filesPresent,
112
+ format,
113
+ projectIdInRepo,
114
+ projectIdMatchesConfig,
115
+ protocolRulesValid,
116
+ issues,
117
+ };
118
+ }
119
+ async function fetchProjectRules(ctx) {
120
+ return (0, http_1.getJson)(ctx, `/v1/dev/projects/${encodeURIComponent(ctx.projectId)}/rules`);
121
+ }
122
+ function writeRulesFiles(workspaceRoot, files) {
123
+ for (const file of files) {
124
+ const fullPath = (0, node_path_1.resolve)(workspaceRoot, file.path);
125
+ (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(fullPath), { recursive: true });
126
+ (0, node_fs_1.writeFileSync)(fullPath, file.content, "utf8");
127
+ }
128
+ }
129
+ async function markProjectRulesInstalled(ctx) {
130
+ return (0, http_1.postJson)(ctx, `/v1/dev/projects/${encodeURIComponent(ctx.projectId)}/rules/mark-installed`, {});
131
+ }
132
+ function extractProjectNameFromRulesResponse(files) {
133
+ const markdown = files.find((file) => file.path === "AGENTBRIDGE.md")?.content;
134
+ if (!markdown)
135
+ return null;
136
+ const match = markdown.match(/^# AgentBridge — (.+)$/m);
137
+ return match?.[1]?.trim() ?? null;
138
+ }
@@ -12,6 +12,7 @@ exports.postObservedDiff = postObservedDiff;
12
12
  exports.postScopedApproval = postScopedApproval;
13
13
  exports.closeServerSession = closeServerSession;
14
14
  exports.fetchAcceptanceCheck = fetchAcceptanceCheck;
15
+ exports.fetchAgentProofGuidance = fetchAgentProofGuidance;
15
16
  exports.reviewOnceForWatch = reviewOnceForWatch;
16
17
  exports.isWatchFastPathUnavailable = isWatchFastPathUnavailable;
17
18
  exports.fetchMemorySuggestion = fetchMemorySuggestion;
@@ -252,6 +253,29 @@ async function fetchAcceptanceCheck(ctx, options) {
252
253
  const query = queryParams.toString();
253
254
  return (0, http_1.getJson)(ctx, `/v1/dev/projects/${encodeURIComponent(ctx.projectId)}/work-sessions/current/acceptance-check${query ? `?${query}` : ""}`);
254
255
  }
256
+ async function fetchAgentProofGuidance(ctx, options) {
257
+ const rolloutParams = new URLSearchParams();
258
+ if (options?.rolloutProofTooWeak) {
259
+ rolloutParams.set("rollout_proof_too_weak", options.rolloutProofTooWeak);
260
+ }
261
+ if (options?.rolloutProofNotRelevant) {
262
+ rolloutParams.set("rollout_proof_not_relevant", options.rolloutProofNotRelevant);
263
+ }
264
+ if (options?.rolloutImpactCoverageGap) {
265
+ rolloutParams.set("rollout_impact_coverage_gap", options.rolloutImpactCoverageGap);
266
+ }
267
+ const rolloutSuffix = rolloutParams.toString();
268
+ if (options?.workSessionId) {
269
+ const qs = rolloutSuffix ? `?${rolloutSuffix}` : "";
270
+ return (0, http_1.getJson)(ctx, `/v1/dev/projects/${encodeURIComponent(ctx.projectId)}/work-sessions/${encodeURIComponent(options.workSessionId)}/proof-guidance${qs}`);
271
+ }
272
+ const queryParams = new URLSearchParams(rolloutParams);
273
+ if (options?.changeRequestId && options.changeRequestId.trim().length > 0) {
274
+ queryParams.set("change_request_id", options.changeRequestId);
275
+ }
276
+ const query = queryParams.toString();
277
+ return (0, http_1.getJson)(ctx, `/v1/dev/projects/${encodeURIComponent(ctx.projectId)}/work-sessions/current/proof-guidance${query ? `?${query}` : ""}`);
278
+ }
255
279
  /**
256
280
  * One-shot Agent Watch review fast path. Collapses cold-create / resume +
257
281
  * observed-diff sync + acceptance check into a single bounded server call.