@jinn-network/jinn-layer 0.1.0

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 (131) hide show
  1. package/dist/adapters/contribution-adapter.d.ts +18 -0
  2. package/dist/adapters/contribution-adapter.js +134 -0
  3. package/dist/adapters/corpus-adapter.d.ts +12 -0
  4. package/dist/adapters/corpus-adapter.js +212 -0
  5. package/dist/adapters/index.d.ts +17 -0
  6. package/dist/adapters/index.js +17 -0
  7. package/dist/adapters/json-map-store.d.ts +4 -0
  8. package/dist/adapters/json-map-store.js +31 -0
  9. package/dist/adapters/local-learning-adapter.d.ts +25 -0
  10. package/dist/adapters/local-learning-adapter.js +49 -0
  11. package/dist/adapters/skills-adapter.d.ts +8 -0
  12. package/dist/adapters/skills-adapter.js +55 -0
  13. package/dist/admission-policy.d.ts +38 -0
  14. package/dist/admission-policy.js +17 -0
  15. package/dist/bin/jinn-distill-mcp.d.ts +2 -0
  16. package/dist/bin/jinn-distill-mcp.js +10 -0
  17. package/dist/bin/jinn-layer.d.ts +7 -0
  18. package/dist/bin/jinn-layer.js +15 -0
  19. package/dist/bridge-fetch-evidence.d.ts +102 -0
  20. package/dist/bridge-fetch-evidence.js +677 -0
  21. package/dist/bridge-verdict-source.d.ts +47 -0
  22. package/dist/bridge-verdict-source.js +216 -0
  23. package/dist/bridge.d.ts +247 -0
  24. package/dist/bridge.js +439 -0
  25. package/dist/capture.d.ts +98 -0
  26. package/dist/capture.js +617 -0
  27. package/dist/cli.d.ts +148 -0
  28. package/dist/cli.js +2221 -0
  29. package/dist/cluster-selection.d.ts +34 -0
  30. package/dist/cluster-selection.js +135 -0
  31. package/dist/cluster.d.ts +89 -0
  32. package/dist/cluster.js +142 -0
  33. package/dist/consume.d.ts +139 -0
  34. package/dist/consume.js +280 -0
  35. package/dist/corpus-probes.d.ts +61 -0
  36. package/dist/corpus-probes.js +120 -0
  37. package/dist/corpus-store.d.ts +16 -0
  38. package/dist/corpus-store.js +101 -0
  39. package/dist/distill-captures.d.ts +56 -0
  40. package/dist/distill-captures.js +283 -0
  41. package/dist/distill-feedback.d.ts +20 -0
  42. package/dist/distill-feedback.js +30 -0
  43. package/dist/distill-llm.d.ts +138 -0
  44. package/dist/distill-llm.js +438 -0
  45. package/dist/distill-mcp-server.d.ts +30 -0
  46. package/dist/distill-mcp-server.js +281 -0
  47. package/dist/distill-mode.d.ts +62 -0
  48. package/dist/distill-mode.js +107 -0
  49. package/dist/distill-progress.d.ts +78 -0
  50. package/dist/distill-progress.js +69 -0
  51. package/dist/distill-prompt.d.ts +34 -0
  52. package/dist/distill-prompt.js +85 -0
  53. package/dist/distill-render.d.ts +123 -0
  54. package/dist/distill-render.js +303 -0
  55. package/dist/distill-runs.d.ts +28 -0
  56. package/dist/distill-runs.js +39 -0
  57. package/dist/distill-traces.d.ts +67 -0
  58. package/dist/distill-traces.js +211 -0
  59. package/dist/distill.d.ts +180 -0
  60. package/dist/distill.js +337 -0
  61. package/dist/distiller.d.ts +118 -0
  62. package/dist/distiller.js +142 -0
  63. package/dist/envelope.d.ts +9 -0
  64. package/dist/envelope.js +9 -0
  65. package/dist/eval-prep.d.ts +129 -0
  66. package/dist/eval-prep.js +523 -0
  67. package/dist/execution-publish.d.ts +62 -0
  68. package/dist/execution-publish.js +72 -0
  69. package/dist/gate.d.ts +39 -0
  70. package/dist/gate.js +106 -0
  71. package/dist/index.d.ts +49 -0
  72. package/dist/index.js +50 -0
  73. package/dist/ipfs-cid.d.ts +18 -0
  74. package/dist/ipfs-cid.js +149 -0
  75. package/dist/layer-default.d.ts +3 -0
  76. package/dist/layer-default.js +10 -0
  77. package/dist/ledger.d.ts +62 -0
  78. package/dist/ledger.js +104 -0
  79. package/dist/measurement.d.ts +90 -0
  80. package/dist/measurement.js +134 -0
  81. package/dist/path-safety.d.ts +19 -0
  82. package/dist/path-safety.js +303 -0
  83. package/dist/pipeline.d.ts +94 -0
  84. package/dist/pipeline.js +124 -0
  85. package/dist/plugin-wiring.d.ts +4 -0
  86. package/dist/plugin-wiring.js +54 -0
  87. package/dist/preview.d.ts +31 -0
  88. package/dist/preview.js +28 -0
  89. package/dist/process-contract.d.ts +303 -0
  90. package/dist/process-contract.js +118 -0
  91. package/dist/publish-skill.d.ts +86 -0
  92. package/dist/publish-skill.js +116 -0
  93. package/dist/publish.d.ts +294 -0
  94. package/dist/publish.js +1090 -0
  95. package/dist/scripts/scan-skills-registry.d.ts +24 -0
  96. package/dist/scripts/scan-skills-registry.js +207 -0
  97. package/dist/seed-import/curated-batch.d.ts +36 -0
  98. package/dist/seed-import/curated-batch.js +103 -0
  99. package/dist/seed-import/episode-execute.d.ts +50 -0
  100. package/dist/seed-import/episode-execute.js +235 -0
  101. package/dist/seed-import/episode-fetch.d.ts +119 -0
  102. package/dist/seed-import/episode-fetch.js +137 -0
  103. package/dist/seed-import/episode-plan.d.ts +11 -0
  104. package/dist/seed-import/episode-plan.js +27 -0
  105. package/dist/seed-import/episode-report.d.ts +39 -0
  106. package/dist/seed-import/episode-report.js +36 -0
  107. package/dist/seed-import/execute.d.ts +61 -0
  108. package/dist/seed-import/execute.js +245 -0
  109. package/dist/seed-import/fetch.d.ts +62 -0
  110. package/dist/seed-import/fetch.js +82 -0
  111. package/dist/seed-import/licence.d.ts +17 -0
  112. package/dist/seed-import/licence.js +36 -0
  113. package/dist/seed-import/plan.d.ts +10 -0
  114. package/dist/seed-import/plan.js +21 -0
  115. package/dist/seed-import/report.d.ts +34 -0
  116. package/dist/seed-import/report.js +31 -0
  117. package/dist/seed-import/state.d.ts +72 -0
  118. package/dist/seed-import/state.js +120 -0
  119. package/dist/signal.d.ts +36 -0
  120. package/dist/signal.js +48 -0
  121. package/dist/signing.d.ts +8 -0
  122. package/dist/signing.js +21 -0
  123. package/dist/skill-package.d.ts +70 -0
  124. package/dist/skill-package.js +141 -0
  125. package/dist/skill.d.ts +21 -0
  126. package/dist/skill.js +77 -0
  127. package/dist/snapshot-transcript.d.ts +54 -0
  128. package/dist/snapshot-transcript.js +117 -0
  129. package/dist/three-arm-run.d.ts +27 -0
  130. package/dist/three-arm-run.js +41 -0
  131. package/package.json +60 -0
