@crouton-kit/crouter 0.3.21 → 0.3.23

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 (111) hide show
  1. package/dist/commands/canvas-history/read.d.ts +1 -0
  2. package/dist/commands/canvas-history/read.js +61 -0
  3. package/dist/commands/canvas-history/search.d.ts +1 -0
  4. package/dist/commands/canvas-history/search.js +270 -0
  5. package/dist/commands/canvas-history/show.d.ts +1 -0
  6. package/dist/commands/canvas-history/show.js +79 -0
  7. package/dist/commands/canvas-history.d.ts +2 -0
  8. package/dist/commands/canvas-history.js +33 -0
  9. package/dist/commands/canvas.js +3 -2
  10. package/dist/commands/memory/find.js +7 -40
  11. package/dist/commands/memory/lint.js +19 -34
  12. package/dist/commands/memory/list.js +8 -18
  13. package/dist/commands/memory/read.js +4 -37
  14. package/dist/commands/pkg/bridge.d.ts +1 -0
  15. package/dist/commands/pkg/bridge.js +137 -0
  16. package/dist/commands/pkg/plugin-inspect.js +7 -6
  17. package/dist/commands/pkg.js +2 -1
  18. package/dist/commands/skill/author.js +35 -44
  19. package/dist/commands/skill/shared.d.ts +1 -5
  20. package/dist/commands/skill/shared.js +9 -63
  21. package/dist/commands/skill.js +5 -6
  22. package/dist/commands/sys/doctor.js +6 -144
  23. package/dist/core/__tests__/broker-dormant-wake.test.js +71 -34
  24. package/dist/core/__tests__/canvas-inbox-watcher-hold.test.js +21 -5
  25. package/dist/core/__tests__/canvas-inbox-watcher.test.js +21 -5
  26. package/dist/core/__tests__/child-death-wake.test.js +133 -185
  27. package/dist/core/__tests__/daemon-liveness.test.js +28 -258
  28. package/dist/core/__tests__/flagship-lifecycle.test.js +156 -135
  29. package/dist/core/__tests__/frame-decoder-perf.test.js +51 -25
  30. package/dist/core/__tests__/{broker-attach-limits.test.js → full/broker-attach-limits.test.js} +62 -35
  31. package/dist/core/__tests__/{broker-attach-stream.test.js → full/broker-attach-stream.test.js} +54 -31
  32. package/dist/core/__tests__/full/broker-crash-teardown.test.js +143 -0
  33. package/dist/core/__tests__/full/broker-dialogs.test.js +153 -0
  34. package/dist/core/__tests__/full/broker-lifecycle.test.js +116 -0
  35. package/dist/core/__tests__/{cascade-close.test.js → full/cascade-close.test.js} +1 -1
  36. package/dist/core/__tests__/full/daemon-liveness-pane.full.test.js +292 -0
  37. package/dist/core/__tests__/{dead-pane-regression.test.js → full/dead-pane-regression.test.js} +2 -2
  38. package/dist/core/__tests__/{detach-focus.test.js → full/detach-focus.test.js} +5 -5
  39. package/dist/core/__tests__/{human-new-window-regression.test.js → full/human-new-window-regression.test.js} +2 -2
  40. package/dist/core/__tests__/{placement-focus.test.js → full/placement-focus.test.js} +5 -5
  41. package/dist/core/__tests__/{placement-reconcile.test.js → full/placement-reconcile.test.js} +3 -3
  42. package/dist/core/__tests__/{placement-revive.test.js → full/placement-revive.test.js} +5 -5
  43. package/dist/core/__tests__/{placement-teardown.test.js → full/placement-teardown.test.js} +4 -4
  44. package/dist/core/__tests__/{review-render-pane-regression.test.js → full/review-render-pane-regression.test.js} +2 -2
  45. package/dist/core/__tests__/full/spike-harness.test.js +117 -0
  46. package/dist/core/__tests__/grace-clock.test.js +72 -75
  47. package/dist/core/__tests__/helpers/harness.d.ts +35 -1
  48. package/dist/core/__tests__/helpers/harness.js +70 -12
  49. package/dist/core/__tests__/live-mutation-verbs.test.js +99 -104
  50. package/dist/core/__tests__/live-mutation.test.js +111 -134
  51. package/dist/core/__tests__/revive.test.js +96 -86
  52. package/dist/core/__tests__/subscription-delivery.test.js +116 -138
  53. package/dist/core/__tests__/wake-origin.test.js +54 -50
  54. package/dist/core/bridge-map.d.ts +19 -0
  55. package/dist/core/bridge-map.js +73 -0
  56. package/dist/core/canvas/history.d.ts +91 -0
  57. package/dist/core/canvas/history.js +389 -0
  58. package/dist/core/canvas/index.d.ts +1 -0
  59. package/dist/core/canvas/index.js +1 -0
  60. package/dist/core/config.js +3 -22
  61. package/dist/core/memory-resolver.d.ts +9 -2
  62. package/dist/core/memory-resolver.js +47 -7
  63. package/dist/core/resolver.d.ts +1 -30
  64. package/dist/core/resolver.js +6 -546
  65. package/dist/core/runtime/persona.js +5 -7
  66. package/dist/core/scope.d.ts +7 -5
  67. package/dist/core/scope.js +16 -17
  68. package/dist/core/substrate/ceiling.d.ts +28 -0
  69. package/dist/core/substrate/ceiling.js +87 -0
  70. package/dist/core/substrate/index.d.ts +2 -0
  71. package/dist/core/substrate/index.js +1 -0
  72. package/dist/core/substrate/on-read.js +18 -2
  73. package/dist/core/substrate/render.d.ts +2 -3
  74. package/dist/core/substrate/render.js +63 -51
  75. package/dist/pi-extensions/canvas-inbox-watcher.js +19 -2
  76. package/dist/types.d.ts +3 -18
  77. package/dist/types.js +0 -8
  78. package/package.json +4 -3
  79. package/dist/builtin-skills/.crouter-plugin/plugin.json +0 -5
  80. package/dist/builtin-skills/skills/crouter-development/marketplaces/SKILL.md +0 -157
  81. package/dist/builtin-skills/skills/crouter-development/personas/SKILL.md +0 -106
  82. package/dist/builtin-skills/skills/crouter-development/personas/base-prompt/SKILL.md +0 -49
  83. package/dist/builtin-skills/skills/crouter-development/personas/orchestrator-prompt/SKILL.md +0 -49
  84. package/dist/builtin-skills/skills/crouter-development/plugins/SKILL.md +0 -156
  85. package/dist/builtin-skills/skills/design/SKILL.md +0 -51
  86. package/dist/builtin-skills/skills/development/SKILL.md +0 -109
  87. package/dist/builtin-skills/skills/planning/SKILL.md +0 -59
  88. package/dist/builtin-skills/skills/spec/SKILL.md +0 -83
  89. package/dist/commands/skill/state.d.ts +0 -3
  90. package/dist/commands/skill/state.js +0 -71
  91. package/dist/core/__tests__/broker-crash-teardown.test.js +0 -116
  92. package/dist/core/__tests__/broker-dialogs.test.js +0 -126
  93. package/dist/core/__tests__/broker-lifecycle.test.js +0 -87
  94. package/dist/core/__tests__/resolver.test.js +0 -181
  95. package/dist/core/__tests__/spike-harness.test.js +0 -242
  96. /package/dist/core/__tests__/{broker-attach-limits.test.d.ts → full/broker-attach-limits.test.d.ts} +0 -0
  97. /package/dist/core/__tests__/{broker-attach-stream.test.d.ts → full/broker-attach-stream.test.d.ts} +0 -0
  98. /package/dist/core/__tests__/{broker-crash-teardown.test.d.ts → full/broker-crash-teardown.test.d.ts} +0 -0
  99. /package/dist/core/__tests__/{broker-dialogs.test.d.ts → full/broker-dialogs.test.d.ts} +0 -0
  100. /package/dist/core/__tests__/{broker-lifecycle.test.d.ts → full/broker-lifecycle.test.d.ts} +0 -0
  101. /package/dist/core/__tests__/{cascade-close.test.d.ts → full/cascade-close.test.d.ts} +0 -0
  102. /package/dist/core/__tests__/{dead-pane-regression.test.d.ts → full/daemon-liveness-pane.full.test.d.ts} +0 -0
  103. /package/dist/core/__tests__/{detach-focus.test.d.ts → full/dead-pane-regression.test.d.ts} +0 -0
  104. /package/dist/core/__tests__/{human-new-window-regression.test.d.ts → full/detach-focus.test.d.ts} +0 -0
  105. /package/dist/core/__tests__/{placement-focus.test.d.ts → full/human-new-window-regression.test.d.ts} +0 -0
  106. /package/dist/core/__tests__/{placement-reconcile.test.d.ts → full/placement-focus.test.d.ts} +0 -0
  107. /package/dist/core/__tests__/{placement-revive.test.d.ts → full/placement-reconcile.test.d.ts} +0 -0
  108. /package/dist/core/__tests__/{placement-teardown.test.d.ts → full/placement-revive.test.d.ts} +0 -0
  109. /package/dist/core/__tests__/{resolver.test.d.ts → full/placement-teardown.test.d.ts} +0 -0
  110. /package/dist/core/__tests__/{review-render-pane-regression.test.d.ts → full/review-render-pane-regression.test.d.ts} +0 -0
  111. /package/dist/core/__tests__/{spike-harness.test.d.ts → full/spike-harness.test.d.ts} +0 -0
