@agentbridge1/cli 0.0.1

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.
Files changed (69) hide show
  1. package/bin/agentbridge.js +11 -0
  2. package/dist/acceptance-block.js +21 -0
  3. package/dist/acceptance-preflight.js +91 -0
  4. package/dist/api-client.js +6 -0
  5. package/dist/authority-request.js +25 -0
  6. package/dist/briefing.js +26 -0
  7. package/dist/bug-registry.js +350 -0
  8. package/dist/build-info.json +6 -0
  9. package/dist/canonical-state.js +11 -0
  10. package/dist/claimed-paths.js +42 -0
  11. package/dist/cli-failure-log.js +34 -0
  12. package/dist/commands/accept.js +241 -0
  13. package/dist/commands/attention.js +85 -0
  14. package/dist/commands/autopilot.js +93 -0
  15. package/dist/commands/bug.js +106 -0
  16. package/dist/commands/check.js +283 -0
  17. package/dist/commands/connect.js +159 -0
  18. package/dist/commands/dist-freshness.js +105 -0
  19. package/dist/commands/doctor.js +300 -0
  20. package/dist/commands/done.js +292 -0
  21. package/dist/commands/handoff.js +189 -0
  22. package/dist/commands/handshake.js +78 -0
  23. package/dist/commands/health.js +154 -0
  24. package/dist/commands/identity.js +57 -0
  25. package/dist/commands/init.js +5 -0
  26. package/dist/commands/memory.js +400 -0
  27. package/dist/commands/next.js +21 -0
  28. package/dist/commands/precommit-check.js +17 -0
  29. package/dist/commands/recover.js +116 -0
  30. package/dist/commands/session.js +229 -0
  31. package/dist/commands/setup-mcp.js +56 -0
  32. package/dist/commands/start.js +626 -0
  33. package/dist/commands/status.js +486 -0
  34. package/dist/commands/use.js +13 -0
  35. package/dist/commands/verify.js +264 -0
  36. package/dist/commands/version.js +32 -0
  37. package/dist/commands/watch.js +1718 -0
  38. package/dist/config.js +55 -0
  39. package/dist/domain-resolution.js +63 -0
  40. package/dist/error-catalog.js +494 -0
  41. package/dist/errors.js +276 -0
  42. package/dist/file-fingerprints.js +45 -0
  43. package/dist/gates.js +200 -0
  44. package/dist/git-evidence.js +285 -0
  45. package/dist/git-status.js +81 -0
  46. package/dist/http.js +151 -0
  47. package/dist/index.js +622 -0
  48. package/dist/init.js +458 -0
  49. package/dist/memory-context-render.js +51 -0
  50. package/dist/operator-snapshot.js +99 -0
  51. package/dist/precommit.js +72 -0
  52. package/dist/preflight-changed-files.js +109 -0
  53. package/dist/proof-guidance.js +110 -0
  54. package/dist/redact-secrets.js +15 -0
  55. package/dist/revert-crossing.js +73 -0
  56. package/dist/server-sync.js +433 -0
  57. package/dist/session-state.js +138 -0
  58. package/dist/session.js +89 -0
  59. package/dist/supervision.js +212 -0
  60. package/dist/terminal-ui.js +18 -0
  61. package/dist/test-runner.js +62 -0
  62. package/dist/types.js +2 -0
  63. package/dist/verification-conditions.js +185 -0
  64. package/dist/watch-core.js +208 -0
  65. package/dist/watch-packet-handshake.js +71 -0
  66. package/dist/watcher.js +62 -0
  67. package/dist/work-context-resolver.js +412 -0
  68. package/dist/work-contract.js +110 -0
  69. package/package.json +44 -0
