@zenera/cli 1.1.24 → 1.1.26

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 (199) hide show
  1. package/README.md +65 -0
  2. package/dist/ask.js +28 -5
  3. package/dist/banner.js +2 -2
  4. package/dist/batch.d.ts +6 -0
  5. package/dist/batch.js +76 -1
  6. package/dist/catalog.d.ts +31 -1
  7. package/dist/catalog.js +140 -7
  8. package/dist/commands/index.js +2 -0
  9. package/dist/commands/inspect.d.ts +52 -2
  10. package/dist/commands/inspect.js +220 -31
  11. package/dist/commands/memory.js +156 -32
  12. package/dist/commands/meta.js +333 -24
  13. package/dist/commands/models.js +97 -12
  14. package/dist/commands/open.js +4 -4
  15. package/dist/commands/run.js +38 -4
  16. package/dist/commands/sandbox.js +148 -16
  17. package/dist/commands/view.d.ts +8 -0
  18. package/dist/commands/view.js +114 -0
  19. package/dist/engine.d.ts +8 -1
  20. package/dist/engine.js +7 -0
  21. package/dist/eventlog.d.ts +64 -0
  22. package/dist/eventlog.js +170 -0
  23. package/dist/liveness.d.ts +7 -0
  24. package/dist/liveness.js +35 -3
  25. package/dist/main.js +0 -0
  26. package/dist/meta/editor.d.ts +9 -0
  27. package/dist/meta/editor.js +43 -0
  28. package/dist/meta/finetune/analyze-brief.d.ts +26 -0
  29. package/dist/meta/finetune/analyze-brief.js +131 -0
  30. package/dist/meta/finetune/apply-brief.d.ts +23 -0
  31. package/dist/meta/finetune/apply-brief.js +247 -0
  32. package/dist/meta/finetune/brief.d.ts +45 -0
  33. package/dist/meta/finetune/brief.js +158 -0
  34. package/dist/meta/finetune/children.d.ts +8 -0
  35. package/dist/meta/finetune/children.js +76 -0
  36. package/dist/meta/finetune/command.d.ts +30 -0
  37. package/dist/meta/finetune/command.js +516 -0
  38. package/dist/meta/finetune/cost.d.ts +46 -0
  39. package/dist/meta/finetune/cost.js +105 -0
  40. package/dist/meta/finetune/dataset/anchors.d.ts +41 -0
  41. package/dist/meta/finetune/dataset/anchors.js +239 -0
  42. package/dist/meta/finetune/dataset/changes.d.ts +36 -0
  43. package/dist/meta/finetune/dataset/changes.js +319 -0
  44. package/dist/meta/finetune/dataset/command.d.ts +17 -0
  45. package/dist/meta/finetune/dataset/command.js +1011 -0
  46. package/dist/meta/finetune/dataset/drift.d.ts +33 -0
  47. package/dist/meta/finetune/dataset/drift.js +71 -0
  48. package/dist/meta/finetune/dataset/export.d.ts +26 -0
  49. package/dist/meta/finetune/dataset/export.js +51 -0
  50. package/dist/meta/finetune/dataset/index.d.ts +2 -0
  51. package/dist/meta/finetune/dataset/index.js +2 -0
  52. package/dist/meta/finetune/dataset/sample.d.ts +50 -0
  53. package/dist/meta/finetune/dataset/sample.js +197 -0
  54. package/dist/meta/finetune/dataset/schema.d.ts +33 -0
  55. package/dist/meta/finetune/dataset/schema.js +216 -0
  56. package/dist/meta/finetune/dataset/store.d.ts +35 -0
  57. package/dist/meta/finetune/dataset/store.js +177 -0
  58. package/dist/meta/finetune/dataset/types.d.ts +127 -0
  59. package/dist/meta/finetune/dataset/types.js +39 -0
  60. package/dist/meta/finetune/failure.d.ts +15 -0
  61. package/dist/meta/finetune/failure.js +26 -0
  62. package/dist/meta/finetune/feedback.d.ts +57 -0
  63. package/dist/meta/finetune/feedback.js +104 -0
  64. package/dist/meta/finetune/guard.d.ts +29 -0
  65. package/dist/meta/finetune/guard.js +104 -0
  66. package/dist/meta/finetune/improvements.d.ts +98 -0
  67. package/dist/meta/finetune/improvements.js +501 -0
  68. package/dist/meta/finetune/index.d.ts +2 -0
  69. package/dist/meta/finetune/index.js +2 -0
  70. package/dist/meta/finetune/journal.d.ts +35 -0
  71. package/dist/meta/finetune/journal.js +135 -0
  72. package/dist/meta/finetune/live.d.ts +84 -0
  73. package/dist/meta/finetune/live.js +272 -0
  74. package/dist/meta/finetune/loop.d.ts +3 -0
  75. package/dist/meta/finetune/loop.js +163 -0
  76. package/dist/meta/finetune/memories.d.ts +47 -0
  77. package/dist/meta/finetune/memories.js +154 -0
  78. package/dist/meta/finetune/recheck.d.ts +58 -0
  79. package/dist/meta/finetune/recheck.js +311 -0
  80. package/dist/meta/finetune/report.d.ts +40 -0
  81. package/dist/meta/finetune/report.js +1397 -0
  82. package/dist/meta/finetune/retry.d.ts +24 -0
  83. package/dist/meta/finetune/retry.js +96 -0
  84. package/dist/meta/finetune/sampler.d.ts +18 -0
  85. package/dist/meta/finetune/sampler.js +47 -0
  86. package/dist/meta/finetune/system.d.ts +27 -0
  87. package/dist/meta/finetune/system.js +132 -0
  88. package/dist/meta/finetune/train.d.ts +43 -0
  89. package/dist/meta/finetune/train.js +425 -0
  90. package/dist/meta/finetune/tui.d.ts +16 -0
  91. package/dist/meta/finetune/tui.js +316 -0
  92. package/dist/meta/finetune/tuning.d.ts +193 -0
  93. package/dist/meta/finetune/tuning.js +308 -0
  94. package/dist/meta/finetune/usage.d.ts +66 -0
  95. package/dist/meta/finetune/usage.js +260 -0
  96. package/dist/meta/finetune/view.d.ts +9 -0
  97. package/dist/meta/finetune/view.js +27 -0
  98. package/dist/meta/host.d.ts +15 -0
  99. package/dist/meta/host.js +21 -0
  100. package/dist/meta/index.d.ts +11 -0
  101. package/dist/meta/index.js +11 -0
  102. package/dist/meta/launch.d.ts +81 -0
  103. package/dist/meta/launch.js +286 -0
  104. package/dist/meta/model.d.ts +44 -0
  105. package/dist/meta/model.js +119 -0
  106. package/dist/meta/prompts.d.ts +18 -0
  107. package/dist/meta/prompts.js +61 -0
  108. package/dist/meta/relay.d.ts +20 -0
  109. package/dist/meta/relay.js +123 -0
  110. package/dist/meta/tokens.d.ts +52 -0
  111. package/dist/meta/tokens.js +226 -0
  112. package/dist/meta/window.d.ts +61 -0
  113. package/dist/meta/window.js +198 -0
  114. package/dist/meta/wire.d.ts +42 -0
  115. package/dist/meta/wire.js +129 -0
  116. package/dist/meta.d.ts +71 -1
  117. package/dist/meta.js +303 -6
  118. package/dist/modelref.d.ts +16 -0
  119. package/dist/modelref.js +41 -0
  120. package/dist/narrate.js +5 -2
  121. package/dist/podman.d.ts +26 -0
  122. package/dist/podman.js +83 -3
  123. package/dist/projects.d.ts +0 -5
  124. package/dist/projects.js +2 -15
  125. package/dist/resources.d.ts +215 -0
  126. package/dist/resources.js +856 -0
  127. package/dist/sandbox.d.ts +2 -0
  128. package/dist/sandbox.js +10 -3
  129. package/dist/scaffold.d.ts +15 -7
  130. package/dist/scaffold.js +24 -67
  131. package/dist/session.js +4 -29
  132. package/dist/term.d.ts +2 -0
  133. package/dist/term.js +11 -2
  134. package/dist/trace.d.ts +5 -0
  135. package/dist/trace.js +107 -7
  136. package/dist/tui/app.js +19 -5
  137. package/dist/tui/links.d.ts +27 -0
  138. package/dist/tui/links.js +142 -0
  139. package/dist/tui/models.d.ts +35 -0
  140. package/dist/tui/models.js +447 -0
  141. package/dist/tui/sandbox.d.ts +10 -0
  142. package/dist/tui/sandbox.js +131 -0
  143. package/dist/tui/wrap.d.ts +5 -0
  144. package/dist/tui/wrap.js +12 -0
  145. package/dist/usage.d.ts +78 -0
  146. package/dist/usage.js +51 -0
  147. package/package.json +4 -2
  148. package/templates/editor/.github/copilot-instructions.md +60 -145
  149. package/templates/editor/.github/prompts/analyze.prompt.md +33 -0
  150. package/templates/editor/.github/prompts/dataset.prompt.md +108 -0
  151. package/templates/editor/.github/prompts/finetune-apply.prompt.md +270 -0
  152. package/templates/editor/.github/prompts/finetune.prompt.md +691 -198
  153. package/templates/editor/.github/prompts/new-agent.prompt.md +3 -1
  154. package/templates/editor/.github/prompts/new-skill.prompt.md +2 -0
  155. package/templates/editor/.github/prompts/project-review.prompt.md +5 -4
  156. package/templates/editor/.github/skills/zen-analyze-run/SKILL.md +588 -0
  157. package/templates/editor/.github/skills/zen-cli/SKILL.md +9 -1
  158. package/templates/editor/.github/skills/zen-cli/references/dataset.md +34 -0
  159. package/templates/editor/.github/skills/zen-cli/references/finetune.md +137 -0
  160. package/templates/editor/.github/skills/zen-cli/references/inspect.md +63 -11
  161. package/templates/editor/.github/skills/zen-cli/references/memory.md +33 -10
  162. package/templates/editor/.github/skills/zen-cli/references/meta.md +61 -0
  163. package/templates/editor/.github/skills/zen-cli/references/models.md +5 -1
  164. package/templates/editor/.github/skills/zen-cli/references/projects.md +4 -6
  165. package/templates/editor/.github/skills/zen-cli/references/rag.md +24 -6
  166. package/templates/editor/.github/skills/zen-cli/references/sandbox.md +35 -14
  167. package/templates/editor/.github/skills/zen-code-python/SKILL.md +123 -0
  168. package/templates/editor/.github/skills/zen-code-python/references/code_python.md +48 -0
  169. package/templates/editor/.github/skills/zen-dataset/SKILL.md +253 -0
  170. package/templates/editor/.github/skills/zen-finetune/SKILL.md +1492 -660
  171. package/templates/editor/.github/skills/zen-finetune/references/readme-template.md +268 -0
  172. package/templates/editor/.github/skills/zen-finetune/scripts/batch.mjs +390 -0
  173. package/templates/editor/.github/skills/zen-finetune/scripts/difficult.mjs +243 -0
  174. package/templates/editor/.github/skills/zen-finetune/scripts/memory.mjs +280 -0
  175. package/templates/editor/.github/skills/zen-finetune/scripts/next.mjs +398 -0
  176. package/templates/editor/.github/skills/zen-finetune/scripts/report.mjs +797 -0
  177. package/templates/editor/.github/skills/zen-finetune/scripts/select.mjs +354 -0
  178. package/templates/editor/.github/skills/zen-finetune/scripts/usage.mjs +722 -0
  179. package/templates/editor/.github/skills/zen-inspect/SKILL.md +74 -26
  180. package/templates/editor/.github/skills/zen-inspect-ask/SKILL.md +271 -0
  181. package/templates/editor/.github/skills/zen-instructions/SKILL.md +4 -12
  182. package/templates/editor/.github/skills/zen-memory/SKILL.md +82 -53
  183. package/templates/editor/.github/skills/zen-rag-docs/SKILL.md +20 -8
  184. package/templates/editor/.github/skills/zen-review/SKILL.md +8 -31
  185. package/templates/editor/.github/skills/zen-review/scripts/check-paths.sh +5 -5
  186. package/templates/editor/.github/skills/zen-review/scripts/review.sh +3 -12
  187. package/templates/editor/.github/skills/zen-sandbox/SKILL.md +9 -4
  188. package/templates/editor/.github/skills/zen-sandbox-capacity/SKILL.md +2 -2
  189. package/templates/editor/.github/skills/zen-spec-sync/SKILL.md +2 -4
  190. package/templates/editor/.github/skills/zen-topology/SKILL.md +283 -0
  191. package/templates/editor/.vscode/settings.json +1 -0
  192. package/templates/project/agents/files-instructions.md +29 -20
  193. package/templates/project/agents/fork-instructions.md +78 -28
  194. package/templates/project/agents/memory-instructions.md +222 -494
  195. package/templates/project/agents/tools-instructions.md +14 -4
  196. package/templates/project/gitignore +2 -0
  197. package/templates/editor/.github/skills/zen-finetune/scripts/collect.sh +0 -432
  198. package/templates/editor/.github/skills/zen-finetune/scripts/sample.sh +0 -199
  199. package/templates/editor/.github/skills/zen-memory/scripts/check-instructions.sh +0 -116