@@ -1,27 +1,40 @@
1
1
  // Run with: node --import tsx/esm --test src/core/__tests__/wake-origin.test.ts
2
2
  //
3
- // Wake-origin self-knowledge at the DAEMON FIRING seams (the other half of the
4
- // regression wake-bearings.test.ts covers the pure block + the bare-revive
5
- // kickoff). The observed gap: a scheduled wake arrived indistinguishable from an
6
- // ordinary message/spawn (Invariant D). These lock the daemon's third pass:
7
- // noted — the delivered inbox label carries the ⏰ scheduled-wake marker, so
8
- // a timed note is distinguishable from a plain `node msg` (no tmux needed).
9
- // spawn — a node BORN by a spawn wake gets the <crtr-wake> block prepended
10
- // to its kickoff prompt (naming the armer + cadence); an ordinary `node new`
11
- // spawn does NOT. The headline. (tmux-gated integration via the harness.)
3
+ // HEADLESS RETARGET (foundation-spec §C.16 + §E). Wake-origin self-knowledge at
4
+ // the DAEMON FIRING seams (the other half of the regression — wake-bearings.test.ts
5
+ // covers the pure block in isolation). The observed gap: a scheduled wake arrived
6
+ // indistinguishable from an ordinary message/spawn (Invariant D). Both seams are
7
+ // now driven model-only ZERO tmux, ZERO boot:
8
+ //
9
+ // (1) BUG LOCKED — a node woken or BORN by a TIMER must, by construction, learn a
10
+ // CLOCK (not an event) caused it: a noted wake rides a ⏰-marked inbox label
11
+ // (distinct from a plain `node msg`), and a spawn wake prepends the
12
+ // <crtr-wake> birth block (armer + cadence) to the newborn's kickoff while an
13
+ // ordinary `node new` carries none.
14
+ //
15
+ // (2) WHY MODEL-LEVEL, NOT TMUX CHROME — the noted seam is pure data layer
16
+ // (superviseTick's third pass → appendInbox; readInboxSince reads it back).
17
+ // The spawn-birth seam (spawn.ts) assembles the kickoff as
18
+ // `${buildWakeBearings(wakeOrigin)}\n\n${idBlock}${prompt}` ONLY when a
19
+ // wakeOrigin is present; `node new` passes none → no block. The load-bearing
20
+ // fact is the PURE rendering of buildWakeBearings for a spawn origin (and its
21
+ // absence without one) — reading it off a real booted pi's prompt was pure
22
+ // tmux/SDK overhead for a string a pure call already returns.
23
+ //
24
+ // (3) HOW THE DRIVE STILL FAILS IF THE BUG REGRESSES — drop the ⏰ label marker
25
+ // and the noted assert goes RED; drop the spawn-kind <crtr-wake> rendering
26
+ // and the spawn assert goes RED (the block / armer / cadence vanish).
12
27
  import { test, before, after, beforeEach } from 'node:test';
