@agentbridge1/cli 0.0.4 → 0.0.6
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/accept.js +4 -4
- package/dist/commands/check.js +10 -1
- package/dist/commands/connect.js +192 -13
- package/dist/commands/doctor.js +163 -29
- package/dist/commands/proof-guidance.js +30 -0
- package/dist/commands/recover.js +171 -22
- package/dist/commands/setup-mcp.js +22 -1
- package/dist/commands/start.js +57 -63
- package/dist/commands/verify.js +124 -91
- package/dist/commands/watch.js +428 -113
- package/dist/error-catalog.js +57 -16
- package/dist/gates.js +3 -3
- package/dist/git-evidence.js +2 -0
- package/dist/http.js +29 -0
- package/dist/index.js +47 -30
- package/dist/init.js +204 -30
- package/dist/local-memory.js +33 -0
- package/dist/local-proof.js +158 -0
- package/dist/local-session-mirror.js +247 -0
- package/dist/local-supervision.js +250 -0
- package/dist/mcp/agentbridge-mcp.js +22947 -0
- package/dist/mcp/agentbridge-mcp.js.map +7 -0
- package/dist/mcp-runtime.js +31 -0
- package/dist/preflight-changed-files.js +24 -17
- package/dist/proof-obligations.js +155 -0
- package/dist/recovery-reconcile.js +183 -0
- package/dist/server-sync.js +36 -0
- package/dist/session-state.js +119 -21
- package/dist/session.js +9 -2
- package/dist/supervision.js +100 -6
- package/package.json +5 -2
package/dist/init.js
CHANGED
|
@@ -5,6 +5,7 @@ exports.refineRecoveredDomains = refineRecoveredDomains;
|
|
|
5
5
|
exports.writeLocalRulesArtifacts = writeLocalRulesArtifacts;
|
|
6
6
|
exports.buildBootstrapPayloadFromEvidence = buildBootstrapPayloadFromEvidence;
|
|
7
7
|
exports.runInit = runInit;
|
|
8
|
+
exports.scanRecoveryFromRepo = scanRecoveryFromRepo;
|
|
8
9
|
exports.runBootstrapRecovery = runBootstrapRecovery;
|
|
9
10
|
const promises_1 = require("node:readline/promises");
|
|
10
11
|
const node_child_process_1 = require("node:child_process");
|
|
@@ -16,6 +17,7 @@ const git_evidence_1 = require("./git-evidence");
|
|
|
16
17
|
const config_1 = require("./config");
|
|
17
18
|
const watch_core_1 = require("./watch-core");
|
|
18
19
|
const precommit_1 = require("./precommit");
|
|
20
|
+
const recovery_reconcile_1 = require("./recovery-reconcile");
|
|
19
21
|
const WORKFLOW_SUMMARY = "Recover domains from repository evidence, map ownership boundaries, enforce lane discipline, and require authority requests before cross-domain edits.";
|
|
20
22
|
const KNOWN_DOMAIN_LABELS = {
|
|
21
23
|
communications: "Communications",
|
|
@@ -104,25 +106,83 @@ function assertInsideGitRepo() {
|
|
|
104
106
|
function normalizeToken(value) {
|
|
105
107
|
return value.trim().toLowerCase().replace(/[_\s]+/g, "-");
|
|
106
108
|
}
|
|
107
|
-
function
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
109
|
+
function normalizePath(path) {
|
|
110
|
+
return path.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
111
|
+
}
|
|
112
|
+
function deriveDomainKeyFromPath(path) {
|
|
113
|
+
const normalized = normalizePath(path);
|
|
114
|
+
const packageSourcesMatch = normalized.match(/^([^/]+)\/Sources\/([^/]+)\//);
|
|
115
|
+
if (packageSourcesMatch) {
|
|
116
|
+
const folder = normalizeToken(packageSourcesMatch[2] ?? "");
|
|
117
|
+
if (folder === "supabase")
|
|
118
|
+
return "database";
|
|
119
|
+
return folder || null;
|
|
120
|
+
}
|
|
121
|
+
const srcLikeMatch = normalized.match(/^(src|Sources|modules|Modules|feature|features)\/([^/]+)\//);
|
|
122
|
+
if (srcLikeMatch) {
|
|
123
|
+
const folder = normalizeToken(srcLikeMatch[2] ?? "");
|
|
112
124
|
if (folder === "supabase")
|
|
113
125
|
return "database";
|
|
114
|
-
|
|
115
|
-
return folder;
|
|
116
|
-
return null;
|
|
126
|
+
return folder || null;
|
|
117
127
|
}
|
|
118
128
|
if (normalized.startsWith("db/") || normalized.startsWith("prisma/") || normalized.startsWith("supabase/")) {
|
|
119
129
|
return "database";
|
|
120
130
|
}
|
|
131
|
+
if (normalized.startsWith("backend/supabase/") ||
|
|
132
|
+
normalized.includes("/supabase/functions/") ||
|
|
133
|
+
normalized.includes("/supabase/migrations/")) {
|
|
134
|
+
return "database";
|
|
135
|
+
}
|
|
121
136
|
if (/^supabase_.*\.sql$/i.test(normalized)) {
|
|
122
137
|
return "database";
|
|
123
138
|
}
|
|
139
|
+
const xcodeAppMatch = normalized.match(/^([^/]+)\/\1\//);
|
|
140
|
+
if (xcodeAppMatch) {
|
|
141
|
+
const appName = normalizeToken(xcodeAppMatch[1] ?? "");
|
|
142
|
+
if (appName)
|
|
143
|
+
return appName;
|
|
144
|
+
}
|
|
145
|
+
const topLevelMatch = normalized.match(/^([^/]+)\//);
|
|
146
|
+
if (topLevelMatch) {
|
|
147
|
+
const top = normalizeToken(topLevelMatch[1] ?? "");
|
|
148
|
+
if (top === "backend")
|
|
149
|
+
return "backend";
|
|
150
|
+
}
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
function extractDomainPrefix(path) {
|
|
154
|
+
const normalized = normalizePath(path);
|
|
155
|
+
const packageSourcesMatch = normalized.match(/^([^/]+)\/Sources\/([^/]+)\//);
|
|
156
|
+
if (packageSourcesMatch) {
|
|
157
|
+
return `${packageSourcesMatch[1]}/Sources/${packageSourcesMatch[2]}/`;
|
|
158
|
+
}
|
|
159
|
+
const srcLikeMatch = normalized.match(/^(src|Sources|modules|Modules|feature|features)\/([^/]+)\//);
|
|
160
|
+
if (srcLikeMatch) {
|
|
161
|
+
return `${srcLikeMatch[1]}/${srcLikeMatch[2]}/`;
|
|
162
|
+
}
|
|
163
|
+
if (normalized.startsWith("db/"))
|
|
164
|
+
return "db/";
|
|
165
|
+
if (normalized.startsWith("prisma/"))
|
|
166
|
+
return "prisma/";
|
|
167
|
+
if (normalized.startsWith("supabase/"))
|
|
168
|
+
return "supabase/";
|
|
169
|
+
if (normalized.startsWith("backend/supabase/"))
|
|
170
|
+
return "backend/supabase/";
|
|
171
|
+
if (/^supabase_.*\.sql$/i.test(normalized))
|
|
172
|
+
return "supabase_*.sql";
|
|
173
|
+
const xcodeAppMatch = normalized.match(/^([^/]+)\/\1\//);
|
|
174
|
+
if (xcodeAppMatch) {
|
|
175
|
+
return `${xcodeAppMatch[1]}/${xcodeAppMatch[1]}/`;
|
|
176
|
+
}
|
|
177
|
+
const topLevelMatch = normalized.match(/^([^/]+)\//);
|
|
178
|
+
if (topLevelMatch?.[1] === "backend") {
|
|
179
|
+
return "backend/";
|
|
180
|
+
}
|
|
124
181
|
return null;
|
|
125
182
|
}
|
|
183
|
+
function classifyDomainKey(path) {
|
|
184
|
+
return deriveDomainKeyFromPath(path);
|
|
185
|
+
}
|
|
126
186
|
function tierForDomainKey(domainKey, files) {
|
|
127
187
|
if (domainKey === "sessions") {
|
|
128
188
|
const highRisk = files.some((file) => /(token|auth|state|orchestrator|repository|session)/i.test(file));
|
|
@@ -148,9 +208,11 @@ function confidenceForDomain(fileCount) {
|
|
|
148
208
|
}
|
|
149
209
|
function collectWorkspaceDomainSeeds() {
|
|
150
210
|
const seeds = new Set();
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
211
|
+
for (const root of ["src", "Sources", "modules", "Modules", "feature", "features"]) {
|
|
212
|
+
const rootPath = (0, node_path_1.resolve)(process.cwd(), root);
|
|
213
|
+
if (!(0, node_fs_1.existsSync)(rootPath))
|
|
214
|
+
continue;
|
|
215
|
+
for (const entry of (0, node_fs_1.readdirSync)(rootPath, { withFileTypes: true })) {
|
|
154
216
|
if (!entry.isDirectory())
|
|
155
217
|
continue;
|
|
156
218
|
const folder = normalizeToken(entry.name);
|
|
@@ -162,19 +224,53 @@ function collectWorkspaceDomainSeeds() {
|
|
|
162
224
|
}
|
|
163
225
|
}
|
|
164
226
|
}
|
|
227
|
+
for (const topLevelEntry of (0, node_fs_1.readdirSync)(process.cwd(), { withFileTypes: true })) {
|
|
228
|
+
if (!topLevelEntry.isDirectory())
|
|
229
|
+
continue;
|
|
230
|
+
const packageSourcesPath = (0, node_path_1.resolve)(process.cwd(), topLevelEntry.name, "Sources");
|
|
231
|
+
if (!(0, node_fs_1.existsSync)(packageSourcesPath))
|
|
232
|
+
continue;
|
|
233
|
+
for (const sourceEntry of (0, node_fs_1.readdirSync)(packageSourcesPath, { withFileTypes: true })) {
|
|
234
|
+
if (!sourceEntry.isDirectory())
|
|
235
|
+
continue;
|
|
236
|
+
const folder = normalizeToken(sourceEntry.name);
|
|
237
|
+
if (folder === "supabase") {
|
|
238
|
+
seeds.add("database");
|
|
239
|
+
}
|
|
240
|
+
else if (folder) {
|
|
241
|
+
seeds.add(folder);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
165
245
|
for (const dbFolder of ["db", "prisma", "supabase"]) {
|
|
166
246
|
if ((0, node_fs_1.existsSync)((0, node_path_1.resolve)(process.cwd(), dbFolder))) {
|
|
167
247
|
seeds.add("database");
|
|
168
248
|
}
|
|
169
249
|
}
|
|
250
|
+
if ((0, node_fs_1.existsSync)((0, node_path_1.resolve)(process.cwd(), "backend", "supabase"))) {
|
|
251
|
+
seeds.add("database");
|
|
252
|
+
}
|
|
253
|
+
for (const topLevelEntry of (0, node_fs_1.readdirSync)(process.cwd(), { withFileTypes: true })) {
|
|
254
|
+
if (!topLevelEntry.isDirectory())
|
|
255
|
+
continue;
|
|
256
|
+
const nestedSameName = (0, node_path_1.resolve)(process.cwd(), topLevelEntry.name, topLevelEntry.name);
|
|
257
|
+
if ((0, node_fs_1.existsSync)(nestedSameName)) {
|
|
258
|
+
const folder = normalizeToken(topLevelEntry.name);
|
|
259
|
+
if (folder)
|
|
260
|
+
seeds.add(folder);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
170
263
|
return Array.from(seeds);
|
|
171
264
|
}
|
|
172
265
|
function refineRecoveredDomains(inferredClusters, evidence, architectureDomains) {
|
|
173
266
|
const domainFiles = new Map();
|
|
267
|
+
const domainPrefixes = new Map();
|
|
174
268
|
const workspaceSeeds = collectWorkspaceDomainSeeds();
|
|
175
269
|
for (const seed of workspaceSeeds) {
|
|
176
270
|
if (!domainFiles.has(seed))
|
|
177
271
|
domainFiles.set(seed, new Set());
|
|
272
|
+
if (!domainPrefixes.has(seed))
|
|
273
|
+
domainPrefixes.set(seed, new Set());
|
|
178
274
|
}
|
|
179
275
|
for (const file of evidence.files) {
|
|
180
276
|
const key = classifyDomainKey(file);
|
|
@@ -183,6 +279,12 @@ function refineRecoveredDomains(inferredClusters, evidence, architectureDomains)
|
|
|
183
279
|
if (!domainFiles.has(key))
|
|
184
280
|
domainFiles.set(key, new Set());
|
|
185
281
|
domainFiles.get(key)?.add(file);
|
|
282
|
+
const prefix = extractDomainPrefix(file);
|
|
283
|
+
if (prefix) {
|
|
284
|
+
if (!domainPrefixes.has(key))
|
|
285
|
+
domainPrefixes.set(key, new Set());
|
|
286
|
+
domainPrefixes.get(key)?.add(prefix);
|
|
287
|
+
}
|
|
186
288
|
}
|
|
187
289
|
for (const cluster of inferredClusters) {
|
|
188
290
|
const clusterPaths = [...cluster.files, ...cluster.path_prefixes];
|
|
@@ -207,6 +309,12 @@ function refineRecoveredDomains(inferredClusters, evidence, architectureDomains)
|
|
|
207
309
|
for (const file of cluster.files) {
|
|
208
310
|
if (classifyDomainKey(file) === chosenKey) {
|
|
209
311
|
domainFiles.get(chosenKey)?.add(file);
|
|
312
|
+
const prefix = extractDomainPrefix(file);
|
|
313
|
+
if (prefix) {
|
|
314
|
+
if (!domainPrefixes.has(chosenKey))
|
|
315
|
+
domainPrefixes.set(chosenKey, new Set());
|
|
316
|
+
domainPrefixes.get(chosenKey)?.add(prefix);
|
|
317
|
+
}
|
|
210
318
|
}
|
|
211
319
|
}
|
|
212
320
|
}
|
|
@@ -237,12 +345,14 @@ function refineRecoveredDomains(inferredClusters, evidence, architectureDomains)
|
|
|
237
345
|
return Array.from(domainFiles.entries())
|
|
238
346
|
.map(([key, filesSet]) => {
|
|
239
347
|
const files = Array.from(filesSet).sort((a, b) => a.localeCompare(b));
|
|
348
|
+
const inferredPrefixes = Array.from(domainPrefixes.get(key) ?? []);
|
|
240
349
|
const pathPatterns = DOMAIN_PREFIX_BY_KEY[key] ??
|
|
350
|
+
(inferredPrefixes.length > 0 ? inferredPrefixes : undefined) ??
|
|
241
351
|
(key === "database"
|
|
242
352
|
? ["db/", "prisma/", "supabase/", "supabase_*.sql"]
|
|
243
353
|
: key === "shared"
|
|
244
354
|
? ["src/shared/"]
|
|
245
|
-
: [
|
|
355
|
+
: [`${key}/`]);
|
|
246
356
|
const primaryPrefix = pathPatterns[0] ? pathPatterns[0].replace(/\/$/, "") : key;
|
|
247
357
|
const protectionTier = tierForDomainKey(key, files);
|
|
248
358
|
return {
|
|
@@ -298,14 +408,42 @@ function toCursorRule(domains) {
|
|
|
298
408
|
"",
|
|
299
409
|
].join("\n");
|
|
300
410
|
}
|
|
301
|
-
function writeLocalRulesArtifacts(domains) {
|
|
411
|
+
function writeLocalRulesArtifacts(domains, opts = {}) {
|
|
302
412
|
const md = toRulesMarkdown(domains);
|
|
303
413
|
const cursorRule = toCursorRule(domains);
|
|
304
414
|
const rootFile = (0, node_path_1.resolve)(process.cwd(), "AGENTBRIDGE.md");
|
|
305
415
|
const cursorRulesDir = (0, node_path_1.resolve)(process.cwd(), ".cursor", "rules");
|
|
416
|
+
const cursorFile = (0, node_path_1.resolve)(cursorRulesDir, "agentbridge.mdc");
|
|
417
|
+
const mdTarget = `${md}\n`;
|
|
418
|
+
const cursorTarget = `${cursorRule}\n`;
|
|
419
|
+
if ((0, node_fs_1.existsSync)(rootFile) && (0, node_fs_1.existsSync)(cursorFile) && !opts.force) {
|
|
420
|
+
const existingMd = (0, node_fs_1.readFileSync)(rootFile, "utf8");
|
|
421
|
+
const existingCursor = (0, node_fs_1.readFileSync)(cursorFile, "utf8");
|
|
422
|
+
if (existingMd === mdTarget && existingCursor === cursorTarget) {
|
|
423
|
+
return { wrote: false, skipped: true, reason: "already up to date" };
|
|
424
|
+
}
|
|
425
|
+
if (!existingMd.startsWith("# AgentBridge Local Rules") ||
|
|
426
|
+
!existingCursor.includes("Recovered domains:")) {
|
|
427
|
+
return { wrote: false, skipped: true, reason: "local edits preserved" };
|
|
428
|
+
}
|
|
429
|
+
}
|
|
306
430
|
(0, node_fs_1.mkdirSync)(cursorRulesDir, { recursive: true });
|
|
307
|
-
(0, node_fs_1.writeFileSync)(rootFile,
|
|
308
|
-
(0, node_fs_1.writeFileSync)(
|
|
431
|
+
(0, node_fs_1.writeFileSync)(rootFile, mdTarget, "utf8");
|
|
432
|
+
(0, node_fs_1.writeFileSync)(cursorFile, cursorTarget, "utf8");
|
|
433
|
+
return { wrote: true, skipped: false };
|
|
434
|
+
}
|
|
435
|
+
function refsToBootstrapDomains(refs) {
|
|
436
|
+
return refs.map((ref) => ({
|
|
437
|
+
name: ref.name,
|
|
438
|
+
pathPatterns: ref.pathPatterns.length > 0 ? ref.pathPatterns : [`${ref.name.toLowerCase()}/`],
|
|
439
|
+
files: [],
|
|
440
|
+
evidenceSource: "git_history",
|
|
441
|
+
confidence: 0.72,
|
|
442
|
+
protectionTier: (0, watch_core_1.inferTierFromDomainName)(ref.name),
|
|
443
|
+
knownTraps: [],
|
|
444
|
+
relatedDomains: [],
|
|
445
|
+
openRisks: [],
|
|
446
|
+
}));
|
|
309
447
|
}
|
|
310
448
|
async function buildBootstrapPayloadFromEvidence(ctx, productSummary, architectureDomains, evidence) {
|
|
311
449
|
const clusterResult = await (0, http_1.postJson)(ctx, `/v1/dev/projects/${encodeURIComponent(ctx.projectId)}/recovery/evidence`, evidence);
|
|
@@ -416,16 +554,12 @@ async function runInit(ctx) {
|
|
|
416
554
|
rl.close();
|
|
417
555
|
}
|
|
418
556
|
}
|
|
419
|
-
async function
|
|
557
|
+
async function scanRecoveryFromRepo(ctx, opts = {}) {
|
|
420
558
|
assertInsideGitRepo();
|
|
421
|
-
const runtimeCliPath = process.argv[1] ? (0, node_path_1.resolve)(process.argv[1]) : undefined;
|
|
422
|
-
const hookCliPath = runtimeCliPath ?? (0, node_path_1.resolve)(__dirname, "index.js");
|
|
423
559
|
const productSummary = opts.productSummary?.trim() || "Recovered existing repository baseline for supervised agent work.";
|
|
424
560
|
const architectureDomains = opts.architectureDomains ?? [];
|
|
425
561
|
const collectedEvidence = (0, git_evidence_1.collectGitEvidence)();
|
|
426
|
-
const
|
|
427
|
-
const { inferredClusters, payload: bootstrapPayload } = await buildBootstrapPayloadFromEvidence(ctx, productSummary, architectureDomains, evidence);
|
|
428
|
-
await (0, http_1.postJson)(ctx, `/v1/dev/projects/${encodeURIComponent(ctx.projectId)}/bootstrap`, bootstrapPayload);
|
|
562
|
+
const { inferredClusters, payload: bootstrapPayload } = await buildBootstrapPayloadFromEvidence(ctx, productSummary, architectureDomains, collectedEvidence.payload);
|
|
429
563
|
const recoveredDomains = inferredClusters
|
|
430
564
|
.filter(isValidAuthorityDomain)
|
|
431
565
|
.map((cluster) => ({
|
|
@@ -434,25 +568,65 @@ async function runBootstrapRecovery(ctx, opts = {}) {
|
|
|
434
568
|
ownerAgentId: `${cluster.suggested_name} Agent`,
|
|
435
569
|
tier: cluster.protection_tier ?? (0, watch_core_1.inferTierFromDomainName)(cluster.suggested_name),
|
|
436
570
|
}));
|
|
571
|
+
const candidateRefs = recoveredDomains.map((domain) => ({
|
|
572
|
+
name: domain.domain,
|
|
573
|
+
pathPatterns: domain.pathPatterns,
|
|
574
|
+
}));
|
|
575
|
+
return { inferredClusters, bootstrapPayload, recoveredDomains, candidateRefs };
|
|
576
|
+
}
|
|
577
|
+
async function runBootstrapRecovery(ctx, opts = {}) {
|
|
578
|
+
assertInsideGitRepo();
|
|
579
|
+
const runtimeCliPath = process.argv[1] ? (0, node_path_1.resolve)(process.argv[1]) : undefined;
|
|
580
|
+
const hookCliPath = runtimeCliPath ?? (0, node_path_1.resolve)(__dirname, "index.js");
|
|
581
|
+
const scan = opts.scan ??
|
|
582
|
+
(await scanRecoveryFromRepo(ctx, {
|
|
583
|
+
productSummary: opts.productSummary,
|
|
584
|
+
architectureDomains: opts.architectureDomains,
|
|
585
|
+
}));
|
|
586
|
+
const { inferredClusters, bootstrapPayload, recoveredDomains } = scan;
|
|
587
|
+
if (opts.reconcilePlan) {
|
|
588
|
+
const candidateBootstrap = toBootstrapDomains(scan.candidateRefs.map((ref) => ({
|
|
589
|
+
name: ref.name,
|
|
590
|
+
files: [],
|
|
591
|
+
pathPatterns: ref.pathPatterns,
|
|
592
|
+
confidence: 0.72,
|
|
593
|
+
protectionTier: (0, watch_core_1.inferTierFromDomainName)(ref.name),
|
|
594
|
+
knownTraps: [],
|
|
595
|
+
rationale: "candidate from repository scan",
|
|
596
|
+
})));
|
|
597
|
+
const existingBootstrap = refsToBootstrapDomains(opts.reconcilePlan.existingActive);
|
|
598
|
+
bootstrapPayload.domains = (0, recovery_reconcile_1.mergeBootstrapDomainEntries)(opts.reconcilePlan, candidateBootstrap, existingBootstrap);
|
|
599
|
+
}
|
|
600
|
+
await (0, http_1.postJson)(ctx, `/v1/dev/projects/${encodeURIComponent(ctx.projectId)}/bootstrap`, bootstrapPayload);
|
|
601
|
+
const payloadDomains = Array.isArray(bootstrapPayload.domains)
|
|
602
|
+
? bootstrapPayload.domains
|
|
603
|
+
: [];
|
|
604
|
+
const payloadDomainKeys = new Set(payloadDomains.map((d) => (d.name ?? "").trim().toLowerCase()).filter(Boolean));
|
|
605
|
+
const mergedLocalDomains = payloadDomainKeys.size > 0
|
|
606
|
+
? recoveredDomains.filter((d) => payloadDomainKeys.has(d.domain.trim().toLowerCase()))
|
|
607
|
+
: recoveredDomains;
|
|
437
608
|
(0, config_1.updateConfig)({
|
|
438
609
|
apiBaseUrl: ctx.apiBaseUrl,
|
|
439
610
|
projectId: ctx.projectId,
|
|
440
|
-
domains:
|
|
611
|
+
domains: (0, watch_core_1.toDomainOwnershipInput)(mergedLocalDomains),
|
|
441
612
|
cliPath: runtimeCliPath,
|
|
442
613
|
});
|
|
443
|
-
writeLocalRulesArtifacts(
|
|
614
|
+
writeLocalRulesArtifacts(mergedLocalDomains.map((domain) => ({
|
|
444
615
|
domain: domain.domain,
|
|
445
616
|
owner: domain.ownerAgentId,
|
|
446
617
|
tier: domain.tier,
|
|
447
|
-
})));
|
|
618
|
+
})), { force: opts.reconcilePlan?.mode === "force" });
|
|
448
619
|
if (opts.installHook) {
|
|
449
620
|
(0, precommit_1.installPrecommitHookWithPath)(hookCliPath);
|
|
450
621
|
(0, config_1.updateConfig)({ precommitHookInstalled: true, cliPath: runtimeCliPath });
|
|
451
622
|
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
623
|
+
if (!opts.reconcilePlan) {
|
|
624
|
+
node_process_1.stdout.write([
|
|
625
|
+
"Recovery baseline created.",
|
|
626
|
+
`Domains mapped: ${mergedLocalDomains.length}.`,
|
|
627
|
+
"Project context and local rules are now ready.",
|
|
628
|
+
"",
|
|
629
|
+
].join("\n"));
|
|
630
|
+
}
|
|
631
|
+
return { recoveredDomains: mergedLocalDomains };
|
|
458
632
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.writeLocalMemory = writeLocalMemory;
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const node_path_1 = require("node:path");
|
|
6
|
+
const MEMORY_DIR = (0, node_path_1.resolve)(process.cwd(), ".agentbridge", "memory");
|
|
7
|
+
function summarizeProof(state) {
|
|
8
|
+
const run = state.lastLocalVerificationRun;
|
|
9
|
+
if (!run) {
|
|
10
|
+
return state.changedFiles.length > 0 ? { status: "missing" } : { status: "none" };
|
|
11
|
+
}
|
|
12
|
+
if (run.exitCode !== 0) {
|
|
13
|
+
return { status: "failed", command: run.command, finishedAt: run.finishedAt };
|
|
14
|
+
}
|
|
15
|
+
return { status: "ok", command: run.command, finishedAt: run.finishedAt };
|
|
16
|
+
}
|
|
17
|
+
function writeLocalMemory(state, options) {
|
|
18
|
+
const closedAt = state.closedAt ?? new Date().toISOString();
|
|
19
|
+
const record = {
|
|
20
|
+
sessionId: state.id,
|
|
21
|
+
intent: state.intent?.trim() || "Untitled task",
|
|
22
|
+
mode: state.mode ?? "local_supervision",
|
|
23
|
+
changedFiles: [...state.changedFiles].sort(),
|
|
24
|
+
proofSummary: summarizeProof(state),
|
|
25
|
+
openedAt: state.startedAt ?? state.createdAt,
|
|
26
|
+
closedAt,
|
|
27
|
+
cloudSync: options.cloudSync,
|
|
28
|
+
};
|
|
29
|
+
(0, node_fs_1.mkdirSync)(MEMORY_DIR, { recursive: true });
|
|
30
|
+
const filePath = (0, node_path_1.resolve)(MEMORY_DIR, `${state.id}.json`);
|
|
31
|
+
(0, node_fs_1.writeFileSync)(filePath, `${JSON.stringify(record, null, 2)}\n`, "utf8");
|
|
32
|
+
return filePath;
|
|
33
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isProofNoiseFile = isProofNoiseFile;
|
|
4
|
+
exports.normalizeProofMatchingFileSet = normalizeProofMatchingFileSet;
|
|
5
|
+
exports.fingerprintSetKeyForPaths = fingerprintSetKeyForPaths;
|
|
6
|
+
exports.evaluateLocalProof = evaluateLocalProof;
|
|
7
|
+
exports.buildLocalProofBlockingIssue = buildLocalProofBlockingIssue;
|
|
8
|
+
exports.isLocalVerificationRun = isLocalVerificationRun;
|
|
9
|
+
const file_fingerprints_1 = require("./file-fingerprints");
|
|
10
|
+
function normalizePath(path) {
|
|
11
|
+
return path.trim().replaceAll("\\", "/");
|
|
12
|
+
}
|
|
13
|
+
function isProofNoiseFile(file) {
|
|
14
|
+
const normalized = normalizePath(file).toLowerCase();
|
|
15
|
+
return (normalized === "agentbridge.md" ||
|
|
16
|
+
normalized === ".cursor" ||
|
|
17
|
+
normalized.startsWith(".cursor/"));
|
|
18
|
+
}
|
|
19
|
+
function normalizeProofMatchingFileSet(files) {
|
|
20
|
+
return [...new Set(files.map(normalizePath).filter((file) => file.length > 0 && !isProofNoiseFile(file)))].sort();
|
|
21
|
+
}
|
|
22
|
+
function fingerprintValue(fingerprint) {
|
|
23
|
+
if (!fingerprint.exists)
|
|
24
|
+
return "missing";
|
|
25
|
+
if (fingerprint.sha256)
|
|
26
|
+
return `sha:${fingerprint.sha256}`;
|
|
27
|
+
return `meta:${fingerprint.fileType ?? "unknown"}:${fingerprint.size ?? -1}:${Math.trunc(fingerprint.mtimeMs ?? -1)}`;
|
|
28
|
+
}
|
|
29
|
+
function fingerprintSetKeyForPaths(paths, fingerprints) {
|
|
30
|
+
const normalizedPaths = normalizeProofMatchingFileSet(paths);
|
|
31
|
+
if (normalizedPaths.length === 0)
|
|
32
|
+
return "";
|
|
33
|
+
const map = new Map(fingerprints.map((fingerprint) => [normalizePath(fingerprint.path), fingerprint]));
|
|
34
|
+
const entries = [];
|
|
35
|
+
for (const path of normalizedPaths) {
|
|
36
|
+
const fingerprint = map.get(path);
|
|
37
|
+
if (!fingerprint)
|
|
38
|
+
return null;
|
|
39
|
+
entries.push(`${path}=>${fingerprintValue(fingerprint)}`);
|
|
40
|
+
}
|
|
41
|
+
return entries.join("\n");
|
|
42
|
+
}
|
|
43
|
+
function evaluateLocalProof(changedFiles, lastRun) {
|
|
44
|
+
const normalizedChanged = normalizeProofMatchingFileSet(changedFiles);
|
|
45
|
+
if (normalizedChanged.length === 0) {
|
|
46
|
+
return { decision: "ok", changedFiles: [], staleFiles: [] };
|
|
47
|
+
}
|
|
48
|
+
if (!lastRun) {
|
|
49
|
+
return { decision: "needs_proof", changedFiles: normalizedChanged, staleFiles: [] };
|
|
50
|
+
}
|
|
51
|
+
if (lastRun.status !== "passed") {
|
|
52
|
+
return { decision: "failed", changedFiles: normalizedChanged, staleFiles: normalizedChanged };
|
|
53
|
+
}
|
|
54
|
+
const currentKey = fingerprintSetKeyForPaths(normalizedChanged, (0, file_fingerprints_1.computeFileFingerprints)(normalizedChanged));
|
|
55
|
+
const proofKey = fingerprintSetKeyForPaths(lastRun.proofScopeFiles, lastRun.proofScopeFingerprints);
|
|
56
|
+
if (currentKey === null || proofKey === null || currentKey !== proofKey) {
|
|
57
|
+
const proofScopeSet = new Set(normalizeProofMatchingFileSet(lastRun.proofScopeFiles));
|
|
58
|
+
const staleFiles = normalizedChanged.filter((file) => {
|
|
59
|
+
if (!proofScopeSet.has(file))
|
|
60
|
+
return true;
|
|
61
|
+
const currentFp = (0, file_fingerprints_1.computeFileFingerprints)([file])[0];
|
|
62
|
+
const proofFp = lastRun.proofScopeFingerprints.find((fp) => normalizePath(fp.path) === file);
|
|
63
|
+
if (!proofFp)
|
|
64
|
+
return true;
|
|
65
|
+
return fingerprintValue(currentFp) !== fingerprintValue(proofFp);
|
|
66
|
+
});
|
|
67
|
+
return {
|
|
68
|
+
decision: "stale_evidence",
|
|
69
|
+
changedFiles: normalizedChanged,
|
|
70
|
+
staleFiles: staleFiles.length > 0 ? staleFiles : normalizedChanged,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return { decision: "ok", changedFiles: normalizedChanged, staleFiles: [] };
|
|
74
|
+
}
|
|
75
|
+
function buildLocalProofBlockingIssue(evaluation) {
|
|
76
|
+
const summarizePromptFiles = (files) => {
|
|
77
|
+
const unique = [...new Set(files)];
|
|
78
|
+
if (unique.length === 0)
|
|
79
|
+
return "current files";
|
|
80
|
+
const visible = unique.slice(0, 3);
|
|
81
|
+
if (unique.length <= 3)
|
|
82
|
+
return visible.join(", ");
|
|
83
|
+
return `${visible.join(", ")} (+${unique.length - visible.length} more)`;
|
|
84
|
+
};
|
|
85
|
+
if (evaluation.decision === "ok")
|
|
86
|
+
return null;
|
|
87
|
+
if (evaluation.decision === "stale_evidence") {
|
|
88
|
+
const staleFiles = [...new Set(evaluation.staleFiles)];
|
|
89
|
+
return {
|
|
90
|
+
errorCode: "PROOF_STALE_AFTER_CHANGE",
|
|
91
|
+
whatHappened: "Verification proof is stale because files changed after the last passing verify.",
|
|
92
|
+
whyItMatters: "Proof must match the final edited files before you can trust the agent is done.",
|
|
93
|
+
files: staleFiles,
|
|
94
|
+
suggestedPrompt: [
|
|
95
|
+
"Your proof is stale because files changed after verification.",
|
|
96
|
+
"Rerun verification after your final edit: agentbridge verify -- <test command>",
|
|
97
|
+
`Files: ${summarizePromptFiles(staleFiles)}`,
|
|
98
|
+
].join("\n"),
|
|
99
|
+
nextAction: "Run: agentbridge verify -- <your test command>",
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
if (evaluation.decision === "failed") {
|
|
103
|
+
return {
|
|
104
|
+
errorCode: "VERIFICATION_FAILED",
|
|
105
|
+
whatHappened: "The last verification command failed.",
|
|
106
|
+
whyItMatters: "Failed verification cannot count as proof.",
|
|
107
|
+
files: evaluation.changedFiles,
|
|
108
|
+
suggestedPrompt: [
|
|
109
|
+
"The last verification command failed.",
|
|
110
|
+
"Fix the failure and rerun verification with a passing result: agentbridge verify -- <test command>",
|
|
111
|
+
].join("\n"),
|
|
112
|
+
nextAction: "Run: agentbridge verify -- <your test command>",
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
const files = [...new Set(evaluation.changedFiles)];
|
|
116
|
+
return {
|
|
117
|
+
errorCode: "PROOF_MISSING",
|
|
118
|
+
whatHappened: "Coding changes were detected with no passing verification proof.",
|
|
119
|
+
whyItMatters: "Changed files need a recorded verify command before they are safe to trust.",
|
|
120
|
+
files,
|
|
121
|
+
suggestedPrompt: [
|
|
122
|
+
`AgentBridge found ${files.length} changed files without proof.`,
|
|
123
|
+
"Run verification for the changed files: agentbridge verify -- <test command>",
|
|
124
|
+
`Files: ${summarizePromptFiles(files)}`,
|
|
125
|
+
].join("\n"),
|
|
126
|
+
nextAction: "Run: agentbridge verify -- <your test command>",
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
function isLocalVerificationRun(value) {
|
|
130
|
+
if (typeof value !== "object" || value === null)
|
|
131
|
+
return false;
|
|
132
|
+
const row = value;
|
|
133
|
+
if (typeof row.command !== "string" || row.command.length === 0)
|
|
134
|
+
return false;
|
|
135
|
+
if (typeof row.startedAt !== "string" || row.startedAt.length === 0)
|
|
136
|
+
return false;
|
|
137
|
+
if (typeof row.finishedAt !== "string" || row.finishedAt.length === 0)
|
|
138
|
+
return false;
|
|
139
|
+
if (typeof row.exitCode !== "number" || !Number.isFinite(row.exitCode))
|
|
140
|
+
return false;
|
|
141
|
+
if (typeof row.stdoutExcerpt !== "string")
|
|
142
|
+
return false;
|
|
143
|
+
if (typeof row.stderrExcerpt !== "string")
|
|
144
|
+
return false;
|
|
145
|
+
if (typeof row.gitHead !== "string")
|
|
146
|
+
return false;
|
|
147
|
+
if (!Array.isArray(row.repoDirtySnapshot) || !row.repoDirtySnapshot.every((entry) => typeof entry === "string")) {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
if (!Array.isArray(row.proofScopeFiles) || !row.proofScopeFiles.every((entry) => typeof entry === "string")) {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
if (!Array.isArray(row.proofScopeFingerprints))
|
|
154
|
+
return false;
|
|
155
|
+
if (row.status !== "passed" && row.status !== "failed")
|
|
156
|
+
return false;
|
|
157
|
+
return true;
|
|
158
|
+
}
|