@@ -0,0 +1,105 @@
1
+ import { readdirSync, statSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { validateProject } from "../host.js";
4
+ export async function measure(root) {
5
+ return costOf(root, await validateProject({ dir: root }));
6
+ }
7
+ /** No `skills:` key is the whole catalog as an index, the same as the runtime. */
8
+ export const bindingOf = (a) => a.skills ?? { discovery: 'index' };
9
+ /** The skills a binding lets its agent load. */
10
+ export const visibleTo = (b, entries) => entries.filter((s) => !b.allow || b.allow.includes(s.name));
11
+ function size(root, rel) {
12
+ try {
13
+ return statSync(join(root, rel)).size;
14
+ }
15
+ catch {
16
+ return 0;
17
+ }
18
+ }
19
+ export function costOf(root, report) {
20
+ const entries = report.skills.entries;
21
+ const skills = Object.fromEntries(entries.map((s) => [s.name, size(root, s.path)]));
22
+ const alwaysRead = {};
23
+ const visible = {};
24
+ for (const a of report.agents) {
25
+ const binding = bindingOf(a);
26
+ const seen = visibleTo(binding, entries);
27
+ const index = binding.discovery === 'index'
28
+ ? seen.reduce((n, s) => n + Buffer.byteLength(`- ${s.name}: ${s.description}\n`), 0)
29
+ : 0;
30
+ const preloaded = (binding.preload ?? []).reduce((n, s) => n + (skills[s] ?? 0), 0);
31
+ alwaysRead[a.name] =
32
+ a.instructions.reduce((n, rel) => n + size(root, rel), 0) + preloaded + index;
33
+ visible[a.name] = seen.length;
34
+ }
35
+ const total = Object.values(alwaysRead).reduce((n, b) => n + b, 0);
36
+ return { alwaysRead, total, skills, visible, files: markdown(root) };
37
+ }
38
+ function markdown(root) {
39
+ const out = {};
40
+ let names = [];
41
+ try {
42
+ names = readdirSync(join(root, 'agents'), { recursive: true });
43
+ }
44
+ catch {
45
+ return out;
46
+ }
47
+ for (const name of names.sort()) {
48
+ if (name.endsWith('.md')) {
49
+ const rel = join('agents', name).split('\\').join('/');
50
+ out[rel] = size(root, rel);
51
+ }
52
+ }
53
+ return out;
54
+ }
55
+ export function limits(start, c) {
56
+ return {
57
+ budget: c.budget,
58
+ // A project that starts nearly empty still gets one apply's worth of room.
59
+ ceiling: Math.max(start.total + c.budget, Math.round(start.total * (1 + c.maxGrowth / 100))),
60
+ skillCap: c.skillCap,
61
+ maxSkills: c.maxSkills,
62
+ };
63
+ }
64
+ /** Every limit an edit broke, said so the next attempt can act on it; empty when none. */
65
+ export function overLimits(before, after, l) {
66
+ const out = [];
67
+ const grew = after.total - before.total;
68
+ if (grew > l.budget) {
69
+ out.push(`what the agents always read grew by ${grew} bytes (${before.total} -> ${after.total}), over the budget of ${l.budget}`);
70
+ }
71
+ if (grew > 0 && after.total > l.ceiling) {
72
+ out.push(`what the agents always read is ${after.total} bytes, over the ceiling of ${l.ceiling}: past it an apply may add nothing - cut, move or extract to make room`);
73
+ }
74
+ for (const [name, bytes] of Object.entries(after.skills)) {
75
+ if (bytes > l.skillCap && bytes > (before.skills[name] ?? 0)) {
76
+ out.push(`skill ${name} holds ${bytes} bytes, over the cap of ${l.skillCap}: split it by when each part is needed`);
77
+ }
78
+ }
79
+ for (const [agent, n] of Object.entries(after.visible)) {
80
+ if (n > l.maxSkills && n > (before.visible[agent] ?? 0)) {
81
+ out.push(`agent ${agent} sees ${n} skills, over ${l.maxSkills}: merge skills whose descriptions overlap`);
82
+ }
83
+ }
84
+ return out;
85
+ }
86
+ /** What moved between two measures, per agent, skill and file - for a person and the next attempt. */
87
+ export function costChanges(before, after) {
88
+ const rows = [];
89
+ const diff = (label, a, b) => {
90
+ for (const key of [...new Set([...Object.keys(a), ...Object.keys(b)])].sort()) {
91
+ const was = a[key] ?? 0;
92
+ const now = b[key] ?? 0;
93
+ if (was !== now) {
94
+ rows.push(`| ${label} | ${key} | ${was} | ${now} | ${now - was > 0 ? '+' : ''}${now - was} |`);
95
+ }
96
+ }
97
+ };
98
+ diff('always read', before.alwaysRead, after.alwaysRead);
99
+ diff('skill', before.skills, after.skills);
100
+ diff('file', before.files, after.files);
101
+ return rows.length > 0
102
+ ? ['| What | Name | Before | After | Change |', '| --- | --- | --- | --- | --- |', ...rows]
103
+ : [];
104
+ }
105
+ //# sourceMappingURL=cost.js.map
@@ -0,0 +1,41 @@
1
+ import { type Anchor } from './types.ts';
2
+ export type Found = {
3
+ found: true;
4
+ text: string;
5
+ sha256: string;
6
+ } | {
7
+ found: false;
8
+ why: 'no file' | 'not found' | 'ambiguous' | 'unreadable';
9
+ };
10
+ interface Heading {
11
+ level: number;
12
+ title: string;
13
+ path: string[];
14
+ /** first line, and one past the last line of its section */
15
+ start: number;
16
+ end: number;
17
+ }
18
+ export declare const sha256: (text: string) => string;
19
+ /** Every ATX heading outside a code fence, with the extent of its section. */
20
+ export declare function headingsOf(text: string): Heading[];
21
+ /** Sorted keys, so reformatting or reordering a document is not a change. */
22
+ export declare function canonical(value: unknown): string;
23
+ /** One read of each file per instance: a drift check asks about many cases per file. */
24
+ export declare class Sources {
25
+ #private;
26
+ readonly root: string;
27
+ constructor(root: string);
28
+ text(file: string): string | undefined;
29
+ /** Of the bytes as they are: any edit at all, even one no case sees. */
30
+ hash(file: string): string | undefined;
31
+ section(file: string, anchor: Anchor | undefined): Found;
32
+ /**
33
+ * Sections of a file no case and no ignore accounts for: headings at the
34
+ * levels the covered ones sit at, or the siblings of covered pointers. A
35
+ * section inside a covered one is part of that case, and a section around one
36
+ * is its context, so neither is reported.
37
+ */
38
+ uncovered(file: string, covered: readonly (Anchor | undefined)[]): Anchor[];
39
+ }
40
+ export {};
41
+ //# sourceMappingURL=anchors.d.ts.map
@@ -0,0 +1,239 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { readFileSync } from 'node:fs';
3
+ import { extname, resolve } from 'node:path';
4
+ import { parse as parseYaml } from 'yaml';
5
+ import {} from "./types.js";
6
+ export const sha256 = (text) => createHash('sha256').update(text).digest('hex');
7
+ const normal = (text) => text
8
+ .replace(/\r\n?/g, '\n')
9
+ .split('\n')
10
+ .map((line) => line.trimEnd())
11
+ .join('\n')
12
+ .replace(/\n+$/, '');
13
+ const title = (text) => text.replace(/\s+/g, ' ').trim();
14
+ /** Every ATX heading outside a code fence, with the extent of its section. */
15
+ export function headingsOf(text) {
16
+ const lines = text.replace(/\r\n?/g, '\n').split('\n');
17
+ const found = [];
18
+ const stack = [];
19
+ let fence;
20
+ lines.forEach((line, i) => {
21
+ const marker = /^ {0,3}(`{3,}|~{3,})/.exec(line);
22
+ if (marker) {
23
+ const char = marker[1][0];
24
+ if (!fence) {
25
+ fence = { char, size: marker[1].length };
26
+ }
27
+ else if (fence.char === char && marker[1].length >= fence.size) {
28
+ fence = undefined;
29
+ }
30
+ return;
31
+ }
32
+ if (fence) {
33
+ return;
34
+ }
35
+ const m = /^ {0,3}(#{1,6})(?:[ \t]+(.*?))?(?:[ \t]+#+)?[ \t]*$/.exec(line);
36
+ if (!m || !m[2]?.trim()) {
37
+ return;
38
+ }
39
+ const level = m[1].length;
40
+ while (stack.length > 0 && stack[stack.length - 1].level >= level) {
41
+ stack.pop();
42
+ }
43
+ const heading = {
44
+ level,
45
+ title: title(m[2]),
46
+ path: [...stack.map((h) => h.title), title(m[2])],
47
+ start: i,
48
+ end: lines.length,
49
+ };
50
+ stack.push(heading);
51
+ found.push(heading);
52
+ });
53
+ for (let i = 0; i < found.length; i++) {
54
+ const next = found.slice(i + 1).find((h) => h.level <= found[i].level);
55
+ found[i].end = next ? next.start : lines.length;
56
+ }
57
+ return found;
58
+ }
59
+ /**
60
+ * The last name must be the heading's own; the names before it must appear, in
61
+ * order, among its ancestors. So `["Query: organize my day"]` is enough while it
62
+ * is unique, and an author adds a parent only to tell two apart.
63
+ */
64
+ function matches(path, heading) {
65
+ const want = path.map(title);
66
+ if (want[want.length - 1] !== heading.title) {
67
+ return false;
68
+ }
69
+ let at = 0;
70
+ for (const name of heading.path.slice(0, -1)) {
71
+ if (at < want.length - 1 && name === want[at]) {
72
+ at++;
73
+ }
74
+ }
75
+ return at === want.length - 1;
76
+ }
77
+ /** RFC 6901, over a parsed JSON or YAML document. */
78
+ function follow(doc, pointer) {
79
+ if (pointer === '') {
80
+ return { ok: true, value: doc };
81
+ }
82
+ let at = doc;
83
+ for (const raw of pointer.slice(1).split('/')) {
84
+ const key = raw.replace(/~1/g, '/').replace(/~0/g, '~');
85
+ if (Array.isArray(at) && /^\d+$/.test(key) && Number(key) < at.length) {
86
+ at = at[Number(key)];
87
+ }
88
+ else if (at !== null && typeof at === 'object' && !Array.isArray(at) && key in at) {
89
+ at = at[key];
90
+ }
91
+ else {
92
+ return { ok: false };
93
+ }
94
+ }
95
+ return { ok: true, value: at };
96
+ }
97
+ const escapeKey = (key) => key.replace(/~/g, '~0').replace(/\//g, '~1');
98
+ /** Sorted keys, so reformatting or reordering a document is not a change. */
99
+ export function canonical(value) {
100
+ if (Array.isArray(value)) {
101
+ return `[${value.map(canonical).join(',')}]`;
102
+ }
103
+ if (value !== null && typeof value === 'object') {
104
+ const keys = Object.keys(value).sort();
105
+ return `{${keys
106
+ .map((k) => `${JSON.stringify(k)}:${canonical(value[k])}`)
107
+ .join(',')}}`;
108
+ }
109
+ return JSON.stringify(value) ?? 'null';
110
+ }
111
+ /** One read of each file per instance: a drift check asks about many cases per file. */
112
+ export class Sources {
113
+ root;
114
+ #text = new Map();
115
+ #headings = new Map();
116
+ #docs = new Map();
117
+ constructor(root) {
118
+ this.root = root;
119
+ }
120
+ text(file) {
121
+ if (!this.#text.has(file)) {
122
+ let text;
123
+ try {
124
+ text = readFileSync(resolve(this.root, file), 'utf8');
125
+ }
126
+ catch {
127
+ text = undefined;
128
+ }
129
+ this.#text.set(file, text);
130
+ }
131
+ return this.#text.get(file);
132
+ }
133
+ /** Of the bytes as they are: any edit at all, even one no case sees. */
134
+ hash(file) {
135
+ const text = this.text(file);
136
+ return text === undefined ? undefined : sha256(text);
137
+ }
138
+ #headingsOf(file, text) {
139
+ let found = this.#headings.get(file);
140
+ if (!found) {
141
+ found = headingsOf(text);
142
+ this.#headings.set(file, found);
143
+ }
144
+ return found;
145
+ }
146
+ #doc(file, text) {
147
+ let parsed = this.#docs.get(file);
148
+ if (!parsed) {
149
+ try {
150
+ const ext = extname(file).toLowerCase();
151
+ parsed = { ok: true, doc: ext === '.json' ? JSON.parse(text) : parseYaml(text) };
152
+ }
153
+ catch {
154
+ parsed = { ok: false };
155
+ }
156
+ this.#docs.set(file, parsed);
157
+ }
158
+ return parsed;
159
+ }
160
+ section(file, anchor) {
161
+ const text = this.text(file);
162
+ if (text === undefined) {
163
+ return { found: false, why: 'no file' };
164
+ }
165
+ if (anchor?.heading) {
166
+ const hits = this.#headingsOf(file, text).filter((h) => matches(anchor.heading, h));
167
+ if (hits.length !== 1) {
168
+ return { found: false, why: hits.length === 0 ? 'not found' : 'ambiguous' };
169
+ }
170
+ const lines = text.replace(/\r\n?/g, '\n').split('\n');
171
+ const body = normal(lines.slice(hits[0].start, hits[0].end).join('\n'));
172
+ return { found: true, text: body, sha256: sha256(body) };
173
+ }
174
+ if (anchor?.pointer !== undefined) {
175
+ const parsed = this.#doc(file, text);
176
+ if (!parsed.ok) {
177
+ return { found: false, why: 'unreadable' };
178
+ }
179
+ const hit = follow(parsed.doc, anchor.pointer);
180
+ if (!hit.ok) {
181
+ return { found: false, why: 'not found' };
182
+ }
183
+ const body = canonical(hit.value);
184
+ return { found: true, text: body, sha256: sha256(body) };
185
+ }
186
+ const body = normal(text);
187
+ return { found: true, text: body, sha256: sha256(body) };
188
+ }
189
+ /**
190
+ * Sections of a file no case and no ignore accounts for: headings at the
191
+ * levels the covered ones sit at, or the siblings of covered pointers. A
192
+ * section inside a covered one is part of that case, and a section around one
193
+ * is its context, so neither is reported.
194
+ */
195
+ uncovered(file, covered) {
196
+ const text = this.text(file);
197
+ if (text === undefined || covered.some((a) => !a?.heading && a?.pointer === undefined)) {
198
+ return [];
199
+ }
200
+ const out = [];
201
+ const paths = covered.flatMap((a) => (a?.heading ? [a.heading] : []));
202
+ if (paths.length > 0) {
203
+ const all = this.#headingsOf(file, text);
204
+ const hit = all.filter((h) => paths.some((p) => matches(p, h)));
205
+ const levels = new Set(hit.map((h) => h.level));
206
+ const near = (h) => hit.some((c) => c === h ||
207
+ (c.start <= h.start && h.start < c.end) ||
208
+ (h.start <= c.start && c.start < h.end));
209
+ for (const h of all) {
210
+ if (levels.has(h.level) && !near(h)) {
211
+ out.push({ heading: h.path });
212
+ }
213
+ }
214
+ }
215
+ const pointers = covered.flatMap((a) => (a?.pointer ? [a.pointer] : []));
216
+ if (pointers.length > 0) {
217
+ const parsed = this.#doc(file, text);
218
+ const taken = new Set(pointers);
219
+ const parents = new Set(pointers.map((p) => p.slice(0, p.lastIndexOf('/'))));
220
+ for (const parent of parents) {
221
+ const hit = parsed.ok ? follow(parsed.doc, parent) : { ok: false };
222
+ if (!hit.ok || hit.value === null || typeof hit.value !== 'object') {
223
+ continue;
224
+ }
225
+ const keys = Array.isArray(hit.value)
226
+ ? hit.value.map((_, i) => String(i))
227
+ : Object.keys(hit.value);
228
+ for (const key of keys) {
229
+ const pointer = `${parent}/${escapeKey(key)}`;
230
+ if (!taken.has(pointer)) {
231
+ out.push({ pointer });
232
+ }
233
+ }
234
+ }
235
+ }
236
+ return out;
237
+ }
238
+ }
239
+ //# sourceMappingURL=anchors.js.map
@@ -0,0 +1,36 @@
1
+ import type { Proposed, ProposedRubric } from './schema.ts';
2
+ import type { Commit, DatasetStore } from './store.ts';
3
+ import { type By, type Case, type Manifest, type RubricItem } from './types.ts';
4
+ export interface Plan extends Commit {
5
+ /** cases that were looked at and found the same */
6
+ unchanged: string[];
7
+ }
8
+ /**
9
+ * Rubric items keep their ids across edits, so a note saying `r3 failed` still
10
+ * means the same line. An explicit id wins; otherwise the same words are the
11
+ * same item; anything else is new.
12
+ */
13
+ export declare function assignRubric(before: readonly RubricItem[], proposed: readonly ProposedRubric[]): RubricItem[];
14
+ /**
15
+ * A proposal, applied. Whole by default: an active case the proposal does not
16
+ * name is retired. `partial` touches only the cases it names — what a refresh
17
+ * of a few drifted sections writes.
18
+ */
19
+ export declare function planApply(store: DatasetStore, proposed: readonly Proposed[], options: {
20
+ partial: boolean;
21
+ why: string;
22
+ by: By;
23
+ }): Plan;
24
+ /** One case, changed by hand. `override` keeps the change through later re-extractions. */
25
+ export declare function planEdit(store: DatasetStore, id: string, edit: (c: Case) => Case, options: {
26
+ why: string;
27
+ by: By;
28
+ override?: readonly string[];
29
+ }): Plan;
30
+ export declare function planStatus(store: DatasetStore, ids: readonly string[], status: Case['status'], options: {
31
+ why: string;
32
+ by: By;
33
+ }): Plan;
34
+ /** The ignore list changed: no case moves, so no revision; the manifest is rewritten. */
35
+ export declare function planIgnored(store: DatasetStore, ignored: Manifest['ignored']): Plan;
36
+ //# sourceMappingURL=changes.d.ts.map