13
28
  import assert from 'node:assert/strict';
14
- import { mkdtempSync, rmSync, readdirSync } from 'node:fs';
29
+ import { mkdtempSync, rmSync } from 'node:fs';
15
30
  import { tmpdir } from 'node:os';
16
- import { join, dirname } from 'node:path';
17
- import { fileURLToPath } from 'node:url';
31
+ import { join } from 'node:path';
18
32
  import { createNode } from '../canvas/canvas.js';
19
33
  import { armWake } from '../canvas/wakeups.js';
20
34
  import { closeDb } from '../canvas/db.js';
21
35
  import { readInboxSince } from '../feed/inbox.js';
22
36
  import { superviseTick } from '../../daemon/crtrd.js';
23
- import { hasTmux, createHarness } from './helpers/harness.js';
24
- const CROUTER = join(dirname(fileURLToPath(import.meta.url)), '..', '..', '..');
37
+ import { buildWakeBearings } from '../runtime/bearings.js';
25
38
  function node(id) {
26
39
  return {
27
40
  node_id: id,
@@ -35,7 +48,6 @@ function node(id) {
35
48
  parent: null,
36
49
  };
37
50
  }
38
- // --- noted seam (no tmux) ---------------------------------------------------
39
51
  let home;
40
52
  before(() => {
41
53
  home = mkdtempSync(join(tmpdir(), 'crtr-wake-origin-'));
@@ -50,6 +62,7 @@ after(() => {
50
62
  rmSync(home, { recursive: true, force: true });
51
63
  delete process.env['CRTR_HOME'];
52
64
  });
65
+ // --- noted seam (the daemon's third pass → inbox) ---------------------------
53
66
  test('a noted scheduled wake delivers a ⏰-marked inbox label, distinct from a plain message', async () => {
54
67
  const armer = createNode(node('armer'));
55
68
  const target = createNode(node('tgt')); // no tmux placement → pass 1 skips it
@@ -72,39 +85,30 @@ test('a noted scheduled wake delivers a ⏰-marked inbox label, distinct from a
72
85
  assert.equal(entries[0].label, '⏰ scheduled wake — check CI');
73
86
  assert.notEqual(entries[0].label, 'check CI', 'a plain message would carry the bare label');
74
87
  });
75
- // --- spawn seam (the headline; tmux-gated integration) ----------------------
76
- test('a spawn scheduled wake births a node whose kickoff carries <crtr-wake>; node new does NOT', { skip: hasTmux() ? false : 'tmux unavailable' }, async () => {
77
- const h = await createHarness();
78
- try {
79
- const armer = h.spawnRoot('armer');
80
- // Contrast: an ordinary `node new` spawn its kickoff has NO wake block.
81
- const plainChild = await h.spawnChild(armer, 'plain task');
82
- const plainBoot = await h.awaitBoot(plainChild);
83
- assert.doesNotMatch(plainBoot.prompt ?? '', /<crtr-wake>/, 'node new carries no wake block');
84
- // Arm a recurring spawn-cron (detached), fire one tick, inspect the BORN
85
- // node's kickoff prompt (the message it actually wakes on).
86
- const before = new Set(readdirSync(join(h.home, 'nodes')));
87
- armWake({
88
- wakeup_id: 'wk-spawn-1',
89
- node_id: null, // canvas-detached deferred birth
90
- owner_id: armer,
91
- fire_at: new Date(Date.now() - 1000).toISOString(),
92
- kind: 'spawn',
93
- recur: JSON.stringify({ every: '6h' }),
94
- payload: { kind: 'general', cwd: CROUTER, prompt: 'do the recurring job', parent: armer },
95
- });
96
- await h.tick(Date.now());
97
- const born = readdirSync(join(h.home, 'nodes')).filter((d) => !before.has(d));
98
- assert.equal(born.length, 1, 'exactly one node born from the spawn wake');
99
- const bornBoot = await h.awaitBoot(born[0]);
100
- const prompt = bornBoot.prompt ?? '';
101
- assert.match(prompt, /<crtr-wake>/, 'the born node learns a timer birthed it');
102
- assert.match(prompt, /recurring spawn-cron armed by node /, 'names the armer role-explicitly');
103
- assert.match(prompt, /firing every 6h/, 'surfaces the cadence');
104
- assert.match(prompt, new RegExp(armer), 'the still-alive armer is named');
105
- assert.match(prompt, /do the recurring job/, 'the real task follows the block');
106
- }
107
- finally {
108
- await h.dispose();
109
- }
88
+ // --- spawn-birth seam (the headline; spawn.ts kickoff assembly) -------------
89
+ test('a spawn scheduled wake renders a <crtr-wake> birth block (armer + cadence); a plain node new renders none', async () => {
90
+ const armer = createNode(node('armer'));
91
+ // The spawn-birth seam (spawn.ts): the newborn's pi kickoff is
92
+ // `${buildWakeBearings(wakeOrigin)}\n\n${idBlock}${prompt}`
93
+ // when a wakeOrigin is present, and just `${idBlock}${prompt}` (no block)
94
+ // when it is not. The daemon's third pass builds the WakeOrigin from the fired
95
+ // recurring `spawn` row via wakeOriginFrom; here we assert the block it yields.
96
+ const origin = {
97
+ kind: 'spawn',
98
+ ownerId: armer.node_id,
99
+ ownerName: 'armer',
100
+ armedAt: new Date(Date.now() - 1000).toISOString(),
101
+ recur: JSON.stringify({ every: '6h' }),
102
+ };
103
+ const block = buildWakeBearings(origin);
104
+ assert.match(block, /<crtr-wake>/, 'the born node learns a timer birthed it');
105
+ assert.match(block, /recurring spawn-cron armed by node /, 'names the armer role-explicitly');
106
+ assert.match(block, /firing every 6h/, 'surfaces the cadence');
107
+ assert.match(block, new RegExp(armer.node_id), 'the still-alive armer is named');
108
+ // CONTRAST: an ordinary `node new` passes NO wakeOrigin, so the conditional
109
+ // spawn.ts uses (`opts.wakeOrigin !== undefined ? buildWakeBearings(...) : ''`)
110
+ // prepends nothing no <crtr-wake> in the newborn's kickoff.
111
+ const wakeBlock = (o) => (o !== undefined ? `${buildWakeBearings(o)}\n\n` : '');
112
+ assert.equal(wakeBlock(undefined), '', 'node new (no wakeOrigin) prepends no wake block');
113
+ assert.match(wakeBlock(origin), /<crtr-wake>/, 'a spawn wake prepends the block');
110
114
  });
@@ -0,0 +1,19 @@
1
+ export interface BridgeSkillInput {
2
+ /** Source skill `name` frontmatter (identity only; not emitted as a field). */
3
+ name: string;
4
+ /** Source skill `description` frontmatter — the text we reshape + carry. */
5
+ description: string;
6
+ /** The skill body, copied verbatim below the generated frontmatter. */
7
+ body: string;
8
+ }
9
+ /** Reshape a Claude `description` into a read-routing sentence per
10
+ * taste/why-field-means-why-to-read: "When <situation>, this skill should be
11
+ * read [because <gist>]." Claude descriptions are typically
12
+ * "<gist>. Use when <situation>." — we lift the use-when clause into the
13
+ * situation and the preceding text into the payoff. With no explicit clause,
14
+ * the whole description becomes the situation. */
15
+ export declare function reshapeWhenAndWhy(description: string): string;
16
+ /** Map a parsed Claude SKILL.md to a crouter substrate-doc string: full
17
+ * frontmatter (kind/when-and-why/short-form/visibility/generator) + the copied
18
+ * body. Deterministic and pure. */
19
+ export declare function buildBridgeDoc(input: BridgeSkillInput): string;
@@ -0,0 +1,73 @@
1
+ // Claude SKILL.md → crouter substrate-doc mapping (design §2.3).
2
+ // Pure, unit-testable. The bridge command (`crtr pkg bridge sync`) runs this
3
+ // unattended every session, so the description→read-routing reshape is a
4
+ // deterministic, conservative string rewrite — NOT a content paraphrase. An
5
+ // author who wants durable tuning uses an INDEX (which the bridge never
6
+ // touches), because a generator-stamped leaf is overwritten on the next sync.
7
+ // "Use when …" / "Use this when …" / "This skill should be used when …" / "Used when …"
8
+ const USE_WHEN_RE = /\b(?:use\s+this\s+skill\s+when|use\s+this\s+when|this\s+skill\s+should\s+be\s+used\s+when|used\s+when|use\s+when)\b\s*/i;
9
+ /** Collapse whitespace, strip surrounding connective punctuation, and lowercase
10
+ * a leading Capitalized word (but never an ACRONYM) so the clause reads inline
11
+ * after "When " / "because ". */
12
+ function cleanClause(s) {
13
+ let c = s.replace(/\s+/g, ' ').trim();
14
+ c = c.replace(/^[,;:.\s]+/, '').replace(/[,;:.\s]+$/, '');
15
+ if (/^[A-Z][a-z]/.test(c))
16
+ c = c.charAt(0).toLowerCase() + c.slice(1);
17
+ return c;
18
+ }
19
+ /** Reshape a Claude `description` into a read-routing sentence per
20
+ * taste/why-field-means-why-to-read: "When <situation>, this skill should be
21
+ * read [because <gist>]." Claude descriptions are typically
22
+ * "<gist>. Use when <situation>." — we lift the use-when clause into the
23
+ * situation and the preceding text into the payoff. With no explicit clause,
24
+ * the whole description becomes the situation. */
25
+ export function reshapeWhenAndWhy(description) {
26
+ const desc = description.replace(/\s+/g, ' ').trim();
27
+ if (desc === '')
28
+ return 'When this skill applies, this skill should be read.';
29
+ const m = desc.match(USE_WHEN_RE);
30
+ if (m && m.index !== undefined) {
31
+ const gist = cleanClause(desc.slice(0, m.index));
32
+ const situation = cleanClause(desc.slice(m.index + m[0].length));
33
+ if (situation !== '') {
34
+ let out = `When ${situation}, this skill should be read`;
35
+ if (gist !== '')
36
+ out += ` because ${gist}`;
37
+ return out + '.';
38
+ }
39
+ }
40
+ return `When ${cleanClause(desc)}, this skill should be read.`;
41
+ }
42
+ /** One-line short-form for the human inventory (never loaded into agent
43
+ * context): the source description collapsed to a single line. */
44
+ function toShortForm(description) {
45
+ return description.replace(/\s+/g, ' ').trim();
46
+ }
47
+ /** Always-double-quote a YAML scalar, escaping backslashes and quotes. The two
48
+ * generated fields are single-line, so this is safe regardless of colons,
49
+ * commas, or other YAML-significant characters. */
50
+ function yamlQuote(s) {
51
+ return `"${s.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
52
+ }
53
+ /** Map a parsed Claude SKILL.md to a crouter substrate-doc string: full
54
+ * frontmatter (kind/when-and-why/short-form/visibility/generator) + the copied
55
+ * body. Deterministic and pure. */
56
+ export function buildBridgeDoc(input) {
57
+ const whenAndWhy = reshapeWhenAndWhy(input.description);
58
+ const shortForm = toShortForm(input.description);
59
+ const fm = [
60
+ '---',
61
+ 'kind: skill',
62
+ `when-and-why-to-read: ${yamlQuote(whenAndWhy)}`,
63
+ `short-form: ${yamlQuote(shortForm)}`,
64
+ 'system-prompt-visibility: name',
65
+ 'file-read-visibility: none',
66
+ 'generator: claude-bridge',
67
+ '---',
68
+ '',
69
+ ].join('\n');
70
+ const body = input.body.replace(/^\n+/, '');
71
+ const trailing = body.endsWith('\n') ? '' : '\n';
72
+ return fm + '\n' + body + trailing;
73
+ }
@@ -0,0 +1,91 @@
1
+ import type { NodeStatus } from './types.js';
2
+ export type HistorySource = 'report' | 'doc' | 'roadmap' | 'meta';
3
+ /** One searchable unit of the episodic record: a report, a context doc, a
4
+ * node's roadmap, or a node's meta (name/description/kind). */
5
+ export interface HistoryArtifact {
6
+ /** Stable handle passed verbatim to `history read` — `<node-id>:<relpath>`. */
7
+ ref: string;
8
+ nodeId: string;
9
+ /** Path under the node dir (`reports/x.md`, `context/design.md`) or `meta`. */
10
+ relpath: string;
11
+ source: HistorySource;
12
+ /** final | update | urgent — reports only. */
13
+ reportKind?: string;
14
+ /** ISO 8601 artifact timestamp. */
15
+ ts: string;
16
+ /** Sort key: epoch ms of ts. */
17
+ tsMs: number;
18
+ /** Report title / doc first heading / node name. */
19
+ title: string;
20
+ nodeName: string;
21
+ nodeKind: string;
22
+ nodeStatus: NodeStatus;
23
+ nodeCwd: string;
24
+ nodeDesc: string;
25
+ /** Absolute file path; null for the synthetic `meta` artifact. */
26
+ path: string | null;
27
+ /** Frontmatter-stripped body, loaded once and cached. Empty on read error. */
28
+ loadBody: () => string;
29
+ }
30
+ export interface ScopeFilter {
31
+ /** A specific cwd. Default (all flags absent) = the caller's cwd. */
32
+ cwd?: string;
33
+ /** Every cwd on the canvas. */
34
+ allCwds?: boolean;
35
+ /** A node and its subscription descendants (one initiative / sub-DAG). */
36
+ under?: string;
37
+ /** Explicit node ids. */
38
+ nodes?: string[];
39
+ }
40
+ export interface CorpusFilter {
41
+ types?: HistorySource[];
42
+ /** Narrow reports to one report-kind (final | update). Implies type report. */
43
+ reportKind?: string;
44
+ /** Node kinds (developer, spec, …). */
45
+ kinds?: string[];
46
+ /** Node lifecycle statuses. */
47
+ statuses?: NodeStatus[];
48
+ /** Inclusive lower / upper bound on artifact ts, epoch ms. */
49
+ sinceMs?: number;
50
+ untilMs?: number;
51
+ }
52
+ /** The cwd the caller means by default: the calling node's cwd (resolved from
53
+ * CRTR_NODE_ID via the canvas data layer), falling back to the process cwd
54
+ * when not run as a node. */
55
+ export declare function callerCwd(): string;
56
+ /** Build the searchable artifact set for a scope + corpus filter. Node facets
57
+ * are applied against the db rows first (cheap); only surviving nodes' files
58
+ * are read. */
59
+ export declare function buildCorpus(scope: ScopeFilter, corpus: CorpusFilter): HistoryArtifact[];
60
+ export interface ResolvedRef {
61
+ nodeId: string;
62
+ relpath: string;
63
+ source: HistorySource;
64
+ reportKind?: string;
65
+ ts: string;
66
+ nodeName: string;
67
+ /** Frontmatter-stripped body. */
68
+ body: string;
69
+ /** Raw file content (frontmatter included); null for meta. */
70
+ raw: string | null;
71
+ path: string | null;
72
+ }
73
+ /** Resolve a `<node-id>:<relpath>` handle to its content. Returns null when the
74
+ * node is unknown or the artifact does not exist. Throws on path traversal. */
75
+ export declare function resolveRef(ref: string): ResolvedRef | null;
76
+ /** Enumerate one node's artifacts (for `history show`). */
77
+ export declare function nodeArtifacts(nodeId: string, types?: HistorySource[]): HistoryArtifact[];
78
+ export interface CorpusStats {
79
+ cwd: string;
80
+ nodes: number;
81
+ reports: number;
82
+ docs: number;
83
+ /** ISO date min → max across all artifacts, or null when empty. */
84
+ span: {
85
+ from: string;
86
+ to: string;
87
+ } | null;
88
+ }
89
+ /** Bounded per-cwd aggregate for the branch `-h` `<corpus>` block. Counts files
90
+ * on disk without reading bodies. */
91
+ export declare function corpusStats(cwd: string): CorpusStats;