@@ -0,0 +1,85 @@
1
+ /**
2
+ * `jinn-skill-distill-prompt-v1` — the layer-1-evidence → layer-2-skill
3
+ * distillation prompt (spec/2026-07-06-distillation-v1.md §7, D4/D10 + v0.5).
4
+ *
5
+ * Three modes keyed to the cluster's tier (§7), following the SkillRL
6
+ * decomposition (arXiv 2602.08234, §2.4) plus the ExpeL contrastive axis:
7
+ * successes → strategic patterns; evaluator-confirmed failures → failure
8
+ * lessons stated as DIAGNOSIS, not prescription (the verified-counterfactual
9
+ * rule — the evidence verifies THAT an attempt failed, not the fix); and
10
+ * both-polarity instances → one contrastive skill whose counterfactual IS the
11
+ * verified pass. Every mode emits the fixed skeleton and a trigger/anti-trigger
12
+ * description. Single-shot and flat for v1 (recursion/hierarchy are v3).
13
+ *
14
+ * Like `SESSION_DERIVED_DISTILL_PROMPT_V1`, this is a foundation reference
15
+ * implementation, not protocol canon: a later network-task version may
16
+ * substitute it, and the SHA-256 below is published on every distilled skill
17
+ * (`metadata.jinn.distillPromptSha256`) so generated skills stay auditable.
18
+ */
19
+ export const JINN_SKILL_DISTILL_PROMPT_V1 = `You distil verified agent evidence into ONE reusable Agent-Skill (a SKILL.md package).
20
+
21
+ Input: a cluster of evaluator-verified traces for one coding sub-problem, and a MODE.
22
+
23
+ MODE = strategic-pattern (the traces are SUCCESSES):
24
+ - Extract the critical decision points and the generalizable behavior that made the solve work — the strategy a future agent should reuse, not the specific diff.
25
+
26
+ MODE = failure-lesson (the traces are evaluator-confirmed FAILURES):
27
+ - The evidence verifies THAT this approach failed — it does NOT verify what would have worked. State the DIAGNOSIS: the failure point and WHY the approach fails ("this fails because …"). Do NOT prescribe a fix as fact. You MAY offer a hypothesis, but it must be explicitly marked as one ("likely …", "consider …") — never an imperative "instead, do X" or "the correct fix is X". A verified counterfactual is only available in contrastive mode.
28
+
29
+ MODE = contrastive (the traces are BOTH a verified PASS and a confirmed FAIL of the SAME problem):
30
+ - The delta between the pass and the fail is the signal — extract the causal decision that separates success from failure (what the passing attempt did that the failing one did not). Here the counterfactual IS verified (the pass really worked), so you MAY state it as fact.
31
+
32
+ Every skill (all modes):
33
+ - Produce a name (lowercase-hyphen), a description, and a markdown body.
34
+ - The description is the retrieval surface and MUST carry BOTH a trigger and an anti-trigger: "Use when … Not for: …". The anti-trigger names the nearby-but-situationally-wrong case the skill must NOT fire on. For a failure-lesson the trigger is the RISKY situation and the anti-trigger the safe lookalike.
35
+ - The body MUST use EXACTLY these five sections, each non-empty, in this order:
36
+ ## When to use
37
+ ## Strategy
38
+ ## Steps
39
+ ## Pitfalls
40
+ ## Verify
41
+ - Generalize: name the transferable rule, not the instance. Do NOT copy verbatim diff hunks, file paths, symbol names, instance ids, or PR numbers — those are contamination and are rejected downstream.
42
+ - Never include secrets, keys, tokens, or credentials. A skill has no legitimate need to carry raw key material.
43
+ - Use placeholder paths (/path/to/project) and invented example identifiers. Never real home directories (/Users/<name>, /home/<name>), real email addresses, or machine-specific paths — the output scrub redacts those shapes, and ANY redaction drops the whole skill (fail-closed), deterministically, on every retry.
44
+ - Be concise. A skill that restates the raw trace has not earned its place over just retrieving the trace.`;
45
+ // sha256(JINN_SKILL_DISTILL_PROMPT_V1), verified in distill.test.ts. Recomputed
46
+ // on every prompt edit (v0.5: three modes + verified-counterfactual + skeleton).
47
+ export const JINN_SKILL_DISTILL_PROMPT_V1_SHA256 = '44bf8cad03c6d3c17dd726e0f0b9a3375703028bc4be08d15804442626c6c195';
48
+ /**
49
+ * `jinn-skill-meta-distill-prompt-v1` — the stage-2 cross-instance meta-distill
50
+ * prompt (issue #1463). Input is a BATCH of stage-1 skills that already share a
51
+ * polarity (all strategic-pattern, all failure-lesson, or all contrastive),
52
+ * each labelled with an opaque source id (s1, s2, …). The task is to find the
53
+ * ONE rule that recurs across the batch and is corroborated by AT LEAST TWO
54
+ * DISTINCT instances, and to name which sources corroborate it.
55
+ *
56
+ * Like the v1 distill prompt, this is a foundation reference, not protocol
57
+ * canon; its SHA-256 is published on every meta-distilled skill
58
+ * (`metadata.jinn.distillPromptSha256`).
59
+ */
60
+ export const JINN_SKILL_META_DISTILL_PROMPT_V1 = `You distil a BATCH of already-distilled Agent-Skills into ONE higher-order cross-instance Agent-Skill (a SKILL.md package).
61
+
62
+ Input: a batch of skills, each labelled with an opaque source id (s1, s2, …). Every skill in the batch shares a POLARITY (given below), and each came from a DIFFERENT coding sub-problem instance.
63
+
64
+ MODE = cross-instance:
65
+ - Find the ONE recurring rule that generalises across the batch — the pattern, lesson, or delta that shows up in two or more of the sources for DIFFERENT instances. A rule that appears in only a single source is NOT cross-instance evidence and must not be emitted.
66
+ - Corroboration is the signal: emit a skill only for a rule that at least two DISTINCT sources support. List those source ids in "supports".
67
+
68
+ POLARITY = strategic-pattern: the sources are recurring success strategies — extract the shared generalizable behavior.
69
+ POLARITY = failure-lesson: the sources are recurring failure diagnoses. State the shared DIAGNOSIS ("this class of approach fails because …"). Do NOT prescribe a fix as fact — no imperative "instead, do X" or "the correct fix is X"; a hypothesis MUST be marked as one ("likely …", "consider …"). A verified counterfactual is only available in contrastive polarity.
70
+ POLARITY = contrastive: the sources are recurring pass↔fail deltas — extract the shared causal decision that separates success from failure; here the counterfactual IS verified, so you MAY state it as fact.
71
+
72
+ Every skill:
73
+ - Produce a name (lowercase-hyphen), a description, a markdown body, and a "supports" list of the source ids that corroborate the rule.
74
+ - The description is the retrieval surface and MUST carry BOTH a trigger and an anti-trigger: "Use when … Not for: …".
75
+ - The body MUST use EXACTLY these five sections, each non-empty, in this order:
76
+ ## When to use
77
+ ## Strategy
78
+ ## Steps
79
+ ## Pitfalls
80
+ ## Verify
81
+ - Generalize: name the transferable rule, not any single instance. Do NOT copy verbatim diff hunks, file paths, symbol names, instance ids, or PR numbers — those are contamination and are rejected downstream.
82
+ - Never include secrets, keys, tokens, or credentials. Use placeholder paths (/path/to/project) and invented example identifiers; never real home directories, emails, or machine-specific paths — the output scrub drops the whole skill (fail-closed) on any redaction.
83
+ - Be concise. A cross-instance skill that merely concatenates its sources has not earned its place.`;
84
+ // sha256(JINN_SKILL_META_DISTILL_PROMPT_V1), verified in distill.test.ts.
85
+ export const JINN_SKILL_META_DISTILL_PROMPT_V1_SHA256 = '6d21ed583cb8f3f3f92449c6b67a247be287a767517a9f83ec891e7f737b388b';
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Plain-text renderers for the `distill` consent + run surface (issue #1490).
3
+ *
4
+ * The 1490 mockup is a colour, keyboard-driven console; this package has no ANSI
5
+ * layer and its CLI prints plain text (see `renderScrubReport`, `renderRecord`).
6
+ * So these are pure string renderers that carry the design's *copy* and *states*
7
+ * faithfully — the load-bearing part — in plain text, with box-drawing panels
8
+ * (allowed) standing in for the coloured boxes. Status is a WORD, never a glyph
9
+ * (the brand's ban on `✓/✗` as UI icons), and every string that touches cost or
10
+ * consent drops all vow-metaphor and says exactly what happens.
11
+ *
12
+ * Binary name is `jinn-layer` (the real shipped bin, #1489). The mockup writes
13
+ * `jinn-agent`; that is a pending branding decision, reconciled later.
14
+ */
15
+ import type { DistillMode } from './distill-mode.js';
16
+ import type { DistillerCatalogEntry } from './distill-llm.js';
17
+ /** One skill in a run panel: its name, install state, forward value, and sources. */
18
+ export interface RenderedSkill {
19
+ name: string;
20
+ installed: boolean;
21
+ /** Human-readable source-capture labels (task summaries, or the raw ref). */
22
+ provenance: string[];
23
+ /**
24
+ * Forward-looking value: what this skill will help with next time (the
25
+ * distiller's when-to-use description). Rendered as the `helps` line so the
26
+ * operator sees what they are getting, not just where it came from.
27
+ */
28
+ helpsWith?: string;
29
+ }
30
+ /**
31
+ * 1a — first-run consent disclosure. Leads with what distillation is FOR, then
32
+ * states plainly what it READS, what it COSTS (compute, not money), and what
33
+ * LEAVES the machine (nothing, with the one hosted-distiller exception named).
34
+ */
35
+ export declare function renderConsentDisclosure(o: {
36
+ captureCount: number;
37
+ distillModel: string;
38
+ }): string;
39
+ /** 1a — the preview screen: exactly what a run would read and the distiller. */
40
+ export declare function renderPreview(o: {
41
+ captures: {
42
+ summary: string;
43
+ when: string;
44
+ size: string;
45
+ }[];
46
+ distillModel: string;
47
+ }): string;
48
+ /** 1a — confirm the one spend before a local run. */
49
+ export declare function renderConfirmLocal(o: {
50
+ captureCount: number;
51
+ distillModel: string;
52
+ }): string;
53
+ /** 1c — one-line echo after `distill --where <mode>` sets the persistent mode. */
54
+ export declare function renderModeSet(mode: DistillMode): string;
55
+ /** One-line echo after `distill --set-distiller[-model]` records a default. */
56
+ export declare function renderDistillerSet(o: {
57
+ distiller?: 'claude' | 'codex';
58
+ distillerModel?: string;
59
+ }): string;
60
+ /**
61
+ * `distill models` — the discoverable catalog. One row per runnable distiller
62
+ * with its execution/cost/privacy attributes; the currently-resolved default
63
+ * is marked with the word `(default)` (never a glyph).
64
+ */
65
+ export declare function renderDistillerModels(o: {
66
+ catalog: readonly DistillerCatalogEntry[];
67
+ resolved: {
68
+ provider: string;
69
+ model: string;
70
+ };
71
+ }): string;
72
+ /** 1c — what a bare `distill` prints while the mode is defer: it runs nothing. */
73
+ export declare function renderDeferredRun(o: {
74
+ captureCount: number;
75
+ capturesDir: string;
76
+ }): string;
77
+ /** 1a — the recorded-state confirmation after a mode is chosen. */
78
+ export declare function renderRecorded(mode: DistillMode, o: {
79
+ captureCount: number;
80
+ }): string;
81
+ /** 1d — nothing to distill yet: say what produces a capture. */
82
+ export declare function renderEmpty(o: {
83
+ capturesDir: string;
84
+ }): string;
85
+ /**
86
+ * 1b — the skills panel: per-skill install-state word + name, then the
87
+ * forward-looking `helps` line (what it will help with next) and the backward
88
+ * `from` line (which captures it came from).
89
+ */
90
+ export declare function renderSkillsPanel(skills: RenderedSkill[], title: string): string;
91
+ /**
92
+ * 1b — the run terminus. Header, captures→skills summary, the skills panel with
93
+ * per-skill install state, and a footer keyed to the install outcome (none
94
+ * installed = staged/how-to-install, all = ready, some = partial).
95
+ */
96
+ export declare function renderRunSummary(o: {
97
+ distillModel: string;
98
+ captureCount: number;
99
+ skills: RenderedSkill[];
100
+ }): string;
101
+ /**
102
+ * 1b — the review shown before the install choice (interactive). Frames the
103
+ * skills FORWARD — what they'll help with next — then asks for an explicit
104
+ * all / one / skip choice. Nothing is installed until the operator answers.
105
+ */
106
+ export declare function renderReview(o: {
107
+ distillModel: string;
108
+ captureCount: number;
109
+ skills: RenderedSkill[];
110
+ }): string;
111
+ /** 1d — a run failed mid-way: what stopped, what survived, one resume command. */
112
+ export declare function renderFailure(o: {
113
+ distillModel: string;
114
+ distilledCount: number;
115
+ errors: {
116
+ clusterId: string;
117
+ error: string;
118
+ }[];
119
+ }): string;
120
+ /** 1c/1d — `--resume` with no capture left to distill. */
121
+ export declare function renderResumeNothing(o: {
122
+ captureCount: number;
123
+ }): string;
@@ -0,0 +1,303 @@
1
+ /**
2
+ * Plain-text renderers for the `distill` consent + run surface (issue #1490).
3
+ *
4
+ * The 1490 mockup is a colour, keyboard-driven console; this package has no ANSI
5
+ * layer and its CLI prints plain text (see `renderScrubReport`, `renderRecord`).
6
+ * So these are pure string renderers that carry the design's *copy* and *states*
7
+ * faithfully — the load-bearing part — in plain text, with box-drawing panels
8
+ * (allowed) standing in for the coloured boxes. Status is a WORD, never a glyph
9
+ * (the brand's ban on `✓/✗` as UI icons), and every string that touches cost or
10
+ * consent drops all vow-metaphor and says exactly what happens.
11
+ *
12
+ * Binary name is `jinn-layer` (the real shipped bin, #1489). The mockup writes
13
+ * `jinn-agent`; that is a pending branding decision, reconciled later.
14
+ */
15
+ const BIN = 'jinn-layer';
16
+ function plural(n, one) {
17
+ return `${n} ${one}${n === 1 ? '' : 's'}`;
18
+ }
19
+ // ── boxed panel (plain analogue of the mockup's coloured box) ────────────────
20
+ // The three border rows and every body row share one frame width (IW + 4), so
21
+ // the panel never overflows its widest line.
22
+ function boxTop(iw, title) {
23
+ const t = ` ${title} `;
24
+ return `┌─${t}${'─'.repeat(Math.max(0, iw + 1 - t.length))}┐`;
25
+ }
26
+ function boxBot(iw) {
27
+ return `└${'─'.repeat(iw + 2)}┘`;
28
+ }
29
+ function boxLine(iw, content) {
30
+ const clipped = content.length > iw ? content.slice(0, iw) : content;
31
+ return `│ ${clipped}${' '.repeat(Math.max(0, iw - clipped.length))} │`;
32
+ }
33
+ function box(title, contentLines) {
34
+ const iw = Math.max(title.length + 1, ...contentLines.map((l) => l.length), 40);
35
+ return [boxTop(iw, title), ...contentLines.map((l) => boxLine(iw, l)), boxBot(iw)].join('\n');
36
+ }
37
+ /** Width of the install-state column (mockup uses 14). */
38
+ const STATE_COL = 14;
39
+ /**
40
+ * 1a — first-run consent disclosure. Leads with what distillation is FOR, then
41
+ * states plainly what it READS, what it COSTS (compute, not money), and what
42
+ * LEAVES the machine (nothing, with the one hosted-distiller exception named).
43
+ */
44
+ export function renderConsentDisclosure(o) {
45
+ return [
46
+ ` Distill your recent tasks into skills?`,
47
+ '',
48
+ ` A frontier-class model reads the tasks you've run here, once, and pulls`,
49
+ ` out reusable skills — later tasks then reuse them on a small, cheap model.`,
50
+ ` Pay the heavy pass once; reuse the skills for free.`,
51
+ '',
52
+ ` READS ${plural(o.captureCount, 'capture')} this run — the tasks you've run here so far, more`,
53
+ ` as you work. Nothing else.`,
54
+ '',
55
+ ` COSTS one pass of heavy compute — not money — and it runs here, on`,
56
+ ` this machine.`,
57
+ '',
58
+ ` LEAVES nothing — read, distilled, and written here; nothing is published`,
59
+ ` to the Jinn network. A hosted distiller (below) is the one`,
60
+ ` exception; ${BIN} names it and asks first.`,
61
+ '',
62
+ ` DISTILLER ${o.distillModel} (local) — the model that writes the skills, and`,
63
+ ` yours to choose. Change with --distiller <provider> or`,
64
+ ` --distiller-model <model>.`,
65
+ '',
66
+ ` [L] Local now · [F] Defer (default) · [O] Off · [P] Preview`,
67
+ '',
68
+ ` distill: unset — default is defer, nothing runs, nothing is spent`,
69
+ ].join('\n');
70
+ }
71
+ /** 1a — the preview screen: exactly what a run would read and the distiller. */
72
+ export function renderPreview(o) {
73
+ const rows = o.captures.map((c) => boxLine(54, `${c.when.padEnd(9)}${c.summary}`));
74
+ const iw = 54;
75
+ const panel = [
76
+ boxTop(iw, 'captures · read locally, never sent'),
77
+ ...rows,
78
+ boxBot(iw),
79
+ ].join('\n');
80
+ return [
81
+ ` PREVIEW — NOTHING RUNS FROM THIS SCREEN`,
82
+ ` The exact captures a run would read, and the distiller it would use.`,
83
+ '',
84
+ panel,
85
+ '',
86
+ ` distiller ${o.distillModel} (local, default) · change with --distiller`,
87
+ ` With the local default, none of this leaves the machine.`,
88
+ '',
89
+ ` [L] Run local now · [B] Back`,
90
+ ].join('\n');
91
+ }
92
+ /** 1a — confirm the one spend before a local run. */
93
+ export function renderConfirmLocal(o) {
94
+ return [
95
+ ` CONFIRM`,
96
+ '',
97
+ ` Run distillation now? One frontier pass with ${o.distillModel} over`,
98
+ ` ${plural(o.captureCount, 'capture')}, here on this machine — heavy compute, no money, nothing`,
99
+ ` sent. Stop any time; whatever finished is kept.`,
100
+ '',
101
+ ` [Y] Yes, run now · [N] No, go back`,
102
+ ].join('\n');
103
+ }
104
+ /** 1c — one-line echo after `distill --where <mode>` sets the persistent mode. */
105
+ export function renderModeSet(mode) {
106
+ switch (mode) {
107
+ case 'local':
108
+ return `distill: mode set to local. Runs here with a frontier-class model.`;
109
+ case 'defer':
110
+ return `distill: mode set to deferred. Captures held locally; nothing runs or publishes.`;
111
+ case 'off':
112
+ return `distill: mode set to off. Captures are not reserved for distillation.`;
113
+ }
114
+ }
115
+ /** One-line echo after `distill --set-distiller[-model]` records a default. */
116
+ export function renderDistillerSet(o) {
117
+ const parts = [];
118
+ if (o.distiller !== undefined)
119
+ parts.push(`provider ${o.distiller}`);
120
+ if (o.distillerModel !== undefined)
121
+ parts.push(`model ${o.distillerModel}`);
122
+ return `distill: default distiller set — ${parts.join(', ')}. Applies to future runs; a per-run --distiller[-model] flag still wins.`;
123
+ }
124
+ /**
125
+ * `distill models` — the discoverable catalog. One row per runnable distiller
126
+ * with its execution/cost/privacy attributes; the currently-resolved default
127
+ * is marked with the word `(default)` (never a glyph).
128
+ */
129
+ export function renderDistillerModels(o) {
130
+ const iw = 74;
131
+ const rows = o.catalog.map((e) => {
132
+ const isResolved = e.provider === o.resolved.provider && e.model === o.resolved.model;
133
+ const mark = isResolved ? ' (default)' : '';
134
+ return boxLine(iw, `${e.provider.padEnd(8)}${e.execution.padEnd(8)}${e.cost.padEnd(22)}${e.model}${mark}`);
135
+ });
136
+ const panel = [
137
+ boxTop(iw, 'distillers · the model that writes your skills'),
138
+ boxLine(iw, `${'provider'.padEnd(8)}${'run'.padEnd(8)}${'cost'.padEnd(22)}model`),
139
+ ...rows,
140
+ boxBot(iw),
141
+ ].join('\n');
142
+ return [
143
+ panel,
144
+ '',
145
+ ...o.catalog.map((e) => ` ${e.model} — ${e.privacy}`),
146
+ '',
147
+ ` Set a default · ${BIN} distill --set-distiller <provider> | --set-distiller-model <model>`,
148
+ ` Override once · ${BIN} distill --distiller <provider> | --distiller-model <model>`,
149
+ ].join('\n');
150
+ }
151
+ /** 1c — what a bare `distill` prints while the mode is defer: it runs nothing. */
152
+ export function renderDeferredRun(o) {
153
+ return [
154
+ ` distill: deferred — ${plural(o.captureCount, 'capture')} held locally, nothing runs.`,
155
+ '',
156
+ ` They stay on this machine and are not published. When the bonded network`,
157
+ ` can distill a contributed signal, you'll be asked before anything leaves`,
158
+ ` this machine. (not available yet — rung 3)`,
159
+ '',
160
+ ` Run locally now · ${BIN} distill --where local`,
161
+ ].join('\n');
162
+ }
163
+ /** 1a — the recorded-state confirmation after a mode is chosen. */
164
+ export function renderRecorded(mode, o) {
165
+ switch (mode) {
166
+ case 'local':
167
+ return [
168
+ ` recorded — distill mode is LOCAL`,
169
+ '',
170
+ ` Distillation runs on this machine with a frontier-class model.`,
171
+ '',
172
+ ` Change any time · ${BIN} distill --where defer | --where off`,
173
+ ].join('\n');
174
+ case 'defer':
175
+ return [
176
+ ` recorded — distill mode is DEFERRED`,
177
+ '',
178
+ ` Your ${plural(o.captureCount, 'capture')} are held on this machine. Nothing runs, nothing is`,
179
+ ` spent, and nothing is published to the Jinn network.`,
180
+ '',
181
+ ` Run locally instead · ${BIN} distill --where local`,
182
+ ].join('\n');
183
+ case 'off':
184
+ return [
185
+ ` recorded — distill is OFF`,
186
+ '',
187
+ ` Distillation is disabled and no captures are reserved for it. Nothing`,
188
+ ` the harness already keeps is deleted — this only turns distilling off.`,
189
+ '',
190
+ ` Turn it on any time · ${BIN} distill --where local | --where defer`,
191
+ ].join('\n');
192
+ }
193
+ }
194
+ /** 1d — nothing to distill yet: say what produces a capture. */
195
+ export function renderEmpty(o) {
196
+ return [
197
+ ` No eligible captures.`,
198
+ '',
199
+ ` Distillation reads captures — the tasks you've run with the harness.`,
200
+ ` There aren't any yet — run a task first, then run \`${BIN} distill\` again.`,
201
+ '',
202
+ ` (looked in ${o.capturesDir})`,
203
+ ].join('\n');
204
+ }
205
+ /**
206
+ * 1b — the skills panel: per-skill install-state word + name, then the
207
+ * forward-looking `helps` line (what it will help with next) and the backward
208
+ * `from` line (which captures it came from).
209
+ */
210
+ export function renderSkillsPanel(skills, title) {
211
+ const content = [];
212
+ skills.forEach((s, i) => {
213
+ const state = (s.installed ? 'installed' : 'not installed').padEnd(STATE_COL);
214
+ content.push(`${state}${s.name}`);
215
+ if (s.helpsWith)
216
+ content.push(`${' '.repeat(STATE_COL)}helps ${s.helpsWith}`);
217
+ content.push(`${' '.repeat(STATE_COL)}from ${s.provenance.join(' · ')}`);
218
+ if (i < skills.length - 1)
219
+ content.push('');
220
+ });
221
+ return box(title, content);
222
+ }
223
+ /** Title + footer for the run terminus, keyed to how many skills are installed. */
224
+ function runSummaryChrome(installed, total) {
225
+ if (installed === 0) {
226
+ return {
227
+ title: 'skills · distilled locally · not installed',
228
+ footer: [
229
+ ` They stay local until you install them — nothing goes live until you choose.`,
230
+ ` Install · ${BIN} distill --install all · --install <name> · --install none`,
231
+ ` List them · /jinn skills`,
232
+ ],
233
+ };
234
+ }
235
+ if (installed === total) {
236
+ return {
237
+ title: 'skills · installed · ready',
238
+ footer: [
239
+ ` Next runs use these on a small model — no frontier pass.`,
240
+ ` Manage · /jinn skills · /jinn skills remove <name>`,
241
+ ],
242
+ };
243
+ }
244
+ return {
245
+ title: `skills · ${installed} installed · ${total - installed} available`,
246
+ footer: [
247
+ ` Installed skills run on a small model next time — no frontier pass.`,
248
+ ` Install the rest · ${BIN} distill --install all`,
249
+ ` Manage · /jinn skills`,
250
+ ],
251
+ };
252
+ }
253
+ /**
254
+ * 1b — the run terminus. Header, captures→skills summary, the skills panel with
255
+ * per-skill install state, and a footer keyed to the install outcome (none
256
+ * installed = staged/how-to-install, all = ready, some = partial).
257
+ */
258
+ export function renderRunSummary(o) {
259
+ const installed = o.skills.filter((s) => s.installed).length;
260
+ const { title, footer } = runSummaryChrome(installed, o.skills.length);
261
+ return [
262
+ ` distill: local · distiller ${o.distillModel} · ${plural(o.captureCount, 'capture')}`,
263
+ '',
264
+ ` distilled · ${plural(o.captureCount, 'capture')} → ${plural(o.skills.length, 'skill')} · nothing left this machine`,
265
+ '',
266
+ renderSkillsPanel(o.skills, title),
267
+ '',
268
+ ...footer,
269
+ ].join('\n');
270
+ }
271
+ /**
272
+ * 1b — the review shown before the install choice (interactive). Frames the
273
+ * skills FORWARD — what they'll help with next — then asks for an explicit
274
+ * all / one / skip choice. Nothing is installed until the operator answers.
275
+ */
276
+ export function renderReview(o) {
277
+ return [
278
+ ` distill: local · distiller ${o.distillModel} · ${plural(o.captureCount, 'capture')}`,
279
+ '',
280
+ ` distilled ${plural(o.skills.length, 'skill')} — they'll help with tasks like these next time:`,
281
+ '',
282
+ renderSkillsPanel(o.skills.map((s) => ({ ...s, installed: false })), 'skills · distilled locally · not installed'),
283
+ '',
284
+ ` Install all, one, or skip? Nothing goes live until you choose.`,
285
+ ].join('\n');
286
+ }
287
+ /** 1d — a run failed mid-way: what stopped, what survived, one resume command. */
288
+ export function renderFailure(o) {
289
+ const first = o.errors[0]?.error ?? 'the distiller stopped responding';
290
+ return [
291
+ ` distill failed — ${first}`,
292
+ '',
293
+ ` ${plural(o.distilledCount, 'skill')} were distilled and kept. ${plural(o.errors.length, 'cluster')} did not`,
294
+ ` finish; the rest weren't distilled. Nothing was sent or lost.`,
295
+ '',
296
+ ` Resume the rest · ${BIN} distill --resume`,
297
+ ` See what's done · /jinn skills`,
298
+ ].join('\n');
299
+ }
300
+ /** 1c/1d — `--resume` with no capture left to distill. */
301
+ export function renderResumeNothing(o) {
302
+ return `Nothing to resume — all ${plural(o.captureCount, 'capture')} are already distilled.`;
303
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * The distill run log (#1535): one JSON line per own-captures run, appended on
3
+ * every terminal outcome (ok / partial / empty). This is the history behind
4
+ * `distill runs` and the `lastRun` field of `distill status` — without it a
5
+ * completed run's only trace was the staged/installed skills themselves.
6
+ *
7
+ * Mirrors the ledger's shape discipline: append-only JSONL, corrupt-line
8
+ * tolerant reads (a bad line is skipped, never fatal).
9
+ */
10
+ /** One recorded run — the `run_end` facts plus when it started. */
11
+ export interface DistillRunRecord {
12
+ runId: string;
13
+ startedAt: string;
14
+ durationMs: number;
15
+ outcome: 'ok' | 'partial' | 'empty';
16
+ clusterCount: number;
17
+ published: string[];
18
+ rejectedCount: number;
19
+ errorCount: number;
20
+ installed: string[];
21
+ distillModel: string;
22
+ }
23
+ /** The default run log, alongside the layer's other operator state. */
24
+ export declare const DEFAULT_DISTILL_RUNS_PATH: string;
25
+ /** Append one run record, creating the parent directory if needed. */
26
+ export declare function appendDistillRun(record: DistillRunRecord, path?: string): void;
27
+ /** Read up to `limit` runs, newest-first. Corrupt lines are skipped. */
28
+ export declare function readDistillRuns(limit: number, path?: string): DistillRunRecord[];
@@ -0,0 +1,39 @@
1
+ /**
2
+ * The distill run log (#1535): one JSON line per own-captures run, appended on
3
+ * every terminal outcome (ok / partial / empty). This is the history behind
4
+ * `distill runs` and the `lastRun` field of `distill status` — without it a
5
+ * completed run's only trace was the staged/installed skills themselves.
6
+ *
7
+ * Mirrors the ledger's shape discipline: append-only JSONL, corrupt-line
8
+ * tolerant reads (a bad line is skipped, never fatal).
9
+ */
10
+ import { appendFileSync, existsSync, mkdirSync, readFileSync } from 'node:fs';
11
+ import { homedir } from 'node:os';
12
+ import { dirname, join } from 'node:path';
13
+ /** The default run log, alongside the layer's other operator state. */
14
+ export const DEFAULT_DISTILL_RUNS_PATH = join(homedir(), '.jinn-client', 'harness-layer', 'distill-runs.jsonl');
15
+ /** Append one run record, creating the parent directory if needed. */
16
+ export function appendDistillRun(record, path = DEFAULT_DISTILL_RUNS_PATH) {
17
+ mkdirSync(dirname(path), { recursive: true });
18
+ appendFileSync(path, JSON.stringify(record) + '\n', { encoding: 'utf-8' });
19
+ }
20
+ /** Read up to `limit` runs, newest-first. Corrupt lines are skipped. */
21
+ export function readDistillRuns(limit, path = DEFAULT_DISTILL_RUNS_PATH) {
22
+ if (!existsSync(path))
23
+ return [];
24
+ const records = [];
25
+ for (const line of readFileSync(path, 'utf-8').split('\n')) {
26
+ if (line.trim() === '')
27
+ continue;
28
+ try {
29
+ const parsed = JSON.parse(line);
30
+ if (typeof parsed === 'object' && parsed !== null && typeof parsed.outcome === 'string') {
31
+ records.push(parsed);
32
+ }
33
+ }
34
+ catch {
35
+ // Tolerate a corrupt line — the log must never brick the CLI.
36
+ }
37
+ }
38
+ return records.reverse().slice(0, limit);
39
+ }
@@ -0,0 +1,67 @@
1
+ import type { CapturedTask } from './capture.js';
2
+ export type TraceReadMode = 'summary' | 'events' | 'tool_calls' | 'transcript_excerpt' | 'full_transcript';
3
+ export type EstimatedDistillCost = 'low' | 'medium' | 'high';
4
+ export type EvidenceTierEstimate = 'single-example' | 'recurring-pattern' | 'contrastive';
5
+ export interface LocalTraceCard {
6
+ traceId: string;
7
+ sessionId: string;
8
+ sourceTool: string;
9
+ repo?: string;
10
+ startedAt: string;
11
+ durationMs: number;
12
+ summary: string;
13
+ tools: string[];
14
+ commands: string[];
15
+ filesTouched: string[];
16
+ errorSnippets: string[];
17
+ outcome: CapturedTask['outcome']['status'];
18
+ skillsUsed: string[];
19
+ redactionFlags: string[];
20
+ }
21
+ export interface TraceEventView {
22
+ spanId: string;
23
+ kind: string;
24
+ name: string;
25
+ text?: string;
26
+ toolName?: string;
27
+ attributes?: Record<string, unknown>;
28
+ }
29
+ export interface TraceReadResult {
30
+ mode: TraceReadMode;
31
+ traceId: string;
32
+ summary: string;
33
+ events?: TraceEventView[];
34
+ toolCalls?: TraceEventView[];
35
+ }
36
+ export interface TraceSearchQuery {
37
+ query?: string;
38
+ command?: string;
39
+ file?: string;
40
+ error?: string;
41
+ tool?: string;
42
+ outcome?: CapturedTask['outcome']['status'];
43
+ limit?: number;
44
+ }
45
+ export interface TraceCandidate {
46
+ candidateId: string;
47
+ title: string;
48
+ traceIds: string[];
49
+ repeatedSignals: {
50
+ commands?: string[];
51
+ errors?: string[];
52
+ files?: string[];
53
+ };
54
+ evidenceTierEstimate: EvidenceTierEstimate;
55
+ estimatedSourceTokens: number;
56
+ estimatedDistillCost: EstimatedDistillCost;
57
+ privacyFlags: string[];
58
+ }
59
+ export declare function traceCardFromCapture(task: CapturedTask): LocalTraceCard;
60
+ export declare function readTrace(task: CapturedTask, opts: {
61
+ mode: TraceReadMode;
62
+ query?: string;
63
+ limit?: number;
64
+ allowFullTranscript?: boolean;
65
+ }): TraceReadResult;
66
+ export declare function searchTraceCards(cards: LocalTraceCard[], query: TraceSearchQuery): LocalTraceCard[];
67
+ export declare function clusterTraceCards(cards: LocalTraceCard[]): TraceCandidate[];