package/dist/init.js ADDED
@@ -0,0 +1,458 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.assertInsideGitRepo = assertInsideGitRepo;
4
+ exports.refineRecoveredDomains = refineRecoveredDomains;
5
+ exports.writeLocalRulesArtifacts = writeLocalRulesArtifacts;
6
+ exports.buildBootstrapPayloadFromEvidence = buildBootstrapPayloadFromEvidence;
7
+ exports.runInit = runInit;
8
+ exports.runBootstrapRecovery = runBootstrapRecovery;
9
+ const promises_1 = require("node:readline/promises");
10
+ const node_child_process_1 = require("node:child_process");
11
+ const node_fs_1 = require("node:fs");
12
+ const node_path_1 = require("node:path");
13
+ const node_process_1 = require("node:process");
14
+ const http_1 = require("./http");
15
+ const git_evidence_1 = require("./git-evidence");
16
+ const config_1 = require("./config");
17
+ const watch_core_1 = require("./watch-core");
18
+ const precommit_1 = require("./precommit");
19
+ const WORKFLOW_SUMMARY = "Recover domains from repository evidence, map ownership boundaries, enforce lane discipline, and require authority requests before cross-domain edits.";
20
+ const KNOWN_DOMAIN_LABELS = {
21
+ communications: "Communications",
22
+ diagnosis: "Diagnosis",
23
+ dispatch: "Dispatch",
24
+ "photo-upload": "Photo Upload",
25
+ "live-ui": "Live UI",
26
+ ops: "Ops",
27
+ plumber: "Plumber",
28
+ "pm-report": "PM Report",
29
+ sessions: "Sessions",
30
+ database: "Supabase / Database",
31
+ shared: "Shared",
32
+ };
33
+ const KNOWN_DOMAIN_TIERS = {
34
+ communications: "tier_b",
35
+ diagnosis: "tier_b",
36
+ dispatch: "tier_b",
37
+ "photo-upload": "tier_b",
38
+ "live-ui": "tier_b",
39
+ ops: "tier_b",
40
+ plumber: "tier_b",
41
+ "pm-report": "tier_b",
42
+ sessions: "tier_b",
43
+ database: "tier_a",
44
+ shared: "tier_c",
45
+ };
46
+ const DOMAIN_PREFIX_BY_KEY = {
47
+ communications: ["src/communications/"],
48
+ diagnosis: ["src/diagnosis/"],
49
+ dispatch: ["src/dispatch/"],
50
+ "photo-upload": ["src/photo-upload/"],
51
+ "live-ui": ["src/live-ui/"],
52
+ ops: ["src/ops/"],
53
+ plumber: ["src/plumber/"],
54
+ "pm-report": ["src/pm-report/"],
55
+ sessions: ["src/sessions/"],
56
+ database: ["src/supabase/", "db/", "prisma/", "supabase/", "supabase_*.sql"],
57
+ shared: ["src/shared/"],
58
+ };
59
+ function toBootstrapDomains(domains) {
60
+ return domains.map((domain) => ({
61
+ name: domain.name,
62
+ pathPatterns: domain.pathPatterns,
63
+ files: domain.files,
64
+ evidenceSource: "git_history",
65
+ confidence: domain.confidence,
66
+ protectionTier: domain.protectionTier,
67
+ knownTraps: domain.knownTraps,
68
+ relatedDomains: [],
69
+ openRisks: [],
70
+ }));
71
+ }
72
+ function toTitle(value) {
73
+ return value
74
+ .split(/[\s_-]+/)
75
+ .filter(Boolean)
76
+ .map((part) => part[0]?.toUpperCase() + part.slice(1))
77
+ .join(" ");
78
+ }
79
+ function describeCluster(cluster) {
80
+ const tier = (cluster.protection_tier ?? (0, watch_core_1.inferTierFromDomainName)(cluster.suggested_name)).toUpperCase();
81
+ const confidence = Math.round((cluster.confidence ?? 0.5) * 100);
82
+ const reason = cluster.rationale
83
+ ? cluster.rationale
84
+ : `files changed together under ${cluster.path_prefixes[0] ?? "mixed paths"}`;
85
+ return `- ${toTitle(cluster.suggested_name)} (${tier}, ${confidence}% confidence): ${reason}`;
86
+ }
87
+ function isValidAuthorityDomain(cluster) {
88
+ const hasOwnedFiles = cluster.files.length > 0 || cluster.path_prefixes.length > 0;
89
+ return hasOwnedFiles && cluster.confidence != null && cluster.confidence > 0;
90
+ }
91
+ function summarizeSkipped(skippedByReason) {
92
+ const top = Object.entries(skippedByReason)
93
+ .sort((a, b) => b[1] - a[1])
94
+ .slice(0, 5);
95
+ if (top.length === 0)
96
+ return "none";
97
+ return top.map(([reason, count]) => `${reason}=${count}`).join(", ");
98
+ }
99
+ function assertInsideGitRepo() {
100
+ (0, node_child_process_1.execFileSync)("git", ["rev-parse", "--is-inside-work-tree"], {
101
+ stdio: ["ignore", "pipe", "pipe"],
102
+ });
103
+ }
104
+ function normalizeToken(value) {
105
+ return value.trim().toLowerCase().replace(/[_\s]+/g, "-");
106
+ }
107
+ function classifyDomainKey(path) {
108
+ const normalized = path.replace(/\\/g, "/");
109
+ const srcMatch = normalized.match(/^src\/([^/]+)\//);
110
+ if (srcMatch) {
111
+ const folder = normalizeToken(srcMatch[1] ?? "");
112
+ if (folder === "supabase")
113
+ return "database";
114
+ if (folder)
115
+ return folder;
116
+ return null;
117
+ }
118
+ if (normalized.startsWith("db/") || normalized.startsWith("prisma/") || normalized.startsWith("supabase/")) {
119
+ return "database";
120
+ }
121
+ if (/^supabase_.*\.sql$/i.test(normalized)) {
122
+ return "database";
123
+ }
124
+ return null;
125
+ }
126
+ function tierForDomainKey(domainKey, files) {
127
+ if (domainKey === "sessions") {
128
+ const highRisk = files.some((file) => /(token|auth|state|orchestrator|repository|session)/i.test(file));
129
+ return highRisk ? "tier_a" : "tier_b";
130
+ }
131
+ return KNOWN_DOMAIN_TIERS[domainKey] ?? "tier_b";
132
+ }
133
+ function knownTrapsForDomainKey(domainKey) {
134
+ if (domainKey === "database") {
135
+ return ["Schema changes can impact production state and migrations."];
136
+ }
137
+ if (domainKey === "sessions") {
138
+ return ["Session orchestration changes can disrupt active workflows."];
139
+ }
140
+ if (domainKey === "shared") {
141
+ return ["Shared contracts require handshake with dependent domains."];
142
+ }
143
+ return [];
144
+ }
145
+ function confidenceForDomain(fileCount) {
146
+ const confidence = 0.55 + Math.min(0.38, fileCount * 0.04);
147
+ return Number(confidence.toFixed(2));
148
+ }
149
+ function collectWorkspaceDomainSeeds() {
150
+ const seeds = new Set();
151
+ const srcPath = (0, node_path_1.resolve)(process.cwd(), "src");
152
+ if ((0, node_fs_1.existsSync)(srcPath)) {
153
+ for (const entry of (0, node_fs_1.readdirSync)(srcPath, { withFileTypes: true })) {
154
+ if (!entry.isDirectory())
155
+ continue;
156
+ const folder = normalizeToken(entry.name);
157
+ if (folder === "supabase") {
158
+ seeds.add("database");
159
+ }
160
+ else if (folder) {
161
+ seeds.add(folder);
162
+ }
163
+ }
164
+ }
165
+ for (const dbFolder of ["db", "prisma", "supabase"]) {
166
+ if ((0, node_fs_1.existsSync)((0, node_path_1.resolve)(process.cwd(), dbFolder))) {
167
+ seeds.add("database");
168
+ }
169
+ }
170
+ return Array.from(seeds);
171
+ }
172
+ function refineRecoveredDomains(inferredClusters, evidence, architectureDomains) {
173
+ const domainFiles = new Map();
174
+ const workspaceSeeds = collectWorkspaceDomainSeeds();
175
+ for (const seed of workspaceSeeds) {
176
+ if (!domainFiles.has(seed))
177
+ domainFiles.set(seed, new Set());
178
+ }
179
+ for (const file of evidence.files) {
180
+ const key = classifyDomainKey(file);
181
+ if (!key)
182
+ continue;
183
+ if (!domainFiles.has(key))
184
+ domainFiles.set(key, new Set());
185
+ domainFiles.get(key)?.add(file);
186
+ }
187
+ for (const cluster of inferredClusters) {
188
+ const clusterPaths = [...cluster.files, ...cluster.path_prefixes];
189
+ const keyVotes = new Map();
190
+ for (const path of clusterPaths) {
191
+ const key = classifyDomainKey(path);
192
+ if (!key)
193
+ continue;
194
+ keyVotes.set(key, (keyVotes.get(key) ?? 0) + 1);
195
+ }
196
+ const dominantKey = Array.from(keyVotes.entries()).sort((a, b) => b[1] - a[1])[0]?.[0];
197
+ const normalizedSuggested = normalizeToken(cluster.suggested_name);
198
+ const clusterIsGeneric = normalizedSuggested === "general" || normalizedSuggested === "misc";
199
+ const chosenKey = dominantKey ??
200
+ (normalizedSuggested === "general" || normalizedSuggested === "misc"
201
+ ? null
202
+ : normalizedSuggested || null);
203
+ if (!chosenKey || (clusterIsGeneric && !dominantKey))
204
+ continue;
205
+ if (!domainFiles.has(chosenKey))
206
+ domainFiles.set(chosenKey, new Set());
207
+ for (const file of cluster.files) {
208
+ if (classifyDomainKey(file) === chosenKey) {
209
+ domainFiles.get(chosenKey)?.add(file);
210
+ }
211
+ }
212
+ }
213
+ for (const hint of architectureDomains) {
214
+ const normalizedHint = normalizeToken(hint);
215
+ const hintKey = normalizedHint in KNOWN_DOMAIN_LABELS ? normalizedHint : null;
216
+ if (!hintKey)
217
+ continue;
218
+ if (!domainFiles.has(hintKey))
219
+ domainFiles.set(hintKey, new Set());
220
+ }
221
+ if (domainFiles.size === 0) {
222
+ return inferredClusters.map((cluster) => {
223
+ const name = toTitle(cluster.suggested_name);
224
+ const files = cluster.files.length > 0 ? cluster.files : cluster.path_prefixes;
225
+ const tier = cluster.protection_tier ?? (0, watch_core_1.inferTierFromDomainName)(cluster.suggested_name);
226
+ return {
227
+ name,
228
+ files,
229
+ pathPatterns: cluster.path_prefixes,
230
+ confidence: Number((cluster.confidence ?? 0.5).toFixed(2)),
231
+ protectionTier: tier,
232
+ knownTraps: cluster.known_traps ?? [],
233
+ rationale: cluster.rationale ?? `files changed together under ${cluster.path_prefixes[0] ?? "mixed paths"}`,
234
+ };
235
+ });
236
+ }
237
+ return Array.from(domainFiles.entries())
238
+ .map(([key, filesSet]) => {
239
+ const files = Array.from(filesSet).sort((a, b) => a.localeCompare(b));
240
+ const pathPatterns = DOMAIN_PREFIX_BY_KEY[key] ??
241
+ (key === "database"
242
+ ? ["db/", "prisma/", "supabase/", "supabase_*.sql"]
243
+ : key === "shared"
244
+ ? ["src/shared/"]
245
+ : [`src/${key}/`]);
246
+ const primaryPrefix = pathPatterns[0] ? pathPatterns[0].replace(/\/$/, "") : key;
247
+ const protectionTier = tierForDomainKey(key, files);
248
+ return {
249
+ name: KNOWN_DOMAIN_LABELS[key] ?? toTitle(key),
250
+ files,
251
+ pathPatterns,
252
+ confidence: files.length > 0 ? confidenceForDomain(files.length) : 0.56,
253
+ protectionTier,
254
+ knownTraps: knownTrapsForDomainKey(key),
255
+ rationale: files.length > 0
256
+ ? `grouped ${files.length} file(s) under ${primaryPrefix}`
257
+ : `seeded from workspace signals for ${primaryPrefix}`,
258
+ };
259
+ })
260
+ .filter((domain) => domain.pathPatterns.length > 0)
261
+ .sort((a, b) => a.name.localeCompare(b.name));
262
+ }
263
+ function toRulesMarkdown(domains) {
264
+ const protectedDomains = domains.filter((d) => d.tier === "tier_a" || d.tier === "tier_b");
265
+ return [
266
+ "# AgentBridge Local Rules",
267
+ "",
268
+ "## Recovered Domains",
269
+ ...domains.map((d) => `- ${d.domain} — owner: ${d.owner} (${d.tier.toUpperCase()})`),
270
+ "",
271
+ "## Protected Departments",
272
+ ...(protectedDomains.length > 0
273
+ ? protectedDomains.map((d) => `- ${d.domain}`)
274
+ : ["- none detected"]),
275
+ "",
276
+ "## Working Rules",
277
+ "- Do not cross domains without an authority request.",
278
+ "- Use `agentbridge watch` while editing.",
279
+ "- Handoff when work enters another owned domain.",
280
+ "",
281
+ ].join("\n");
282
+ }
283
+ function toCursorRule(domains) {
284
+ return [
285
+ "---",
286
+ "description: AgentBridge local enforcement",
287
+ "globs: [\"**/*\"]",
288
+ "alwaysApply: true",
289
+ "---",
290
+ "",
291
+ "Recovered domains:",
292
+ ...domains.map((d) => `- ${d.domain} :: ${d.owner} :: ${d.tier}`),
293
+ "",
294
+ "Required behavior:",
295
+ "- Request authority before cross-domain edits.",
296
+ "- Use `agentbridge watch` during implementation.",
297
+ "- Handoff when entering another domain boundary.",
298
+ "",
299
+ ].join("\n");
300
+ }
301
+ function writeLocalRulesArtifacts(domains) {
302
+ const md = toRulesMarkdown(domains);
303
+ const cursorRule = toCursorRule(domains);
304
+ const rootFile = (0, node_path_1.resolve)(process.cwd(), "AGENTBRIDGE.md");
305
+ const cursorRulesDir = (0, node_path_1.resolve)(process.cwd(), ".cursor", "rules");
306
+ (0, node_fs_1.mkdirSync)(cursorRulesDir, { recursive: true });
307
+ (0, node_fs_1.writeFileSync)(rootFile, `${md}\n`, "utf8");
308
+ (0, node_fs_1.writeFileSync)((0, node_path_1.resolve)(cursorRulesDir, "agentbridge.mdc"), `${cursorRule}\n`, "utf8");
309
+ }
310
+ async function buildBootstrapPayloadFromEvidence(ctx, productSummary, architectureDomains, evidence) {
311
+ const clusterResult = await (0, http_1.postJson)(ctx, `/v1/dev/projects/${encodeURIComponent(ctx.projectId)}/recovery/evidence`, evidence);
312
+ const refinedDomains = refineRecoveredDomains(clusterResult.proposed_clusters, evidence, architectureDomains);
313
+ return {
314
+ inferredClusters: refinedDomains.map((domain, index) => ({
315
+ id: `refined_${index + 1}`,
316
+ suggested_name: domain.name,
317
+ files: domain.files,
318
+ path_prefixes: domain.pathPatterns,
319
+ churn_hot_files: domain.files.slice(0, 3),
320
+ confidence: domain.confidence,
321
+ evidence_source: "mixed",
322
+ protection_tier: domain.protectionTier,
323
+ related_domains: [],
324
+ known_traps: domain.knownTraps,
325
+ rationale: domain.rationale,
326
+ })),
327
+ payload: {
328
+ product_summary: productSummary,
329
+ recommended_first_action: "map_before_modify",
330
+ core_workflow: WORKFLOW_SUMMARY,
331
+ known_unknowns: [],
332
+ domains: toBootstrapDomains(refinedDomains),
333
+ architecture: {
334
+ summary: "Auto-generated from git evidence",
335
+ keyComponents: architectureDomains,
336
+ stackSignals: [],
337
+ },
338
+ },
339
+ };
340
+ }
341
+ async function runInit(ctx) {
342
+ if (!ctx.apiKey || ctx.apiKey.trim().length === 0) {
343
+ throw new Error("Missing AGENTBRIDGE_API_KEY. Provide --api-key, set AGENTBRIDGE_API_KEY, or save apiKey in .agentbridge/config.json.");
344
+ }
345
+ assertInsideGitRepo();
346
+ const rl = (0, promises_1.createInterface)({ input: node_process_1.stdin, output: node_process_1.stdout });
347
+ const runtimeCliPath = process.argv[1] ? (0, node_path_1.resolve)(process.argv[1]) : undefined;
348
+ const hookCliPath = runtimeCliPath ?? (0, node_path_1.resolve)(__dirname, "index.js");
349
+ try {
350
+ const productSummary = await rl.question("What does this project do? (1-3 sentences)\n> ");
351
+ const domainsRaw = await rl.question("Main product domains (comma-separated, optional)\n> ");
352
+ const architectureDomains = domainsRaw
353
+ .split(",")
354
+ .map((d) => d.trim())
355
+ .filter(Boolean);
356
+ const collectedEvidence = (0, git_evidence_1.collectGitEvidence)();
357
+ const evidence = collectedEvidence.payload;
358
+ node_process_1.stdout.write(`Recovery evidence commits scanned: ${collectedEvidence.report.commitCount}\n`);
359
+ node_process_1.stdout.write(`Recovery evidence counts before compaction: files=${collectedEvidence.report.beforeCounts.files}, coChange=${collectedEvidence.report.beforeCounts.coChange}, churn=${collectedEvidence.report.beforeCounts.churn}\n`);
360
+ node_process_1.stdout.write(`Recovery evidence counts after compaction: files=${collectedEvidence.report.afterCounts.files}, coChange=${collectedEvidence.report.afterCounts.coChange}, churn=${collectedEvidence.report.afterCounts.churn}\n`);
361
+ node_process_1.stdout.write(`Recovery evidence payload bytes: ${collectedEvidence.report.payloadBytes}\n`);
362
+ node_process_1.stdout.write(`Recovery evidence skipped/noisy summary: ${summarizeSkipped(collectedEvidence.report.skippedByReason)}\n`);
363
+ node_process_1.stdout.write(`Init auth context: hasApiKey=${Boolean(ctx.apiKey)} apiKeySource=${ctx.apiKeySource ?? "missing"} apiBaseUrl=${ctx.apiBaseUrl} projectId=${ctx.projectId}\n`);
364
+ const { inferredClusters, payload: bootstrapPayload } = await buildBootstrapPayloadFromEvidence(ctx, productSummary, architectureDomains, evidence);
365
+ node_process_1.stdout.write("Recovered domain map:\n");
366
+ for (const cluster of inferredClusters) {
367
+ node_process_1.stdout.write(`${describeCluster(cluster)}\n`);
368
+ }
369
+ const tierADomains = inferredClusters.filter((cluster) => (cluster.protection_tier ?? (0, watch_core_1.inferTierFromDomainName)(cluster.suggested_name)) === "tier_a");
370
+ if (tierADomains.length > 0) {
371
+ node_process_1.stdout.write(`Defaulting protection ON for high-risk domains: ${tierADomains
372
+ .map((cluster) => toTitle(cluster.suggested_name))
373
+ .join(", ")}.\n`);
374
+ const needsPaymentsPrompt = tierADomains.some((cluster) => /(billing|payment|stripe|checkout)/i.test(cluster.suggested_name));
375
+ if (needsPaymentsPrompt) {
376
+ const paymentsAnswer = await rl.question("Does this app take payments? [Y/n]\n> ");
377
+ if (paymentsAnswer.trim().toLowerCase() === "n") {
378
+ node_process_1.stdout.write("Noted. Keep billing-related domain as advisory until you confirm provider details.\n");
379
+ }
380
+ }
381
+ }
382
+ await (0, http_1.postJson)(ctx, `/v1/dev/projects/${encodeURIComponent(ctx.projectId)}/bootstrap`, bootstrapPayload);
383
+ const recoveredDomains = inferredClusters
384
+ .filter(isValidAuthorityDomain)
385
+ .map((cluster) => ({
386
+ domain: cluster.suggested_name,
387
+ pathPatterns: cluster.files.length > 0 ? cluster.files : cluster.path_prefixes,
388
+ ownerAgentId: `${cluster.suggested_name} Agent`,
389
+ tier: cluster.protection_tier ?? (0, watch_core_1.inferTierFromDomainName)(cluster.suggested_name),
390
+ }));
391
+ (0, config_1.updateConfig)({
392
+ apiBaseUrl: ctx.apiBaseUrl,
393
+ projectId: ctx.projectId,
394
+ domains: recoveredDomains,
395
+ cliPath: runtimeCliPath,
396
+ });
397
+ writeLocalRulesArtifacts(recoveredDomains.map((domain) => ({
398
+ domain: domain.domain,
399
+ owner: domain.ownerAgentId,
400
+ tier: domain.tier,
401
+ })));
402
+ node_process_1.stdout.write("Wrote AGENTBRIDGE.md and .cursor/rules/agentbridge.mdc\n");
403
+ const installHookAnswer = await rl.question("Install AgentBridge pre-commit protection? Recommended for hard-stop enforcement. [Y/n]\n> ");
404
+ if (installHookAnswer.trim() === "" || installHookAnswer.trim().toLowerCase() === "y") {
405
+ (0, precommit_1.installPrecommitHookWithPath)(hookCliPath);
406
+ (0, config_1.updateConfig)({ precommitHookInstalled: true, cliPath: runtimeCliPath });
407
+ node_process_1.stdout.write("Pre-commit hook installed.\n");
408
+ }
409
+ else {
410
+ (0, config_1.updateConfig)({ precommitHookInstalled: false, cliPath: runtimeCliPath });
411
+ node_process_1.stdout.write("Pre-commit protection disabled. Watch will still warn/block, but commits are not blocked.\n");
412
+ }
413
+ node_process_1.stdout.write(`Recovery bootstrap completed with ${inferredClusters.length} inferred domains.\n`);
414
+ }
415
+ finally {
416
+ rl.close();
417
+ }
418
+ }
419
+ async function runBootstrapRecovery(ctx, opts = {}) {
420
+ 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
+ const productSummary = opts.productSummary?.trim() || "Recovered existing repository baseline for supervised agent work.";
424
+ const architectureDomains = opts.architectureDomains ?? [];
425
+ const collectedEvidence = (0, git_evidence_1.collectGitEvidence)();
426
+ const evidence = collectedEvidence.payload;
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);
429
+ const recoveredDomains = inferredClusters
430
+ .filter(isValidAuthorityDomain)
431
+ .map((cluster) => ({
432
+ domain: cluster.suggested_name,
433
+ pathPatterns: cluster.files.length > 0 ? cluster.files : cluster.path_prefixes,
434
+ ownerAgentId: `${cluster.suggested_name} Agent`,
435
+ tier: cluster.protection_tier ?? (0, watch_core_1.inferTierFromDomainName)(cluster.suggested_name),
436
+ }));
437
+ (0, config_1.updateConfig)({
438
+ apiBaseUrl: ctx.apiBaseUrl,
439
+ projectId: ctx.projectId,
440
+ domains: recoveredDomains,
441
+ cliPath: runtimeCliPath,
442
+ });
443
+ writeLocalRulesArtifacts(recoveredDomains.map((domain) => ({
444
+ domain: domain.domain,
445
+ owner: domain.ownerAgentId,
446
+ tier: domain.tier,
447
+ })));
448
+ if (opts.installHook) {
449
+ (0, precommit_1.installPrecommitHookWithPath)(hookCliPath);
450
+ (0, config_1.updateConfig)({ precommitHookInstalled: true, cliPath: runtimeCliPath });
451
+ }
452
+ node_process_1.stdout.write([
453
+ "Recovery baseline created.",
454
+ `Domains mapped: ${recoveredDomains.length}.`,
455
+ "Project context and local rules are now ready.",
456
+ "",
457
+ ].join("\n"));
458
+ }
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.renderMemoryContextLines = renderMemoryContextLines;
4
+ exports.renderWatchMemoryContext = renderWatchMemoryContext;
5
+ function isRelevant(context) {
6
+ return Boolean(context?.relevant);
7
+ }
8
+ function renderMemoryContextLines(context, options) {
9
+ if (!isRelevant(context))
10
+ return [];
11
+ const prefix = options?.prefix ?? "";
12
+ const lines = [];
13
+ const traps = context.items.filter((item) => item.type === "known_trap" || item.type === "invariant");
14
+ const lessons = context.items.filter((item) => item.type === "accepted_lesson");
15
+ const contextItems = [...traps, ...lessons].slice(0, 3);
16
+ if (contextItems.length > 0) {
17
+ lines.push(`${prefix}Memory context:`);
18
+ for (const item of contextItems) {
19
+ const label = item.type === "known_trap" ? "Known trap" : item.type === "invariant" ? "Invariant" : "Lesson";
20
+ lines.push(`${prefix}- ${label}: ${item.summary}`);
21
+ }
22
+ }
23
+ if (context.proof_requirements.length > 0) {
24
+ lines.push(`${prefix}Memory-derived proof requirements:`);
25
+ for (const req of context.proof_requirements) {
26
+ const status = req.satisfied ? "satisfied" : "missing";
27
+ lines.push(`${prefix}- ${req.requirement}`);
28
+ lines.push(`${prefix} Status: ${status}`);
29
+ }
30
+ }
31
+ return lines;
32
+ }
33
+ function renderWatchMemoryContext(context) {
34
+ if (!isRelevant(context))
35
+ return [];
36
+ const lines = ["AgentBridge memory context:"];
37
+ const traps = context.items.filter((item) => item.type === "known_trap");
38
+ if (traps.length > 0) {
39
+ lines.push("Known traps:");
40
+ for (const trap of traps.slice(0, 3)) {
41
+ lines.push(`- ${trap.summary}`);
42
+ }
43
+ }
44
+ if (context.proof_requirements.length > 0) {
45
+ lines.push("Memory-derived proof requirements:");
46
+ for (const req of context.proof_requirements.slice(0, 4)) {
47
+ lines.push(`- ${req.requirement}`);
48
+ }
49
+ }
50
+ return lines;
51
+ }
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.COMPLETION_STATE_LABELS = void 0;
4
+ exports.buildOperatorSnapshot = buildOperatorSnapshot;
5
+ exports.renderOperatorSnapshotHuman = renderOperatorSnapshotHuman;
6
+ exports.renderOperatorNextStepBlock = renderOperatorNextStepBlock;
7
+ const canonical_state_1 = require("./canonical-state");
8
+ exports.COMPLETION_STATE_LABELS = {
9
+ no_current_session: "No current session",
10
+ needs_proof: "Needs proof",
11
+ stale_evidence: "Needs proof — stale evidence",
12
+ failed: "Failed",
13
+ needs_review: "Needs review",
14
+ ready_for_handoff: "Ready for handoff",
15
+ ready_to_accept: "Ready to accept",
16
+ accepted: "Accepted",
17
+ };
18
+ function quote(value) {
19
+ return value.replace(/"/g, '\\"');
20
+ }
21
+ function inferNoSessionNextAction(resolution) {
22
+ if (resolution.state === "other_active_sessions_exist") {
23
+ return "agentbridge session list --active";
24
+ }
25
+ if (resolution.state === "ambiguous_active_sessions") {
26
+ if (resolution.requestedChangeRequestId) {
27
+ return `agentbridge session list --change-request "${quote(resolution.requestedChangeRequestId)}" --active`;
28
+ }
29
+ return "agentbridge session list --active";
30
+ }
31
+ if (resolution.state === "mismatch" || resolution.state === "stale_orphan_session_detected") {
32
+ return 'agentbridge session abandon --reason "clear stale/mismatched local session"';
33
+ }
34
+ if (resolution.state === "current_session_closed") {
35
+ return 'agentbridge session abandon --reason "session closed on server"';
36
+ }
37
+ return "agentbridge watch";
38
+ }
39
+ function inferReportNextAction(report) {
40
+ if (report.completion?.nextCommand && report.completion.nextCommand.trim().length > 0) {
41
+ return report.completion.nextCommand.trim();
42
+ }
43
+ const state = report.completion?.state ?? report.decision;
44
+ switch (state) {
45
+ case "needs_proof":
46
+ case "stale_evidence":
47
+ return "agentbridge verify -- <command>";
48
+ case "failed":
49
+ return "agentbridge check";
50
+ case "needs_review":
51
+ return "agentbridge handoff --summary \"<what changed>\"";
52
+ case "ready_for_handoff":
53
+ return 'agentbridge handoff --summary "<what changed>"';
54
+ case "ready_to_accept":
55
+ return "agentbridge accept";
56
+ case "accepted":
57
+ return "agentbridge watch";
58
+ default:
59
+ return "agentbridge check";
60
+ }
61
+ }
62
+ function buildOperatorSnapshot(resolution) {
63
+ const report = resolution.binding?.report;
64
+ const completionState = report?.completion?.state ?? report?.decision ?? "no_current_session";
65
+ const nextAction = report ? inferReportNextAction(report) : inferNoSessionNextAction(resolution);
66
+ const nextReason = report?.completion?.blockingReasons?.[0] ?? report?.next_required_action?.[0] ?? resolution.message;
67
+ return {
68
+ completion_state: completionState,
69
+ completion_state_label: exports.COMPLETION_STATE_LABELS[completionState],
70
+ decision: report?.decision ?? "none",
71
+ next_action: nextAction,
72
+ next_reason: nextReason,
73
+ work_context_state: (0, canonical_state_1.toCanonicalWorkContextState)(resolution.state),
74
+ work_context_source: resolution.contextSource,
75
+ has_current_session: resolution.state === "current_session_resolved" && !!resolution.binding,
76
+ current_change_request_id: report?.change_request_id ?? resolution.resolvedChangeRequestId,
77
+ current_work_session_id: report?.work_session_id ?? resolution.resolvedServerSessionId,
78
+ other_active_sessions_count: resolution.otherActiveSessions.length,
79
+ changed_files_count: report?.changed_files?.length ?? 0,
80
+ evidence_missing_count: report?.evidence_missing?.length ?? 0,
81
+ risks_remaining_count: report?.risks_remaining?.length ?? 0,
82
+ protocol_warnings_count: report?.protocol_warnings?.length ?? 0,
83
+ };
84
+ }
85
+ function renderOperatorSnapshotHuman(snapshot) {
86
+ return [
87
+ `State: ${snapshot.completion_state}`,
88
+ `Next: ${snapshot.next_action}`,
89
+ `Why: ${snapshot.next_reason}`,
90
+ ].join("\n");
91
+ }
92
+ function renderOperatorNextStepBlock(snapshot) {
93
+ return [
94
+ "Operator next step:",
95
+ `- state: ${snapshot.completion_state}`,
96
+ `- next action: ${snapshot.next_action}`,
97
+ `- why: ${snapshot.next_reason}`,
98
+ ];
99
+ }
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runPrecommitCheck = runPrecommitCheck;
4
+ exports.readStagedFilesFromGitDiff = readStagedFilesFromGitDiff;
5
+ exports.stagedFilesFromGit = stagedFilesFromGit;
6
+ exports.blockedFilesFromSession = blockedFilesFromSession;
7
+ exports.runPrecommitCheckFromSession = runPrecommitCheckFromSession;
8
+ exports.installPrecommitHook = installPrecommitHook;
9
+ exports.installPrecommitHookWithPath = installPrecommitHookWithPath;
10
+ const node_fs_1 = require("node:fs");
11
+ const node_path_1 = require("node:path");
12
+ const node_child_process_1 = require("node:child_process");
13
+ const session_state_1 = require("./session-state");
14
+ function runPrecommitCheck(input) {
15
+ void input.stagedFiles;
16
+ const blocked = [...new Set(input.blockedFiles)];
17
+ return { ok: blocked.length === 0, blocked };
18
+ }
19
+ function readStagedFilesFromGitDiff(diffTextPath) {
20
+ const raw = (0, node_fs_1.readFileSync)(diffTextPath, "utf8");
21
+ return raw
22
+ .split("\n")
23
+ .map((line) => line.trim())
24
+ .filter(Boolean);
25
+ }
26
+ function stagedFilesFromGit() {
27
+ const raw = (0, node_child_process_1.execSync)("git diff --name-only --cached", {
28
+ encoding: "utf8",
29
+ stdio: ["ignore", "pipe", "pipe"],
30
+ });
31
+ return raw
32
+ .split("\n")
33
+ .map((line) => line.trim())
34
+ .filter(Boolean);
35
+ }
36
+ function blockedFilesFromSession() {
37
+ const state = (0, session_state_1.readSessionState)();
38
+ if (!state) {
39
+ return [];
40
+ }
41
+ return state.crossings
42
+ .filter((crossing) => (crossing.status === "unresolved" || crossing.status === "handoff") &&
43
+ (crossing.tier === "tier_a" || crossing.tier === "tier_b"))
44
+ .map((crossing) => crossing.file);
45
+ }
46
+ function runPrecommitCheckFromSession(stagedFiles) {
47
+ return runPrecommitCheck({
48
+ stagedFiles,
49
+ blockedFiles: blockedFilesFromSession(),
50
+ });
51
+ }
52
+ function installPrecommitHook() {
53
+ const runtimeCliPath = process.argv[1] ? (0, node_path_1.resolve)(process.argv[1]) : (0, node_path_1.resolve)(__dirname, "index.js");
54
+ installPrecommitHookWithPath(runtimeCliPath);
55
+ }
56
+ function installPrecommitHookWithPath(cliPath) {
57
+ const hookPath = (0, node_path_1.resolve)(process.cwd(), ".git", "hooks", "pre-commit");
58
+ if (!(0, node_fs_1.existsSync)((0, node_path_1.resolve)(process.cwd(), ".git"))) {
59
+ return;
60
+ }
61
+ const escapedCliPath = cliPath.replace(/"/g, '\\"');
62
+ const script = `#!/bin/sh
63
+ node "${escapedCliPath}" precommit-check
64
+ status=$?
65
+ if [ "$status" -ne 0 ]; then
66
+ echo "agentbridge: unresolved protected crossing; resolve authority request first."
67
+ fi
68
+ exit "$status"
69
+ `;
70
+ (0, node_fs_1.writeFileSync)(hookPath, script, "utf8");
71
+ (0, node_fs_1.chmodSync)(hookPath, 0o755);
72
+ }