@broject/drill 0.0.0 → 0.2.3

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/README.md CHANGED
@@ -1,3 +1,29 @@
1
1
  # @broject/drill
2
2
 
3
- Placeholder for @broject/drill published by @nx-devkit/prepare-for-release.
3
+ Scoped descent frames behind `bro drill` — `drill down` opens a narrower
4
+ investigation frame (materialized as a bead), `drill up` closes it with a
5
+ mandatory result + prevention memo so a problem can't silently recur.
6
+
7
+ > You probably want the CLI instead: `bro drill down --title …`.
8
+ > Install this only when building agents that run drill frames.
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ npm i @broject/drill
14
+ ```
15
+
16
+ Requires Node ≥ 22 and `bd` (frames are beads). ESM only.
17
+
18
+ ## Surface
19
+
20
+ - `drillConnector` — registers `bro drill *` subcommands
21
+ - `PreventionPlan` — the required close-out artifact
22
+ - `parseDrillPlan` / `DRILL_PLAN_KIND` — the unified drill plan payload
23
+ - Re-exports `bd`/`taskStore` primitives from `@broject/core`
24
+
25
+ ## Links
26
+
27
+ - Docs: https://broject.dev/docs/commands/drill
28
+ - Source: https://github.com/ThePlenkov/bro/tree/main/packages/drill
29
+ - CLI: https://www.npmjs.com/package/@broject/bro
@@ -0,0 +1,114 @@
1
+ import { Connector, bd, bdJson, checkBeads, refKind, taskStore } from "@broject/core";
2
+
3
+ //#region src/types.d.ts
4
+ interface DrillRow {
5
+ id: string;
6
+ title: string;
7
+ status: string;
8
+ priority?: number;
9
+ issue_type?: string;
10
+ created_at?: string;
11
+ updated_at?: string;
12
+ labels?: string[];
13
+ notes?: string;
14
+ ephemeral?: boolean;
15
+ }
16
+ interface DrillFrame extends DrillRow {
17
+ parentId?: string;
18
+ depth: number;
19
+ }
20
+ interface DownOptions {
21
+ under?: string;
22
+ /** Force a root frame — by default `down` nests under the current leaf. */
23
+ root?: boolean;
24
+ ephemeral?: boolean;
25
+ type?: string;
26
+ priority?: number;
27
+ description?: string;
28
+ }
29
+ interface UpOptions {
30
+ id?: string;
31
+ result: string;
32
+ prevent?: string[];
33
+ evidence?: string[];
34
+ }
35
+ interface UpResult {
36
+ closed: string;
37
+ preventionIds: string[];
38
+ }
39
+ //#endregion
40
+ //#region src/frames.d.ts
41
+ declare function listDrills(): DrillRow[];
42
+ declare function childrenOf(id: string): DrillRow[];
43
+ /**
44
+ * The active frame: an open drill leaf (no open drill children) on the
45
+ * deepest path. Ties break on most-recently-updated — the frame the agent
46
+ * touched last is almost always the live one.
47
+ */
48
+ declare function currentFrame(): DrillFrame | undefined;
49
+ /** Descend: create a child frame under `opts.under` or the current leaf;
50
+ * `opts.root` forces a parentless frame even when a leaf is open. */
51
+ declare function drillDown(title: string, opts?: DownOptions): DrillRow;
52
+ interface PreventionPlan {
53
+ /** Normalized item key (`titleKey`) → existing bead id. */
54
+ reuse: Map<string, string>;
55
+ /** Item titles needing a new bead, in order, deduped, trimmed. */
56
+ create: string[];
57
+ }
58
+ /** Fold `--prevent` items against what the frame already recorded: an
59
+ * open prevention bead with the same title is reused, not recreated;
60
+ * repeated items within the list collapse to one bead. Pure — the
61
+ * retry-safety core of drillUp: a mid-flight failure followed by a
62
+ * retry converges instead of duplicating. */
63
+ declare function planPreventions(items: string[], prior: DrillRow[]): PreventionPlan;
64
+ /**
65
+ * Ascend: close the frame with a structured memo. `--result` is mandatory —
66
+ * a drill that returns nothing teaches nothing. Each `--prevent` item lands
67
+ * as a task on the parent frame so prevention work lives in the scope that
68
+ * spawned it.
69
+ */
70
+ declare function drillUp(opts: UpOptions): UpResult;
71
+ /** Root frames + rendered tree (indented, roots first). */
72
+ declare function drillTree(): string;
73
+ //#endregion
74
+ //#region src/plan.d.ts
75
+ /**
76
+ * Drill plans — a declared descent tree. The agent writes the
77
+ * investigation shape once, `bro run drill.toml` materializes the
78
+ * frames as beads; investigation fills each frame via `drill up`
79
+ * as usual.
80
+ *
81
+ * kind = "drill"
82
+ * title = "why does the cache miss" # root frame
83
+ *
84
+ * [[steps]]
85
+ * title = "check the parser" # child of root
86
+ *
87
+ * [[steps]]
88
+ * title = "narrow the repro"
89
+ * under = 0 # child of steps[0]
90
+ * ephemeral = true
91
+ */
92
+ interface DrillStep {
93
+ title: string;
94
+ /** index into `steps` — nested under that step; default = root */
95
+ under?: number;
96
+ ephemeral?: boolean;
97
+ description?: string;
98
+ priority?: number;
99
+ type?: string;
100
+ }
101
+ interface DrillPlan {
102
+ title: string;
103
+ steps: DrillStep[];
104
+ }
105
+ /** The `kind` value a drill plan must carry — `bro run` routes on it. */
106
+ declare const PLAN_KIND = "drill";
107
+ /** Validate an already-parsed plan document — the plugin planSchema.
108
+ * Throws one error listing every problem. */
109
+ declare function parseDrillPlan(doc: unknown, source?: string): DrillPlan;
110
+ //#endregion
111
+ //#region src/connector.d.ts
112
+ declare const drillConnector: Connector;
113
+ //#endregion
114
+ export { PLAN_KIND as DRILL_PLAN_KIND, type DownOptions, type DrillFrame, type DrillPlan, type DrillRow, type DrillStep, type PreventionPlan, type UpOptions, type UpResult, bd, bdJson, checkBeads, childrenOf, currentFrame, drillConnector, drillDown, drillTree, drillUp, listDrills, parseDrillPlan, planPreventions, refKind, taskStore };
package/dist/index.js ADDED
@@ -0,0 +1,431 @@
1
+ import { bd, bd as bd$1, bdJson, bdJson as bdJson$1, checkBeads, evidenceKind, refKind, refKind as refKind$1, taskStore, taskStore as taskStore$1 } from "@broject/core";
2
+
3
+ //#region src/frames.ts
4
+ const DRILL_LABEL = "drill";
5
+ const PREVENTION_LABEL = "prevention";
6
+ function isDrill(row) {
7
+ return row.labels?.includes(DRILL_LABEL) ?? false;
8
+ }
9
+ function isOpen(row) {
10
+ return row.status !== "closed" && row.status !== "done";
11
+ }
12
+ function listDrills() {
13
+ const persistent = taskStore$1().list({
14
+ labels: [DRILL_LABEL],
15
+ all: true,
16
+ limit: 0
17
+ });
18
+ let wisps = [];
19
+ try {
20
+ wisps = (bdJson$1([
21
+ "mol",
22
+ "wisp",
23
+ "list",
24
+ "--all"
25
+ ]).wisps ?? []).filter(isDrill);
26
+ } catch (err) {
27
+ const msg = err instanceof Error ? err.message : String(err);
28
+ if (!/unknown command|unrecognized command/i.test(msg)) throw err;
29
+ }
30
+ return [...persistent, ...wisps];
31
+ }
32
+ function childrenOf(id) {
33
+ return taskStore$1().children(id);
34
+ }
35
+ /** One `bd dep list` sweep: parent→kids and kid→parent in a single call
36
+ * (was N+1 `bd children`). kids holds DRILL children only — every
37
+ * consumer filters on isDrill anyway; drillUp's any-child close check
38
+ * still uses childrenOf directly. */
39
+ function drillRelations(rows) {
40
+ const kids = /* @__PURE__ */ new Map();
41
+ const parents = /* @__PURE__ */ new Map();
42
+ if (rows.length === 0) return {
43
+ kids,
44
+ parents
45
+ };
46
+ const byId = new Map(rows.map((r) => [r.id, r]));
47
+ const edges = taskStore$1().deps(rows.map((r) => r.id), { type: "parent-child" });
48
+ for (const e of edges) {
49
+ const kid = byId.get(e.issue_id);
50
+ const parent = byId.get(e.depends_on_id);
51
+ if (e.type !== "parent-child" || !kid || !parent) continue;
52
+ kids.set(parent.id, [...kids.get(parent.id) ?? [], kid]);
53
+ parents.set(kid.id, parent.id);
54
+ }
55
+ return {
56
+ kids,
57
+ parents
58
+ };
59
+ }
60
+ /**
61
+ * The active frame: an open drill leaf (no open drill children) on the
62
+ * deepest path. Ties break on most-recently-updated — the frame the agent
63
+ * touched last is almost always the live one.
64
+ */
65
+ function currentFrame() {
66
+ const rows = listDrills().filter(isOpen);
67
+ if (rows.length === 0) return;
68
+ const { kids, parents } = drillRelations(rows);
69
+ const depthOf = (id) => {
70
+ let d = 0;
71
+ let cur = id;
72
+ while ((cur = parents.get(cur)) !== void 0) d += 1;
73
+ return d;
74
+ };
75
+ const leaves = rows.filter((r) => !(kids.get(r.id) ?? []).some((k) => isOpen(k) && isDrill(k)));
76
+ leaves.sort((a, b) => {
77
+ const d = depthOf(b.id) - depthOf(a.id);
78
+ return d !== 0 ? d : (b.updated_at ?? "").localeCompare(a.updated_at ?? "");
79
+ });
80
+ const leaf = leaves[0];
81
+ if (!leaf) return;
82
+ return {
83
+ ...leaf,
84
+ parentId: parents.get(leaf.id),
85
+ depth: depthOf(leaf.id)
86
+ };
87
+ }
88
+ /** The bead must be an open drill frame, or an explicit selector is wrong. */
89
+ function requireOpenDrill(id, flag) {
90
+ const row = taskStore$1().get(id);
91
+ if (!row || !isDrill(row) || !isOpen(row)) throw new Error(`${flag} ${id} is not an open drill frame`);
92
+ return row;
93
+ }
94
+ /** Record the claim provenance; on failure, delete the frame — an
95
+ * unclaimed frame violates the claim-on-down invariant, and a retry would
96
+ * create a duplicate. A failed cleanup must not swallow the claim error. */
97
+ function claimFrame(id) {
98
+ try {
99
+ bd$1([
100
+ "provenance",
101
+ "record",
102
+ "--issue",
103
+ id,
104
+ "--kind",
105
+ "claim",
106
+ "--source",
107
+ "bro drill down",
108
+ "--at",
109
+ (/* @__PURE__ */ new Date()).toISOString()
110
+ ]);
111
+ } catch (err) {
112
+ try {
113
+ taskStore$1().remove(id);
114
+ } catch (cleanupErr) {
115
+ throw new Error(`claim failed: ${err instanceof Error ? err.message : err}; cleanup of ${id} also failed (frame left unclaimed): ${cleanupErr instanceof Error ? cleanupErr.message : cleanupErr}`);
116
+ }
117
+ throw err;
118
+ }
119
+ }
120
+ /** Descend: create a child frame under `opts.under` or the current leaf;
121
+ * `opts.root` forces a parentless frame even when a leaf is open. */
122
+ function drillDown(title, opts = {}) {
123
+ let parent;
124
+ if (opts.under) parent = requireOpenDrill(opts.under, "--under").id;
125
+ else if (!opts.root) parent = currentFrame()?.id;
126
+ const row = taskStore$1().create({
127
+ title,
128
+ labels: [DRILL_LABEL],
129
+ parent,
130
+ ephemeral: opts.ephemeral,
131
+ type: opts.type,
132
+ priority: opts.priority,
133
+ description: opts.description
134
+ });
135
+ if (!opts.ephemeral) claimFrame(row.id);
136
+ return row;
137
+ }
138
+ /** Open prevention beads already discovered-from this frame — the
139
+ * dedupe set that makes prevention creation retry-safe. Throws when bd
140
+ * returns an unexpected row shape (e.g. dependency-edge objects instead
141
+ * of hydrated issues): a wrong shape would mask as "no priors" and
142
+ * silently resurrect the duplicate-on-retry bug this query prevents. */
143
+ function priorPreventionRows(frameId) {
144
+ const rows = taskStore$1().deps([frameId], {
145
+ direction: "up",
146
+ type: "discovered-from"
147
+ });
148
+ assertHydratedRows(rows, `bd dep list for ${frameId}`);
149
+ return rows;
150
+ }
151
+ /** Fail loudly when a bd listing returns rows without `id`/`title`/`status` —
152
+ * e.g. dependency-edge objects after a bd upgrade. `context` names the
153
+ * query so the error identifies its source. Exported for tests. */
154
+ function assertHydratedRows(rows, context) {
155
+ for (const row of rows) if (typeof row?.id !== "string" || typeof row?.title !== "string" || typeof row?.status !== "string") throw new Error(`${context} returned an unexpected row shape (expected hydrated issue rows): ${JSON.stringify(row).slice(0, 160)}`);
156
+ }
157
+ /** Same-title dedupe key: "handle race" and " Handle Race " are the
158
+ * same prevention — exact-match would duplicate the bead on retry. */
159
+ function titleKey(title) {
160
+ return title.trim().toLowerCase();
161
+ }
162
+ /** Fold `--prevent` items against what the frame already recorded: an
163
+ * open prevention bead with the same title is reused, not recreated;
164
+ * repeated items within the list collapse to one bead. Pure — the
165
+ * retry-safety core of drillUp: a mid-flight failure followed by a
166
+ * retry converges instead of duplicating. */
167
+ function planPreventions(items, prior) {
168
+ const reuse = /* @__PURE__ */ new Map();
169
+ for (const row of prior) {
170
+ const key = titleKey(row.title ?? "");
171
+ if (key && isOpen(row) && row.labels?.includes(PREVENTION_LABEL) && !reuse.has(key)) reuse.set(key, row.id);
172
+ }
173
+ const seen = /* @__PURE__ */ new Set();
174
+ const create = [];
175
+ for (const item of items) {
176
+ const key = titleKey(item);
177
+ if (!key || reuse.has(key) || seen.has(key)) continue;
178
+ seen.add(key);
179
+ create.push(item.trim());
180
+ }
181
+ return {
182
+ create,
183
+ reuse
184
+ };
185
+ }
186
+ /** One prevention bead per item — discovered-from, not --parent:
187
+ * prevention is follow-up work found by the frame, and a child would
188
+ * block the parent's own close. `--title` keeps a `--`-leading item as
189
+ * data, not a flag. `created` holds only beads this call made — the
190
+ * compensation set; `ids` is the full per-item result (reused + new). */
191
+ function createPreventions(frameId, items) {
192
+ const { create, reuse } = planPreventions(items, priorPreventionRows(frameId));
193
+ const created = [];
194
+ const newIds = /* @__PURE__ */ new Map();
195
+ for (const item of create) {
196
+ const row = taskStore$1().create({
197
+ title: item,
198
+ labels: [PREVENTION_LABEL],
199
+ noInheritLabels: true,
200
+ deps: [`discovered-from:${frameId}`]
201
+ });
202
+ created.push(row.id);
203
+ newIds.set(titleKey(item), row.id);
204
+ }
205
+ const ids = [];
206
+ for (const item of items) {
207
+ const key = titleKey(item);
208
+ if (!key) continue;
209
+ const id = reuse.get(key) ?? newIds.get(key);
210
+ if (!id) throw new Error(`internal error: no bead id for prevention item "${item}"`);
211
+ ids.push(id);
212
+ }
213
+ return {
214
+ created,
215
+ ids
216
+ };
217
+ }
218
+ /** `bd note` appends — check the stored notes first so a retry after a
219
+ * later failure can't duplicate the memo. */
220
+ function noteOnce(frameId, memo) {
221
+ if (taskStore$1().get(frameId)?.notes?.includes(memo)) return;
222
+ taskStore$1().note(frameId, memo);
223
+ }
224
+ /** Evidence refs + handoff event — skipped for wisps (no audit trail).
225
+ * Ref'd events are idempotent in bd (deterministic id); the ref-less
226
+ * handoff gets a fresh `--at` each run, so skip it when already logged. */
227
+ function recordHandoff(frame, evidence) {
228
+ for (const ref of evidence) {
229
+ const kind = refKind$1(ref);
230
+ bd$1([
231
+ "provenance",
232
+ "record",
233
+ "--issue",
234
+ frame.id,
235
+ "--kind",
236
+ evidenceKind(ref),
237
+ "--source",
238
+ "bro drill up",
239
+ "--ref",
240
+ ref,
241
+ "--ref-kind",
242
+ kind
243
+ ]);
244
+ }
245
+ if (bdJson$1([
246
+ "provenance",
247
+ "log",
248
+ frame.id
249
+ ]).some((e) => e.kind === "handoff" && e.source === "bro drill up")) return;
250
+ bd$1([
251
+ "provenance",
252
+ "record",
253
+ "--issue",
254
+ frame.id,
255
+ "--kind",
256
+ "handoff",
257
+ "--source",
258
+ "bro drill up",
259
+ "--at",
260
+ (/* @__PURE__ */ new Date()).toISOString()
261
+ ]);
262
+ }
263
+ /**
264
+ * Ascend: close the frame with a structured memo. `--result` is mandatory —
265
+ * a drill that returns nothing teaches nothing. Each `--prevent` item lands
266
+ * as a task on the parent frame so prevention work lives in the scope that
267
+ * spawned it.
268
+ */
269
+ function drillUp(opts) {
270
+ if (!opts.result.trim()) throw new Error("drill up requires --result — a frame must return a curated finding");
271
+ const frame = opts.id ? requireOpenDrill(opts.id, "--id") : (() => {
272
+ const leaf = currentFrame();
273
+ if (!leaf) throw new Error("no open drill frame — nothing to ascend from");
274
+ return requireOpenDrill(leaf.id, "--id");
275
+ })();
276
+ const openKids = childrenOf(frame.id).filter(isOpen);
277
+ if (openKids.length > 0) throw new Error(`frame ${frame.id} has open child issue(s): ${openKids.map((k) => k.id).join(", ")} — close them first`);
278
+ const prevents = (opts.prevent ?? []).map((p) => p.trim()).filter((p) => p !== "");
279
+ const memo = [
280
+ "## Result",
281
+ "",
282
+ opts.result,
283
+ ...prevents.length ? [
284
+ "",
285
+ "## Prevention",
286
+ "",
287
+ ...prevents.map((p) => `- ${p}`)
288
+ ] : []
289
+ ].join("\n");
290
+ let created = [];
291
+ let preventionIds = [];
292
+ try {
293
+ noteOnce(frame.id, memo);
294
+ const prev = createPreventions(frame.id, prevents);
295
+ created = prev.created;
296
+ preventionIds = prev.ids;
297
+ if (!frame.ephemeral) recordHandoff(frame, opts.evidence ?? []);
298
+ taskStore$1().close(frame.id, "drill up — result handed to parent");
299
+ } catch (err) {
300
+ const orphans = [];
301
+ for (const id of created) try {
302
+ taskStore$1().remove(id);
303
+ } catch {
304
+ orphans.push(id);
305
+ }
306
+ if (orphans.length === 0) throw err;
307
+ const msg = err instanceof Error ? err.message : String(err);
308
+ throw new Error(`${msg} — cleanup incomplete: prevention bead(s) left behind: ${orphans.join(", ")}`, { cause: err });
309
+ }
310
+ return {
311
+ closed: frame.id,
312
+ preventionIds
313
+ };
314
+ }
315
+ /** Root frames + rendered tree (indented, roots first). */
316
+ function drillTree() {
317
+ const rows = listDrills();
318
+ if (rows.length === 0) return "no drill frames";
319
+ const { kids, parents } = drillRelations(rows);
320
+ const roots = rows.filter((r) => !parents.has(r.id));
321
+ const lines = [];
322
+ const walk = (row, depth) => {
323
+ const mark = isOpen(row) ? "●" : "○";
324
+ lines.push(`${" ".repeat(depth)}${mark} ${row.id} ${row.title} [${row.status}]`);
325
+ for (const kid of (kids.get(row.id) ?? []).filter(isDrill)) walk(kid, depth + 1);
326
+ };
327
+ for (const root of roots) walk(root, 0);
328
+ return lines.join("\n");
329
+ }
330
+
331
+ //#endregion
332
+ //#region src/plan.ts
333
+ /** The `kind` value a drill plan must carry — `bro run` routes on it. */
334
+ const PLAN_KIND = "drill";
335
+ const STEP_KEYS = new Set([
336
+ "title",
337
+ "under",
338
+ "ephemeral",
339
+ "description",
340
+ "priority",
341
+ "type"
342
+ ]);
343
+ const isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
344
+ const nonEmpty = (v) => typeof v === "string" && v.trim() !== "";
345
+ const isPosInt = (v) => typeof v === "number" && Number.isInteger(v) && v > 0;
346
+ const isInt = (v) => typeof v === "number" && Number.isInteger(v) && v >= 0;
347
+ function checkStep(raw, where, errors) {
348
+ for (const key of Object.keys(raw)) if (!STEP_KEYS.has(key)) errors.push(`${where}: unknown key "${key}"`);
349
+ if (!nonEmpty(raw.title)) errors.push(`${where}: title is required`);
350
+ if (raw.under !== void 0 && !isInt(raw.under)) errors.push(`${where}: under must be a non-negative step index`);
351
+ for (const f of ["description", "type"]) if (raw[f] !== void 0 && typeof raw[f] !== "string") errors.push(`${where}: ${f} must be a string`);
352
+ if (raw.ephemeral !== void 0 && typeof raw.ephemeral !== "boolean") errors.push(`${where}: ephemeral must be a boolean`);
353
+ if (raw.priority !== void 0 && !isPosInt(raw.priority)) errors.push(`${where}: priority must be a positive integer`);
354
+ }
355
+ function parseStep(raw, i, errors) {
356
+ const where = `steps[${i}]`;
357
+ if (!isRecord(raw)) {
358
+ errors.push(`${where}: must be a table`);
359
+ return;
360
+ }
361
+ checkStep(raw, where, errors);
362
+ if (!nonEmpty(raw.title)) return;
363
+ return {
364
+ title: raw.title.trim(),
365
+ under: typeof raw.under === "number" ? raw.under : void 0,
366
+ ephemeral: raw.ephemeral === true ? true : void 0,
367
+ description: typeof raw.description === "string" ? raw.description : void 0,
368
+ priority: isPosInt(raw.priority) ? raw.priority : void 0,
369
+ type: typeof raw.type === "string" ? raw.type : void 0
370
+ };
371
+ }
372
+ /** Validate an already-parsed plan document — the plugin planSchema.
373
+ * Throws one error listing every problem. */
374
+ function parseDrillPlan(doc, source = "plan") {
375
+ const errors = [];
376
+ if (!isRecord(doc)) throw new Error(`${source}: expected a TOML table`);
377
+ for (const key of Object.keys(doc)) if (key !== "steps" && key !== "kind" && key !== "title") errors.push(`unknown top-level key "${key}"`);
378
+ if (doc.kind !== void 0 && doc.kind !== PLAN_KIND) errors.push(`kind: expected "${PLAN_KIND}", got ${JSON.stringify(doc.kind)}`);
379
+ if (!nonEmpty(doc.title)) errors.push("title: the root frame title is required");
380
+ const steps = [];
381
+ if (doc.steps !== void 0 && !Array.isArray(doc.steps)) errors.push("steps: must be an array of tables ([[steps]])");
382
+ else if (Array.isArray(doc.steps)) doc.steps.forEach((raw, i) => {
383
+ const s = parseStep(raw, i, errors);
384
+ if (!s) return;
385
+ if (s.under !== void 0 && s.under >= steps.length) errors.push(`steps[${i}]: under must index a previous step`);
386
+ steps.push(s);
387
+ });
388
+ if (errors.length > 0) throw new Error(`${source}:\n ${errors.join("\n ")}`);
389
+ return {
390
+ title: doc.title.trim(),
391
+ steps
392
+ };
393
+ }
394
+
395
+ //#endregion
396
+ //#region src/connector.ts
397
+ /** The open-frame line, or null — the same text every surface shares. */
398
+ function frameLine() {
399
+ try {
400
+ const frame = currentFrame();
401
+ if (!frame) return null;
402
+ return `drill frame open: ${frame.id} "${frame.title}" [depth=${frame.depth}] — close with \`bro drill up --result "…"\``;
403
+ } catch {
404
+ return null;
405
+ }
406
+ }
407
+ const drillConnector = {
408
+ name: "drill",
409
+ hooks: () => ({
410
+ sessionStart() {
411
+ const line = frameLine();
412
+ return line ? [line] : [];
413
+ },
414
+ promptSubmit() {
415
+ const line = frameLine();
416
+ return line ? [line] : [];
417
+ },
418
+ stopGate() {
419
+ const line = frameLine();
420
+ if (!line) return [];
421
+ return [{
422
+ aspect: "drill",
423
+ block: `bro: ${line}`,
424
+ passive: `bro: ${line} (opened outside this session — informational)`
425
+ }];
426
+ }
427
+ })
428
+ };
429
+
430
+ //#endregion
431
+ export { PLAN_KIND as DRILL_PLAN_KIND, bd, bdJson, checkBeads, childrenOf, currentFrame, drillConnector, drillDown, drillTree, drillUp, listDrills, parseDrillPlan, planPreventions, refKind, taskStore };
package/package.json CHANGED
@@ -1,16 +1,47 @@
1
1
  {
2
- "description": "Placeholder for @broject/drill published by @nx-devkit/prepare-for-release.",
3
- "license": "MIT",
4
2
  "name": "@broject/drill",
5
- "publishConfig": {
6
- "access": "public",
7
- "registry": "https://registry.npmjs.org/"
3
+ "version": "0.2.3",
4
+ "type": "module",
5
+ "dependencies": {
6
+ "@broject/core": "^0.2.0"
7
+ },
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ }
13
+ },
14
+ "scripts": {
15
+ "test": "tsx --test src/**/*.test.ts",
16
+ "typecheck": "tsc --noEmit"
17
+ },
18
+ "devDependencies": {
19
+ "tsdown": "^0.15.0",
20
+ "tsx": "^4.20.0",
21
+ "typescript": "^5.9.0",
22
+ "smol-toml": "1.7.1"
8
23
  },
24
+ "files": [
25
+ "dist"
26
+ ],
27
+ "engines": {
28
+ "node": ">=22"
29
+ },
30
+ "license": "MIT",
9
31
  "repository": {
10
32
  "type": "git",
11
33
  "url": "git+https://github.com/ThePlenkov/bro.git",
12
34
  "directory": "packages/drill"
13
35
  },
14
- "type": "module",
15
- "version": "0.0.0"
16
- }
36
+ "publishConfig": {
37
+ "access": "public"
38
+ },
39
+ "description": "Scoped descent frames for bro — drill down/up over beads with result + prevention memos",
40
+ "keywords": [
41
+ "beads",
42
+ "investigation",
43
+ "agent",
44
+ "bro"
45
+ ],
46
+ "homepage": "https://broject.dev/docs"
47
+ }