@broject/bro 0.0.0 → 0.2.2

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,1177 @@
1
+ import { createRequire } from "node:module";
2
+ import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, realpathSync, rmSync, statSync, writeFileSync } from "node:fs";
3
+ import { basename, dirname, join, resolve, sep } from "node:path";
4
+ import { pathToFileURL } from "node:url";
5
+ import { execFileSync, spawnSync } from "node:child_process";
6
+ import { homedir, tmpdir } from "node:os";
7
+ import { parse } from "smol-toml";
8
+
9
+ //#region ../core/dist/index.js
10
+ function gh(args, cwd) {
11
+ const proc = spawnSync("gh", args, {
12
+ cwd,
13
+ stdio: [
14
+ "ignore",
15
+ "pipe",
16
+ "pipe"
17
+ ],
18
+ encoding: "utf8"
19
+ });
20
+ if (proc.status !== 0) throw new Error(`gh ${args[0]} failed: ${(proc.stderr ?? "").trim()}`);
21
+ return proc.stdout ?? "";
22
+ }
23
+ function ghJson(args, cwd) {
24
+ return JSON.parse(gh(args, cwd));
25
+ }
26
+ /**
27
+ * `gh` without the throw — for commands whose exit code carries meaning
28
+ * (`gh pr checks` exits 1 when checks fail while still printing JSON).
29
+ */
30
+ function ghTry(args, cwd) {
31
+ const proc = spawnSync("gh", args, {
32
+ cwd,
33
+ stdio: [
34
+ "ignore",
35
+ "pipe",
36
+ "pipe"
37
+ ],
38
+ encoding: "utf8"
39
+ });
40
+ return {
41
+ code: proc.status ?? 1,
42
+ out: proc.stdout ?? "",
43
+ err: (proc.stderr ?? "").trim()
44
+ };
45
+ }
46
+ function git(args) {
47
+ const proc = spawnSync("git", args, {
48
+ stdio: [
49
+ "ignore",
50
+ "pipe",
51
+ "pipe"
52
+ ],
53
+ encoding: "utf8"
54
+ });
55
+ if (proc.status !== 0) throw new Error(`git ${args[0]} failed: ${(proc.stderr ?? "").trim()}`);
56
+ return proc.stdout ?? "";
57
+ }
58
+ function gitTry(args) {
59
+ const proc = spawnSync("git", args, {
60
+ stdio: [
61
+ "ignore",
62
+ "pipe",
63
+ "pipe"
64
+ ],
65
+ encoding: "utf8"
66
+ });
67
+ return {
68
+ code: proc.status ?? 1,
69
+ out: proc.stdout ?? "",
70
+ err: (proc.stderr ?? "").trim()
71
+ };
72
+ }
73
+ function bd(args, cwd) {
74
+ return execFileSync("bd", args, {
75
+ encoding: "utf8",
76
+ cwd,
77
+ maxBuffer: 64 * 1024 * 1024,
78
+ stdio: [
79
+ "ignore",
80
+ "pipe",
81
+ "pipe"
82
+ ],
83
+ timeout: 15e3
84
+ });
85
+ }
86
+ /** Non-throwing bd — same contract as gitTry for paths where beads is
87
+ * optional (merge slot, hooks): a missing binary or absent database must
88
+ * degrade, not stall. */
89
+ function bdTry(args, timeoutMs = 15e3, cwd) {
90
+ const proc = spawnSync("bd", args, {
91
+ cwd,
92
+ stdio: [
93
+ "ignore",
94
+ "pipe",
95
+ "pipe"
96
+ ],
97
+ encoding: "utf8",
98
+ timeout: timeoutMs,
99
+ maxBuffer: 64 * 1024 * 1024
100
+ });
101
+ return {
102
+ code: proc.status ?? 1,
103
+ out: proc.stdout ?? "",
104
+ err: (proc.stderr ?? proc.error?.message ?? "").trim()
105
+ };
106
+ }
107
+ function bdJson(args, cwd) {
108
+ const out = bd([...args, "--json"], cwd);
109
+ try {
110
+ return JSON.parse(out);
111
+ } catch (err) {
112
+ throw new Error(`bd returned malformed JSON — ${err instanceof Error ? err.message : String(err)}`);
113
+ }
114
+ }
115
+ /** Classify a provenance ref — shared by drill and retro evidence. */
116
+ function refKind(ref) {
117
+ if (/\/pull\/|\/merge_requests\//.test(ref)) return "pr";
118
+ if (/^[0-9a-f]{40}$/.test(ref)) return "git-sha";
119
+ return "work-id";
120
+ }
121
+ /** Provenance event kind for an evidence ref — a work-id is not a commit. */
122
+ function evidenceKind(ref) {
123
+ switch (refKind(ref)) {
124
+ case "pr": return "land";
125
+ case "git-sha": return "commit";
126
+ default: return "used";
127
+ }
128
+ }
129
+ /** Only a real `.beads` *directory* counts — a file or dangling path must
130
+ * not suppress init (it would fail loudly rather than be repaired). */
131
+ function beadsDirExists() {
132
+ try {
133
+ return statSync(join(process.cwd(), ".beads")).isDirectory();
134
+ } catch (err) {
135
+ const code = err.code;
136
+ if (code === "ENOENT" || code === "ENOTDIR") return false;
137
+ throw err;
138
+ }
139
+ }
140
+ /**
141
+ * Stealth-init `.beads` in the current repo when missing — the single init
142
+ * flags contract shared by debt sync and `bro setup` (local exclude,
143
+ * nothing lands in git). Returns true when it initialized.
144
+ */
145
+ function initBeadsStealth() {
146
+ if (beadsDirExists()) return false;
147
+ try {
148
+ bd([
149
+ "init",
150
+ "--stealth",
151
+ "--skip-agents",
152
+ "--skip-hooks",
153
+ "--quiet"
154
+ ]);
155
+ return true;
156
+ } catch (err) {
157
+ if (beadsDirExists()) return false;
158
+ throw err;
159
+ }
160
+ }
161
+ function checkBeads() {
162
+ try {
163
+ bd(["--version"]);
164
+ } catch (err) {
165
+ if (err.code === "ENOENT") throw new Error("bd not found — install beads first (https://github.com/gastownhall/beads)");
166
+ throw err;
167
+ }
168
+ try {
169
+ bd([
170
+ "list",
171
+ "--json",
172
+ "-n",
173
+ "1"
174
+ ]);
175
+ } catch (err) {
176
+ const stderr = err.stderr?.trim();
177
+ throw new Error(`bd list failed — ${stderr || (err instanceof Error ? err.message : String(err))} (run \`bd init\` if beads is not initialized here)`);
178
+ }
179
+ }
180
+ const STORE_BACKENDS = [
181
+ "jsonl",
182
+ "beads",
183
+ "gitref"
184
+ ];
185
+ const PERSONALITIES = [
186
+ "terse",
187
+ "mentor",
188
+ "sarcastic"
189
+ ];
190
+ const debtSection = (raw) => {
191
+ const obj = typeof raw === "object" && raw !== null ? raw : {};
192
+ return {
193
+ dir: typeof obj.dir === "string" && obj.dir.trim() !== "" ? obj.dir : DEFAULT_CONFIG.debt.dir,
194
+ sources: Array.isArray(obj.sources) && obj.sources.every((s) => typeof s === "string" && s.trim() !== "") && obj.sources.length > 0 ? obj.sources : DEFAULT_CONFIG.debt.sources,
195
+ stale_days: typeof obj.stale_days === "number" && Number.isFinite(obj.stale_days) && obj.stale_days > 0 ? obj.stale_days : DEFAULT_CONFIG.debt.stale_days
196
+ };
197
+ };
198
+ const syncSection = (raw) => {
199
+ const obj = typeof raw === "object" && raw !== null ? raw : {};
200
+ return {
201
+ ...DEFAULT_CONFIG.sync,
202
+ ...Object.fromEntries(Object.entries(obj).filter(([, v]) => typeof v === "string" && v.trim() !== "")),
203
+ beads: typeof obj.beads === "boolean" ? obj.beads : DEFAULT_CONFIG.sync.beads
204
+ };
205
+ };
206
+ const actSection = (raw) => {
207
+ const obj = typeof raw === "object" && raw !== null ? raw : {};
208
+ return {
209
+ ignoreChecks: Array.isArray(obj.ignoreChecks) ? obj.ignoreChecks.filter((v) => typeof v === "string" && v.trim() !== "") : [],
210
+ maxRounds: typeof obj.maxRounds === "number" && Number.isInteger(obj.maxRounds) && obj.maxRounds >= 0 ? obj.maxRounds : DEFAULT_CONFIG.act.maxRounds
211
+ };
212
+ };
213
+ /** bro.config.json `connectors` section — facade → connector precedence,
214
+ * e.g. { "reviews": "gitlab", "tasks": "jira" }. Only string→string
215
+ * entries survive; anything else is dropped. */
216
+ const connectorsSection = (raw) => {
217
+ const out = {};
218
+ if (typeof raw === "object" && raw !== null && !Array.isArray(raw)) {
219
+ for (const [k, v] of Object.entries(raw)) if (k.trim() !== "" && typeof v === "string" && v.trim() !== "") out[k.trim()] = v.trim();
220
+ }
221
+ return out;
222
+ };
223
+ /** Default global-beads store — XDG data dir, not a repo path. */
224
+ const DEFAULT_GLOBAL_BEADS_DIR = join(homedir(), ".local", "share", "bro", "beads");
225
+ /** bro.config.json `beads` section — `beads.global` is the global store
226
+ * dir. BRO_GLOBAL_BEADS wins over the file: a per-machine path should
227
+ * not need a committed edit. `~` expands against the home dir. */
228
+ const beadsSection = (raw) => {
229
+ const fromConfig = typeof raw === "object" && raw !== null ? raw.global : void 0;
230
+ const env = process.env.BRO_GLOBAL_BEADS;
231
+ const v = (typeof env === "string" && env.trim() !== "" ? env : void 0) ?? (typeof fromConfig === "string" && fromConfig.trim() !== "" ? fromConfig : void 0);
232
+ if (v === void 0) return { global: DEFAULT_GLOBAL_BEADS_DIR };
233
+ const p = v.trim();
234
+ let dir = p;
235
+ if (p === "~") dir = homedir();
236
+ else if (p.startsWith("~/")) dir = join(homedir(), p.slice(2));
237
+ return { global: dir };
238
+ };
239
+ /** bro.config.json `stack` section — how `bro work enter` picks the base
240
+ * for a new worktree when a session already produced a PR branch.
241
+ * 'manual' (default): stack only on explicit --stack/--base.
242
+ * 'auto': base on the current worktree's branch whenever it isn't the
243
+ * main checkout's branch — second+ PR in a session lands on the stack
244
+ * head; gh-stack then drives submit/sync/merge bottom-up. */
245
+ const stackSection = (raw) => {
246
+ const m = typeof raw === "object" && raw !== null ? raw.mode : void 0;
247
+ if (m !== void 0 && m !== "auto" && m !== "manual") {
248
+ console.error(`bro.config: stack.mode must be "auto" or "manual" — got ${JSON.stringify(m)}`);
249
+ return { mode: "manual" };
250
+ }
251
+ return { mode: m ?? "manual" };
252
+ };
253
+ const SDD_MODES = [
254
+ "off",
255
+ "remind",
256
+ "gate"
257
+ ];
258
+ /** bro.config.json `sdd` section — spec-driven development policy.
259
+ * off (default): nothing emitted. remind: session-start/prompt context
260
+ * names claimed beads without a spec. gate: the stop gate also blocks
261
+ * once while an own claim lacks a spec. `dir` is where `specs/<id>.md`
262
+ * lives; a `spec:` link in the bead description counts too. */
263
+ const sddSection = (raw) => {
264
+ const obj = typeof raw === "object" && raw !== null ? raw : {};
265
+ if (obj.mode !== void 0 && !SDD_MODES.includes(obj.mode)) console.error(`bro.config: sdd.mode must be one of ${SDD_MODES.map((m) => `"${m}"`).join("|")} — got ${JSON.stringify(obj.mode)}`);
266
+ return {
267
+ mode: obj.mode !== void 0 && SDD_MODES.includes(obj.mode) ? obj.mode : DEFAULT_CONFIG.sdd.mode,
268
+ dir: typeof obj.dir === "string" && obj.dir.trim() !== "" ? obj.dir.trim() : DEFAULT_CONFIG.sdd.dir
269
+ };
270
+ };
271
+ /** Sections core normalizes itself — identical to what the built-in
272
+ * plugins declare as their configSchema. */
273
+ const CORE_SECTIONS = {
274
+ debt: debtSection,
275
+ sync: syncSection,
276
+ act: actSection,
277
+ connectors: connectorsSection,
278
+ sdd: sddSection
279
+ };
280
+ const DEFAULT_CONFIG = {
281
+ stores: ["jsonl", "beads"],
282
+ personality: "terse",
283
+ debt: {
284
+ dir: ".agents/review-debt",
285
+ sources: ["review-threads"],
286
+ stale_days: 14
287
+ },
288
+ sync: {
289
+ ref: "refs/bro/data",
290
+ remote: "origin",
291
+ beads: true
292
+ },
293
+ act: {
294
+ ignoreChecks: [],
295
+ maxRounds: 3
296
+ },
297
+ connectors: {},
298
+ sdd: {
299
+ mode: "off",
300
+ dir: "specs"
301
+ },
302
+ plugins: []
303
+ };
304
+ function normalizeStores(raw) {
305
+ const isBackend = (s) => typeof s === "string" && STORE_BACKENDS.includes(s);
306
+ if (Array.isArray(raw.stores)) return [...new Set(["jsonl", ...raw.stores.filter(isBackend)])];
307
+ if (raw.stores !== void 0) {
308
+ console.error("warning: bro.config.json \"stores\" must be an array — using jsonl-only");
309
+ return ["jsonl"];
310
+ }
311
+ if (raw.store === "beads" || raw.store === "both") return ["jsonl", "beads"];
312
+ if (raw.store !== void 0) return ["jsonl"];
313
+ return [...DEFAULT_CONFIG.stores];
314
+ }
315
+ /** Identity helper for bro.config.ts: `export default defineConfig({…})`
316
+ * gives typed sections when @broject/core is a local dep; a plain object
317
+ * works without it. Extra keys are plugin sections (see bro-akl). */
318
+ function defineConfig(config) {
319
+ return config;
320
+ }
321
+ function unwrapDefault(mod) {
322
+ if (typeof mod !== "object" || mod === null) return mod;
323
+ const m = mod;
324
+ return (m[Symbol.toStringTag] === "Module" || Object.keys(m).every((k) => k === "default" || k === "__esModule")) && "default" in m ? m.default : mod;
325
+ }
326
+ function loadTsConfig(name, path) {
327
+ if (!process.features.typescript) {
328
+ console.error(`warning: ${name} needs Node >=22.18 (native type stripping) — using jsonl-only stores`);
329
+ return;
330
+ }
331
+ const abs = resolve(path);
332
+ const req = createRequire(abs);
333
+ try {
334
+ return unwrapDefault(req(abs));
335
+ } catch (err) {
336
+ const msg = err instanceof Error ? err.message : String(err);
337
+ if (!/Transform failed|Expected identifier|Cannot use export|ERR_REQUIRE/.test(msg)) throw err;
338
+ const out = execFileSync(process.execPath, ["--eval", `import(${JSON.stringify(pathToFileURL(abs).href)}).then(m => process.stdout.write(JSON.stringify(m.default ?? null)))`], { encoding: "utf8" });
339
+ return JSON.parse(out);
340
+ }
341
+ }
342
+ /** Reads one config file; undefined = failed (caller falls back to
343
+ * jsonl-only — a broken file must not silently enable beads). */
344
+ function readConfigFile(name, path) {
345
+ try {
346
+ if (name.endsWith(".ts")) return loadTsConfig(name, path);
347
+ return JSON.parse(readFileSync(path, "utf8"));
348
+ } catch (err) {
349
+ const msg = err instanceof Error ? err.message : String(err);
350
+ let hint = ` (${msg})`;
351
+ if (/module is not defined|exports is not defined/.test(msg)) hint = " (this repo is ESM — use `export default`, not module.exports)";
352
+ else if (/Transform failed|Expected identifier/.test(msg)) hint = " (this dir resolves as CommonJS — use `module.exports` or add `\"type\": \"module\"` to package.json)";
353
+ console.error(`warning: ${name} failed to load — skipping${hint}`);
354
+ return;
355
+ }
356
+ }
357
+ /** Runs every section schema over the raw file — a throwing schema warns
358
+ * and falls back to schema(undefined), never crashes the load. */
359
+ function applySections(config, raw, sections) {
360
+ for (const [key, schema] of Object.entries({
361
+ ...CORE_SECTIONS,
362
+ ...sections
363
+ })) try {
364
+ config[key] = schema(raw[key]);
365
+ } catch (err) {
366
+ const msg = err instanceof Error ? err.message : String(err);
367
+ console.error(`warning: bro.config "${key}" invalid (${msg}) — using defaults`);
368
+ config[key] = schema(void 0);
369
+ }
370
+ }
371
+ /** The main checkout's root when cwd sits in a linked worktree —
372
+ * `--git-common-dir` resolves to `<main>/.git` there (and to
373
+ * `<cwd>/.git` in the main checkout itself, which dedupes). */
374
+ function mainWorktreeRoot(cwd) {
375
+ const r = gitTry([
376
+ "-C",
377
+ cwd,
378
+ "rev-parse",
379
+ "--git-common-dir"
380
+ ]);
381
+ if (r.code !== 0 || r.out.trim() === "") return null;
382
+ const common = resolve(cwd, r.out.trim());
383
+ if (basename(common) === ".git") return dirname(common);
384
+ const parent = dirname(common);
385
+ let names = [];
386
+ try {
387
+ names = readdirSync(parent);
388
+ } catch {
389
+ return null;
390
+ }
391
+ for (const name of names) {
392
+ const dir = join(parent, name);
393
+ const gitfile = join(dir, ".git");
394
+ try {
395
+ if (!statSync(gitfile).isFile()) continue;
396
+ const m = /^gitdir:\s*(.+)$/m.exec(readFileSync(gitfile, "utf8"));
397
+ if (m && resolve(dir, m[1].trim()) === common) return dir;
398
+ } catch {}
399
+ }
400
+ return null;
401
+ }
402
+ function loadConfig(cwd = process.cwd(), sections = {}) {
403
+ const fallback = (stores) => {
404
+ const config = {
405
+ ...DEFAULT_CONFIG,
406
+ stores
407
+ };
408
+ applySections(config, {}, sections);
409
+ return config;
410
+ };
411
+ const dirs = [cwd, mainWorktreeRoot(cwd)].filter((d) => d !== null && d.trim() !== "");
412
+ let sawBroken = false;
413
+ for (const dir of new Set(dirs)) {
414
+ const r = loadDirConfig(dir, sections);
415
+ if (r === "broken") sawBroken = true;
416
+ else if (r !== null) return r;
417
+ }
418
+ return fallback(sawBroken ? ["jsonl"] : [...DEFAULT_CONFIG.stores]);
419
+ }
420
+ /** Tries bro.config.ts → bro.config.json inside one dir. 'broken' = a
421
+ * config exists but failed (the dir's remaining names are skipped —
422
+ * .ts precedence never promotes the sibling .json); null = nothing
423
+ * here. */
424
+ function loadDirConfig(dir, sections) {
425
+ for (const name of ["bro.config.ts", "bro.config.json"]) {
426
+ const path = join(dir, name);
427
+ if (!existsSync(path)) continue;
428
+ const raw = readConfigFile(name, path);
429
+ if (raw === void 0) return "broken";
430
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
431
+ console.error(`${name}: root must be an object — skipping`);
432
+ return "broken";
433
+ }
434
+ const { stores: _s, store: _legacy, plugins: _p,...rest } = raw;
435
+ const config = {
436
+ ...DEFAULT_CONFIG,
437
+ ...rest,
438
+ stores: normalizeStores(raw),
439
+ plugins: normalizePluginSpecs(dir, raw.plugins)
440
+ };
441
+ applySections(config, raw, sections);
442
+ return config;
443
+ }
444
+ return null;
445
+ }
446
+ /** Relative specs anchor at the config's own dir — an inherited config's
447
+ * `./x.ts` plugin must resolve in the main worktree, not the linked
448
+ * one. Containment is kept here: a spec escaping its anchor is dropped,
449
+ * so the absolute path reaching importPluginModule can't bypass its
450
+ * repo-root guard. */
451
+ function normalizePluginSpecs(dir, plugins) {
452
+ if (!Array.isArray(plugins)) return [];
453
+ return plugins.filter((v) => typeof v === "string").map((v) => v.trim()).filter((v) => v !== "").flatMap((v) => {
454
+ if (!v.startsWith(".")) return [v];
455
+ const anchor = resolve(dir);
456
+ const abs = resolve(dir, v);
457
+ if (abs !== anchor && !abs.startsWith(anchor + sep)) {
458
+ console.error(`warning: plugin spec "${v}" escapes ${dir} — skipped`);
459
+ return [];
460
+ }
461
+ return [abs];
462
+ });
463
+ }
464
+ const DATA_REF = "refs/bro/data";
465
+ function g(root, args, input) {
466
+ return execFileSync("git", [
467
+ "-C",
468
+ root,
469
+ ...args
470
+ ], {
471
+ encoding: "utf8",
472
+ input,
473
+ stdio: [
474
+ "pipe",
475
+ "pipe",
476
+ "pipe"
477
+ ]
478
+ }).trim();
479
+ }
480
+ /** Untrimmed read — file contents must survive byte-for-byte. */
481
+ function gRaw(root, args) {
482
+ return execFileSync("git", [
483
+ "-C",
484
+ root,
485
+ ...args
486
+ ], {
487
+ encoding: "utf8",
488
+ stdio: [
489
+ "pipe",
490
+ "pipe",
491
+ "pipe"
492
+ ]
493
+ });
494
+ }
495
+ /** Resolve the repo root for cwd, or null outside a worktree. */
496
+ function dataRefRoot(cwd = process.cwd()) {
497
+ const r = gitTry([
498
+ "-C",
499
+ cwd,
500
+ "rev-parse",
501
+ "--show-toplevel"
502
+ ]);
503
+ return r.code === 0 ? r.out.trim() : null;
504
+ }
505
+ function refSha(root, ref) {
506
+ const r = gitTry([
507
+ "-C",
508
+ root,
509
+ "rev-parse",
510
+ "--verify",
511
+ "--quiet",
512
+ ref
513
+ ]);
514
+ return r.code === 0 ? r.out.trim() : null;
515
+ }
516
+ /** All git ops share one throwaway index bound to the repo root —
517
+ * created empty per call so a crashed process can't leave a half-staged
518
+ * private index behind. */
519
+ function withIndex(root, fn) {
520
+ const dir = mkdtempSync(join(tmpdir(), "bro-dataref-"));
521
+ const indexPath = join(dir, "index");
522
+ const ix = (args, input) => execFileSync("git", [
523
+ "-C",
524
+ root,
525
+ ...args
526
+ ], {
527
+ encoding: "utf8",
528
+ input,
529
+ env: {
530
+ ...process.env,
531
+ GIT_INDEX_FILE: indexPath
532
+ },
533
+ stdio: [
534
+ "pipe",
535
+ "pipe",
536
+ "pipe"
537
+ ]
538
+ }).trim();
539
+ try {
540
+ return fn(ix);
541
+ } finally {
542
+ rmSync(dir, {
543
+ recursive: true,
544
+ force: true
545
+ });
546
+ }
547
+ }
548
+ /** `git ls-tree -rz` → path → {mode, sha}. */
549
+ function treeEntries(root, sha) {
550
+ const out = g(root, [
551
+ "ls-tree",
552
+ "-rz",
553
+ sha
554
+ ]);
555
+ const map = /* @__PURE__ */ new Map();
556
+ for (const rec of out.split("\0")) {
557
+ if (rec === "") continue;
558
+ const tab = rec.indexOf(" ");
559
+ const [mode, , sha$1] = rec.slice(0, tab).split(" ");
560
+ const path = rec.slice(tab + 1);
561
+ if (tab === -1 || !mode || !sha$1 || !path) throw new Error(`malformed ls-tree record: ${JSON.stringify(rec.slice(0, 60))}`);
562
+ map.set(path, {
563
+ mode,
564
+ sha: sha$1
565
+ });
566
+ }
567
+ return map;
568
+ }
569
+ /** Line-union merge for append-only JSONL — order preserved, dupes dropped. */
570
+ function unionJsonl(a, b) {
571
+ const seen = /* @__PURE__ */ new Set();
572
+ const out = [];
573
+ for (const line of `${a}\n${b}`.split("\n")) {
574
+ const t = line.trim();
575
+ if (t === "" || seen.has(t)) continue;
576
+ seen.add(t);
577
+ out.push(t);
578
+ }
579
+ return `${out.join("\n")}\n`;
580
+ }
581
+ /** Tree-level merge of two data-ref heads: .jsonl unions by line, every
582
+ * other conflict resolves to ours — regenerated artifacts (summaries,
583
+ * indexes) are safe to clobber and the JSONL holds the real history. */
584
+ function mergeDataHeads(root, ref, ours, theirs) {
585
+ const a = treeEntries(root, ours);
586
+ const merged = new Map(a);
587
+ for (const [path, e] of treeEntries(root, theirs)) {
588
+ const cur = merged.get(path);
589
+ if (!cur) {
590
+ merged.set(path, e);
591
+ continue;
592
+ }
593
+ if (cur.sha !== e.sha && path.endsWith(".jsonl")) {
594
+ const union = unionJsonl(g(root, ["show", `${ours}:${path}`]), g(root, ["show", `${theirs}:${path}`]));
595
+ merged.set(path, {
596
+ mode: "100644",
597
+ sha: g(root, [
598
+ "hash-object",
599
+ "-w",
600
+ "--stdin"
601
+ ], union)
602
+ });
603
+ }
604
+ }
605
+ g(root, [
606
+ "update-ref",
607
+ ref,
608
+ g(root, [
609
+ "commit-tree",
610
+ withIndex(root, (ix) => {
611
+ ix(["read-tree", "--empty"]);
612
+ let info = "";
613
+ for (const [path, e] of merged) info += `${e.mode} ${e.sha}\t${path}\0`;
614
+ ix([
615
+ "update-index",
616
+ "-z",
617
+ "--index-info"
618
+ ], info);
619
+ return ix(["write-tree"]);
620
+ }),
621
+ "-p",
622
+ ours,
623
+ "-p",
624
+ theirs,
625
+ "-m",
626
+ "bro data: merge replicas"
627
+ ]),
628
+ ours
629
+ ]);
630
+ }
631
+ /**
632
+ * Commit the untracked+ignored files under relDir to the data ref.
633
+ * Returns the ref head (existing sha when the tree didn't change, null
634
+ * when there's nothing to sync).
635
+ */
636
+ function dataRefCommit(root, relDir, message, ref = DATA_REF) {
637
+ for (let attempt = 0; attempt < 3; attempt++) {
638
+ const base = refSha(root, ref);
639
+ const result = withIndex(root, (ix) => {
640
+ ix(base ? ["read-tree", base] : ["read-tree", "--empty"]);
641
+ const files = ix([
642
+ "ls-files",
643
+ "-o",
644
+ "-i",
645
+ "--exclude-standard",
646
+ "-z",
647
+ "--",
648
+ relDir
649
+ ]).split("\0").filter((f) => f !== "");
650
+ const staged = new Set(ix([
651
+ "ls-files",
652
+ "-z",
653
+ "--",
654
+ relDir
655
+ ]).split("\0").filter(Boolean));
656
+ for (const f of files) {
657
+ ix([
658
+ "update-index",
659
+ "--add",
660
+ "--",
661
+ f
662
+ ]);
663
+ staged.delete(f);
664
+ }
665
+ for (const gone of staged) ix([
666
+ "update-index",
667
+ "--remove",
668
+ "--",
669
+ gone
670
+ ]);
671
+ if (files.length === 0 && staged.size === 0 && base === null) return null;
672
+ return ix(["write-tree"]);
673
+ });
674
+ if (result === null) return null;
675
+ if (result === (base === null ? null : g(root, ["rev-parse", `${base}^{tree}`]))) return base;
676
+ const commit = g(root, [
677
+ "commit-tree",
678
+ result,
679
+ ...base ? ["-p", base] : [],
680
+ "-m",
681
+ message
682
+ ]);
683
+ if (gitTry([
684
+ "-C",
685
+ root,
686
+ "update-ref",
687
+ ref,
688
+ commit,
689
+ base ?? ""
690
+ ]).code === 0) return commit;
691
+ }
692
+ return refSha(root, ref);
693
+ }
694
+ /**
695
+ * Push the data ref with CAS retry. A rejected push means a replica won
696
+ * the race: fetch it, merge trees (JSONL union), retry. Returns false on
697
+ * offline/no-remote after bounded attempts — callers treat sync as
698
+ * best-effort and warn, never fail the command.
699
+ */
700
+ function dataRefPush(root, remote = "origin", ref = DATA_REF) {
701
+ for (let attempt = 0; attempt < 3; attempt++) {
702
+ if (gitTry([
703
+ "-C",
704
+ root,
705
+ "push",
706
+ remote,
707
+ `${ref}:${ref}`
708
+ ]).code === 0) return true;
709
+ if (gitTry([
710
+ "-C",
711
+ root,
712
+ "fetch",
713
+ remote,
714
+ ref
715
+ ]).code !== 0) return false;
716
+ const theirs = g(root, ["rev-parse", "FETCH_HEAD"]);
717
+ const ours = refSha(root, ref);
718
+ if (ours === null) return false;
719
+ if (gitTry([
720
+ "-C",
721
+ root,
722
+ "merge-base",
723
+ "--is-ancestor",
724
+ theirs,
725
+ ours
726
+ ]).code !== 0) mergeDataHeads(root, ref, ours, theirs);
727
+ }
728
+ return false;
729
+ }
730
+ /**
731
+ * Pull the remote data ref and write its files under the worktree —
732
+ * restores artifacts on a fresh clone. Local-only files are left alone;
733
+ * the ref is the truth for everything it contains. Returns the number of
734
+ * files written, or -1 when no remote ref exists.
735
+ */
736
+ function dataRefPull(root, remote = "origin", ref = DATA_REF) {
737
+ if (gitTry([
738
+ "-C",
739
+ root,
740
+ "fetch",
741
+ remote,
742
+ ref
743
+ ]).code !== 0) return -1;
744
+ const theirs = g(root, ["rev-parse", "FETCH_HEAD"]);
745
+ const ours = refSha(root, ref);
746
+ if (ours === null) g(root, [
747
+ "update-ref",
748
+ ref,
749
+ theirs
750
+ ]);
751
+ else if (gitTry([
752
+ "-C",
753
+ root,
754
+ "merge-base",
755
+ "--is-ancestor",
756
+ ours,
757
+ theirs
758
+ ]).code !== 0) if (gitTry([
759
+ "-C",
760
+ root,
761
+ "merge-base",
762
+ "--is-ancestor",
763
+ theirs,
764
+ ours
765
+ ]).code === 0) {} else mergeDataHeads(root, ref, ours, theirs);
766
+ else g(root, [
767
+ "update-ref",
768
+ ref,
769
+ theirs
770
+ ]);
771
+ const tracked = new Set(g(root, ["ls-files", "-z"]).split("\0").filter(Boolean));
772
+ const realRoot = realpathSync(root);
773
+ let written = 0;
774
+ for (const [path] of treeEntries(root, ref)) {
775
+ const dest = resolve(root, path);
776
+ if (dest !== root && !dest.startsWith(root + sep)) {
777
+ console.error(`warning: skipping out-of-root data ref path: ${path}`);
778
+ continue;
779
+ }
780
+ if (tracked.has(path)) {
781
+ console.error(`warning: skipping tracked path in data ref: ${path}`);
782
+ continue;
783
+ }
784
+ mkdirSync(dirname(dest), { recursive: true });
785
+ const realParent = realpathSync(dirname(dest));
786
+ if (realParent !== realRoot && !realParent.startsWith(realRoot + sep)) {
787
+ console.error(`warning: skipping data ref path through symlinked dir: ${path}`);
788
+ continue;
789
+ }
790
+ writeFileSync(dest, gRaw(root, ["show", `${ref}:${path}`]));
791
+ written += 1;
792
+ }
793
+ return written;
794
+ }
795
+ /** Identity helper — typed declaration sites, same role as defineConfig. */
796
+ function definePlugin(plugin) {
797
+ return plugin;
798
+ }
799
+ /** Standard verbs map CLI spelling → adapter method. Any other method
800
+ * name is itself a verb. */
801
+ const STANDARD_VERBS = {
802
+ list: "list",
803
+ show: "get",
804
+ get: "get",
805
+ new: "create",
806
+ create: "create",
807
+ set: "update",
808
+ update: "update",
809
+ rm: "remove",
810
+ remove: "remove"
811
+ };
812
+ /** Verb names an adapter exposes — its function members plus canonical
813
+ * spellings for the standard methods it implements. */
814
+ function docVerbs(adapter) {
815
+ const verbs = /* @__PURE__ */ new Set();
816
+ for (const [verb, method] of Object.entries(STANDARD_VERBS)) if (typeof adapter[method] === "function") verbs.add(verb);
817
+ for (const k of Object.keys(adapter)) if (!k.startsWith("_") && typeof adapter[k] === "function") verbs.add(k);
818
+ return [...verbs].sort((a, b) => a.localeCompare(b));
819
+ }
820
+ /** Resolve CLI verb → the adapter method to invoke (standard verbs map
821
+ * to their method names; anything else is taken literally). */
822
+ function verbMethod(adapter, verb) {
823
+ const method = adapter[STANDARD_VERBS[verb] ?? verb];
824
+ return typeof method === "function" ? method : void 0;
825
+ }
826
+ /** A type matches `token` by name or alias. */
827
+ function docTypeNamed(type, token) {
828
+ return type.name === token || (type.aliases?.includes(token) ?? false);
829
+ }
830
+ function filterArgs(f) {
831
+ const args = [];
832
+ if (f.status) args.push("--status", f.status);
833
+ for (const l of f.labels ?? []) args.push("-l", l);
834
+ for (const l of f.excludeLabels ?? []) args.push("--exclude-label", l);
835
+ if (f.all) args.push("--all");
836
+ if (f.limit !== void 0) args.push("-n", String(f.limit));
837
+ if (f.type) args.push("--type", f.type);
838
+ return args;
839
+ }
840
+ function createArgs(i) {
841
+ const args = [
842
+ "create",
843
+ "--title",
844
+ i.title
845
+ ];
846
+ if (i.description) args.push("-d", i.description);
847
+ if (i.type) args.push("-t", i.type);
848
+ if (i.priority !== void 0) args.push("-p", String(i.priority));
849
+ for (const l of i.labels ?? []) args.push("-l", l);
850
+ if (i.deps?.length) args.push("--deps", i.deps.join(","));
851
+ if (i.parent) args.push("--parent", i.parent);
852
+ if (i.ephemeral) args.push("--ephemeral");
853
+ if (i.noInheritLabels) args.push("--no-inherit-labels");
854
+ if (i.externalRef) args.push("--external-ref", i.externalRef);
855
+ if (i.metadata) args.push("--metadata", JSON.stringify(i.metadata));
856
+ return args;
857
+ }
858
+ /** The beads-backed TaskStore. `dir` selects the store via cwd —
859
+ * callers that need the global store resolve it themselves
860
+ * (`resolveGlobalDir` lives in the cli layer). */
861
+ function taskStore(dir) {
862
+ return {
863
+ list: (f = {}) => bdJson([
864
+ "list",
865
+ "--json",
866
+ ...filterArgs(f)
867
+ ], dir),
868
+ ready: (f = {}) => bdJson(["ready", ...filterArgs(f)], dir),
869
+ get: (id) => {
870
+ const r = bdJson(["show", id], dir);
871
+ return (Array.isArray(r) ? r[0] : r) ?? void 0;
872
+ },
873
+ create: (i) => {
874
+ const r = bdJson(createArgs(i), dir);
875
+ const row = Array.isArray(r) ? r[0] : r;
876
+ if (!row) throw new Error("task create returned no row — backend contract broken");
877
+ return row;
878
+ },
879
+ update: (id, patch) => {
880
+ const args = ["update", id];
881
+ for (const [k, v] of Object.entries(patch)) args.push(...v === "true" ? [`--${k}`] : [`--${k}`, String(v)]);
882
+ bd(args, dir);
883
+ },
884
+ claim: (id) => {
885
+ bd([
886
+ "update",
887
+ id,
888
+ "--claim"
889
+ ], dir);
890
+ },
891
+ reopen: (id) => {
892
+ bd(["reopen", id], dir);
893
+ },
894
+ close: (id, reason) => {
895
+ bd([
896
+ "close",
897
+ id,
898
+ ...reason ? ["--reason", reason] : []
899
+ ], dir);
900
+ },
901
+ remove: (id) => {
902
+ bd([
903
+ "delete",
904
+ id,
905
+ "--force"
906
+ ], dir);
907
+ },
908
+ note: (id, text) => {
909
+ bd([
910
+ "note",
911
+ id,
912
+ text
913
+ ], dir);
914
+ },
915
+ children: (id) => bdJson(["children", id], dir),
916
+ deps: (ids, opts = {}) => bdJson([
917
+ "dep",
918
+ "list",
919
+ ...ids,
920
+ ...opts.type ? ["-t", opts.type] : [],
921
+ ...opts.direction ? [`--direction=${opts.direction}`] : []
922
+ ], dir),
923
+ link: (from, to, type = "related") => {
924
+ bd([
925
+ "link",
926
+ from,
927
+ to,
928
+ "--type",
929
+ type
930
+ ], dir);
931
+ },
932
+ prefix: () => {
933
+ const probe = bdTry([
934
+ "config",
935
+ "get",
936
+ "issue_prefix"
937
+ ], 15e3, dir);
938
+ if (probe.code !== 0) throw new Error(`task store unreachable — ${probe.err || "bd error"}`);
939
+ const line = probe.out.trim().split("\n").pop()?.trim() ?? "";
940
+ if (line === "" || /not set/i.test(line)) return;
941
+ const eq = line.indexOf("=");
942
+ const v = (eq >= 0 ? line.slice(eq + 1) : line).trim().replace(/^['"]|['"]$/g, "");
943
+ return v === "" ? void 0 : v;
944
+ }
945
+ };
946
+ }
947
+ const shortTitle = (t) => {
948
+ const flat = (t ?? "").replace(/\s+/g, " ").trim();
949
+ return flat.length > 60 ? `${flat.slice(0, 60)}…` : flat;
950
+ };
951
+ /** Bead ids this session claimed — the `task` aspect marker records one
952
+ * claim per detail line (line 1 is the timestamp). No session or no
953
+ * marker → nothing is "mine". Exported for policy connectors (sdd)
954
+ * that scope their nudges to the session's own claims. */
955
+ function sessionTaskClaims(ctx) {
956
+ const mine = /* @__PURE__ */ new Set();
957
+ if (!ctx.sessionId) return mine;
958
+ try {
959
+ const gd = gitTry([
960
+ "-C",
961
+ ctx.dir,
962
+ "rev-parse",
963
+ "--git-common-dir"
964
+ ]);
965
+ if (gd.code !== 0) return mine;
966
+ const safe = ctx.sessionId.replace(/[^\w.-]/g, "_");
967
+ const marker = join(resolve(ctx.dir, gd.out.trim()), "bro", "hooks", `${safe}.task`);
968
+ for (const d of readFileSync(marker, "utf8").split("\n").slice(1)) {
969
+ const id = d.trim();
970
+ if (id) mine.add(id);
971
+ }
972
+ } catch {}
973
+ return mine;
974
+ }
975
+ const registry = [{
976
+ name: "beads",
977
+ tasks: (ctx) => taskStore(ctx.dir),
978
+ hooks: () => ({
979
+ sessionStart(ctx) {
980
+ try {
981
+ const ready = taskStore(ctx.dir).ready().slice(0, 8).map((r) => ` ${r.id} ${shortTitle(r.title)}`.trimEnd());
982
+ return ready.length > 0 ? [`bd ready:\n${ready.join("\n")}`] : [];
983
+ } catch {
984
+ return [];
985
+ }
986
+ },
987
+ parallelWork(ctx) {
988
+ try {
989
+ const mine = sessionTaskClaims(ctx);
990
+ const claimed = taskStore(ctx.dir).list({ status: "in_progress" }).filter((r) => !mine.has(r.id)).slice(0, 5).map((r) => `${r.id} ${shortTitle(r.title)}`.trim());
991
+ return claimed.length > 0 ? [`claimed beads: ${claimed.join(", ")}`] : [];
992
+ } catch {
993
+ return [];
994
+ }
995
+ },
996
+ stopGate(ctx) {
997
+ try {
998
+ const claimed = taskStore(ctx.dir).list({ status: "in_progress" });
999
+ if (claimed.length === 0) return [];
1000
+ const mine = sessionTaskClaims(ctx);
1001
+ const own = claimed.filter((r) => mine.has(r.id));
1002
+ const foreign = claimed.filter((r) => !mine.has(r.id));
1003
+ const fmt = (r) => `${r.id} ${shortTitle(r.title)}`.trim();
1004
+ const out = [];
1005
+ if (own.length > 0) out.push({
1006
+ aspect: "task",
1007
+ block: `bro: claimed beads open: ${own.slice(0, 5).map(fmt).join(", ")} — close (\`bd close <id>\`) or release the claim before stopping`
1008
+ });
1009
+ if (foreign.length > 0) out.push({
1010
+ aspect: "task",
1011
+ passive: `bro: claimed beads open: ${foreign.slice(0, 5).map(fmt).join(", ")}`
1012
+ });
1013
+ return out;
1014
+ } catch {
1015
+ return [];
1016
+ }
1017
+ }
1018
+ })
1019
+ }];
1020
+ /** Plugin connectors register here (BroPlugin.connectors). Duplicate
1021
+ * names are skipped — a plugin cannot shadow a built-in system. */
1022
+ function registerConnector(c) {
1023
+ if (registry.some((x) => x.name === c.name)) {
1024
+ console.error(`warning: connector "${c.name}" already registered — skipped`);
1025
+ return;
1026
+ }
1027
+ registry.push(c);
1028
+ }
1029
+ function remoteUrl(dir) {
1030
+ const r = gitTry([
1031
+ "-C",
1032
+ dir,
1033
+ "remote",
1034
+ "get-url",
1035
+ "origin"
1036
+ ]);
1037
+ return r.code === 0 && r.out.trim() !== "" ? r.out.trim() : void 0;
1038
+ }
1039
+ /** The connector chosen to serve `kind` — shared by facade() and auth
1040
+ * probing so both resolve through the same precedence. */
1041
+ function pickConnector(kind, ctx, opts) {
1042
+ const providers = registry.filter((c) => typeof c[kind] === "function");
1043
+ const named = opts.connector ?? opts.prefer?.[kind];
1044
+ let pick;
1045
+ if (named !== void 0) {
1046
+ pick = providers.find((c) => c.name === named);
1047
+ if (!pick) throw new Error(`connector "${named}" does not provide "${kind}"`);
1048
+ } else {
1049
+ const url = remoteUrl(ctx.dir);
1050
+ const remote = url === void 0 ? void 0 : providers.find((c) => c.matchRemote?.(url));
1051
+ pick = remote ?? providers[0];
1052
+ if (!pick) throw new Error(`no connector provides "${kind}"`);
1053
+ if (remote === void 0 && providers.length > 1) console.error(`warning: ${providers.map((c) => c.name).join(", ")} all provide "${kind}" — using ${pick.name}; set connectors.${kind} in bro.config.json`);
1054
+ }
1055
+ return pick;
1056
+ }
1057
+ /** Resolve the facade a command needs. Throws when nothing provides it —
1058
+ * callers that probe rather than require should catch or pre-check
1059
+ * `connectors()`. */
1060
+ function facade(kind, ctx, opts = {}) {
1061
+ const provides = pickConnector(kind, ctx, opts)[kind];
1062
+ return provides(ctx);
1063
+ }
1064
+ /** The serving connector's auth probe — null when ready, else the
1065
+ * remediation line. Resolution failures surface as the message: a
1066
+ * missing provider is as unusable as a missing credential. */
1067
+ function facadeAuth(kind, ctx, opts = {}) {
1068
+ try {
1069
+ return pickConnector(kind, ctx, opts).auth?.(ctx) ?? null;
1070
+ } catch (err) {
1071
+ return err instanceof Error ? err.message : String(err);
1072
+ }
1073
+ }
1074
+ /** Hard auth gate for commands — exits(1) with the remediation line when
1075
+ * the resolved connector isn't usable. */
1076
+ function ensureAuth(kind, ctx, opts = {}) {
1077
+ const msg = facadeAuth(kind, ctx, opts);
1078
+ if (msg) {
1079
+ console.error(`error: ${msg}`);
1080
+ process.exit(1);
1081
+ }
1082
+ }
1083
+ /** facade('reviews') bound to a dir — the common resolution path for
1084
+ * review-host commands (act/debt/hooks). */
1085
+ function reviewHost(dir = process.cwd(), prefer) {
1086
+ return facade("reviews", { dir }, { prefer });
1087
+ }
1088
+ /** Every registered connector's hook probes — hooks collect, never pick:
1089
+ * each system reports its own ambient state. */
1090
+ function connectorHooks(ctx) {
1091
+ const out = [];
1092
+ for (const c of registry) try {
1093
+ const h = c.hooks?.(ctx);
1094
+ if (h) out.push(h);
1095
+ } catch {}
1096
+ return out;
1097
+ }
1098
+ /** Per-probe budget — a hung connector (dead network, wedged CLI) must
1099
+ * not stall the whole hook. Racing the probe means the output goes out
1100
+ * on time even if a spawned child lingers. */
1101
+ const PROBE_TIMEOUT_MS = 4e3;
1102
+ async function probeWithTimeout(p, fallback) {
1103
+ let timer;
1104
+ const timeout = new Promise((resolve$1) => {
1105
+ timer = setTimeout(() => resolve$1(fallback), PROBE_TIMEOUT_MS);
1106
+ timer.unref?.();
1107
+ });
1108
+ try {
1109
+ return await Promise.race([Promise.resolve(p), timeout]);
1110
+ } finally {
1111
+ clearTimeout(timer);
1112
+ }
1113
+ }
1114
+ /** Collect a line-producing probe across all connectors — fail-open
1115
+ * per connector, one wedged system must not starve the rest. */
1116
+ async function collectLines(ctx, probe) {
1117
+ const out = [];
1118
+ for (const h of connectorHooks(ctx)) try {
1119
+ out.push(...await probeWithTimeout(probe(h), void 0) ?? []);
1120
+ } catch {}
1121
+ return out;
1122
+ }
1123
+ /** Collect session-start context lines from all connectors. */
1124
+ function sessionStartLines(ctx) {
1125
+ return collectLines(ctx, (h) => h.sessionStart?.(ctx));
1126
+ }
1127
+ /** Collect parallel-work signals from all connectors. */
1128
+ function parallelWorkLines(ctx) {
1129
+ return collectLines(ctx, (h) => h.parallelWork?.(ctx));
1130
+ }
1131
+ /** Collect prompt-submit context from all connectors. */
1132
+ function promptContextLines(ctx, prompt) {
1133
+ return collectLines(ctx, (h) => h.promptSubmit?.(ctx, prompt));
1134
+ }
1135
+ /** Collect stop-gate contributions from all connectors — the caller
1136
+ * applies the session-arming policy to each. */
1137
+ async function stopGateContributions(ctx) {
1138
+ const out = [];
1139
+ for (const h of connectorHooks(ctx)) try {
1140
+ out.push(...await probeWithTimeout(h.stopGate?.(ctx), void 0) ?? []);
1141
+ } catch {}
1142
+ return out;
1143
+ }
1144
+ const isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
1145
+ /** Reads + TOML-parses a plan file. Throws with the file path in the
1146
+ * message — callers never add context of their own. */
1147
+ function readPlanDoc(file) {
1148
+ let text;
1149
+ try {
1150
+ text = readFileSync(file, "utf8");
1151
+ } catch (err) {
1152
+ const msg = err instanceof Error ? err.message : String(err);
1153
+ throw new Error(`${file}: cannot read plan — ${msg}`);
1154
+ }
1155
+ let doc;
1156
+ try {
1157
+ doc = parse(text);
1158
+ } catch (err) {
1159
+ throw new Error(`${file}: invalid TOML — ${err instanceof Error ? err.message : String(err)}`);
1160
+ }
1161
+ if (!isRecord(doc)) throw new Error(`${file}: plan must be a TOML table`);
1162
+ return doc;
1163
+ }
1164
+ /** The envelope's routing key — undefined when absent or non-string. */
1165
+ function planKind(doc) {
1166
+ return typeof doc.kind === "string" ? doc.kind : void 0;
1167
+ }
1168
+ function makePrinter(_personality) {
1169
+ return {
1170
+ info: (msg) => console.error(msg),
1171
+ warn: (msg) => console.error(`warning: ${msg}`),
1172
+ data: (line) => console.log(line)
1173
+ };
1174
+ }
1175
+
1176
+ //#endregion
1177
+ export { parallelWorkLines as A, stackSection as B, ghJson as C, initBeadsStealth as D, gitTry as E, registerConnector as F, syncSection as H, reviewHost as I, sddSection as L, promptContextLines as M, readPlanDoc as N, loadConfig as O, refKind as P, sessionStartLines as R, gh as S, git as T, taskStore as U, stopGateContributions as V, verbMethod as W, docTypeNamed as _, bd as a, evidenceKind as b, beadsSection as c, dataRefPull as d, dataRefPush as f, definePlugin as g, defineConfig as h, actSection as i, planKind as j, makePrinter as k, checkBeads as l, debtSection as m, PERSONALITIES as n, bdJson as o, dataRefRoot as p, STANDARD_VERBS as r, bdTry as s, DEFAULT_GLOBAL_BEADS_DIR as t, dataRefCommit as u, docVerbs as v, ghTry as w, facade as x, ensureAuth as y, sessionTaskClaims as z };