@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.
- package/dist/adapters/contribution-adapter.d.ts +18 -0
- package/dist/adapters/contribution-adapter.js +134 -0
- package/dist/adapters/corpus-adapter.d.ts +12 -0
- package/dist/adapters/corpus-adapter.js +212 -0
- package/dist/adapters/index.d.ts +17 -0
- package/dist/adapters/index.js +17 -0
- package/dist/adapters/json-map-store.d.ts +4 -0
- package/dist/adapters/json-map-store.js +31 -0
- package/dist/adapters/local-learning-adapter.d.ts +25 -0
- package/dist/adapters/local-learning-adapter.js +49 -0
- package/dist/adapters/skills-adapter.d.ts +8 -0
- package/dist/adapters/skills-adapter.js +55 -0
- package/dist/admission-policy.d.ts +38 -0
- package/dist/admission-policy.js +17 -0
- package/dist/bin/jinn-distill-mcp.d.ts +2 -0
- package/dist/bin/jinn-distill-mcp.js +10 -0
- package/dist/bin/jinn-layer.d.ts +7 -0
- package/dist/bin/jinn-layer.js +15 -0
- package/dist/bridge-fetch-evidence.d.ts +102 -0
- package/dist/bridge-fetch-evidence.js +677 -0
- package/dist/bridge-verdict-source.d.ts +47 -0
- package/dist/bridge-verdict-source.js +216 -0
- package/dist/bridge.d.ts +247 -0
- package/dist/bridge.js +439 -0
- package/dist/capture.d.ts +98 -0
- package/dist/capture.js +617 -0
- package/dist/cli.d.ts +148 -0
- package/dist/cli.js +2221 -0
- package/dist/cluster-selection.d.ts +34 -0
- package/dist/cluster-selection.js +135 -0
- package/dist/cluster.d.ts +89 -0
- package/dist/cluster.js +142 -0
- package/dist/consume.d.ts +139 -0
- package/dist/consume.js +280 -0
- package/dist/corpus-probes.d.ts +61 -0
- package/dist/corpus-probes.js +120 -0
- package/dist/corpus-store.d.ts +16 -0
- package/dist/corpus-store.js +101 -0
- package/dist/distill-captures.d.ts +56 -0
- package/dist/distill-captures.js +283 -0
- package/dist/distill-feedback.d.ts +20 -0
- package/dist/distill-feedback.js +30 -0
- package/dist/distill-llm.d.ts +138 -0
- package/dist/distill-llm.js +438 -0
- package/dist/distill-mcp-server.d.ts +30 -0
- package/dist/distill-mcp-server.js +281 -0
- package/dist/distill-mode.d.ts +62 -0
- package/dist/distill-mode.js +107 -0
- package/dist/distill-progress.d.ts +78 -0
- package/dist/distill-progress.js +69 -0
- package/dist/distill-prompt.d.ts +34 -0
- package/dist/distill-prompt.js +85 -0
- package/dist/distill-render.d.ts +123 -0
- package/dist/distill-render.js +303 -0
- package/dist/distill-runs.d.ts +28 -0
- package/dist/distill-runs.js +39 -0
- package/dist/distill-traces.d.ts +67 -0
- package/dist/distill-traces.js +211 -0
- package/dist/distill.d.ts +180 -0
- package/dist/distill.js +337 -0
- package/dist/distiller.d.ts +118 -0
- package/dist/distiller.js +142 -0
- package/dist/envelope.d.ts +9 -0
- package/dist/envelope.js +9 -0
- package/dist/eval-prep.d.ts +129 -0
- package/dist/eval-prep.js +523 -0
- package/dist/execution-publish.d.ts +62 -0
- package/dist/execution-publish.js +72 -0
- package/dist/gate.d.ts +39 -0
- package/dist/gate.js +106 -0
- package/dist/index.d.ts +49 -0
- package/dist/index.js +50 -0
- package/dist/ipfs-cid.d.ts +18 -0
- package/dist/ipfs-cid.js +149 -0
- package/dist/layer-default.d.ts +3 -0
- package/dist/layer-default.js +10 -0
- package/dist/ledger.d.ts +62 -0
- package/dist/ledger.js +104 -0
- package/dist/measurement.d.ts +90 -0
- package/dist/measurement.js +134 -0
- package/dist/path-safety.d.ts +19 -0
- package/dist/path-safety.js +303 -0
- package/dist/pipeline.d.ts +94 -0
- package/dist/pipeline.js +124 -0
- package/dist/plugin-wiring.d.ts +4 -0
- package/dist/plugin-wiring.js +54 -0
- package/dist/preview.d.ts +31 -0
- package/dist/preview.js +28 -0
- package/dist/process-contract.d.ts +303 -0
- package/dist/process-contract.js +118 -0
- package/dist/publish-skill.d.ts +86 -0
- package/dist/publish-skill.js +116 -0
- package/dist/publish.d.ts +294 -0
- package/dist/publish.js +1090 -0
- package/dist/scripts/scan-skills-registry.d.ts +24 -0
- package/dist/scripts/scan-skills-registry.js +207 -0
- package/dist/seed-import/curated-batch.d.ts +36 -0
- package/dist/seed-import/curated-batch.js +103 -0
- package/dist/seed-import/episode-execute.d.ts +50 -0
- package/dist/seed-import/episode-execute.js +235 -0
- package/dist/seed-import/episode-fetch.d.ts +119 -0
- package/dist/seed-import/episode-fetch.js +137 -0
- package/dist/seed-import/episode-plan.d.ts +11 -0
- package/dist/seed-import/episode-plan.js +27 -0
- package/dist/seed-import/episode-report.d.ts +39 -0
- package/dist/seed-import/episode-report.js +36 -0
- package/dist/seed-import/execute.d.ts +61 -0
- package/dist/seed-import/execute.js +245 -0
- package/dist/seed-import/fetch.d.ts +62 -0
- package/dist/seed-import/fetch.js +82 -0
- package/dist/seed-import/licence.d.ts +17 -0
- package/dist/seed-import/licence.js +36 -0
- package/dist/seed-import/plan.d.ts +10 -0
- package/dist/seed-import/plan.js +21 -0
- package/dist/seed-import/report.d.ts +34 -0
- package/dist/seed-import/report.js +31 -0
- package/dist/seed-import/state.d.ts +72 -0
- package/dist/seed-import/state.js +120 -0
- package/dist/signal.d.ts +36 -0
- package/dist/signal.js +48 -0
- package/dist/signing.d.ts +8 -0
- package/dist/signing.js +21 -0
- package/dist/skill-package.d.ts +70 -0
- package/dist/skill-package.js +141 -0
- package/dist/skill.d.ts +21 -0
- package/dist/skill.js +77 -0
- package/dist/snapshot-transcript.d.ts +54 -0
- package/dist/snapshot-transcript.js +117 -0
- package/dist/three-arm-run.d.ts +27 -0
- package/dist/three-arm-run.js +41 -0
- package/package.json +60 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Evidence-episode seed source (issue #1771, rescope plan §4.2-§4.4).
|
|
3
|
+
*
|
|
4
|
+
* Stage 1's rescoped retrieval serves evidence records, not skill records
|
|
5
|
+
* (docs/superpowers/plans/2026-07-16-jinn-plugin-stage-1-rescope-plan.md
|
|
6
|
+
* §3.1). The shared testnet corpus had almost no evidence to serve — skills.sh
|
|
7
|
+
* skill seeds only. This source reads a directory of hand-authored
|
|
8
|
+
* seed-episode JSON files (each transformed offline from a REAL completed
|
|
9
|
+
* session per the runbook, docs/runbooks/stage1-evidence-seeding.md) and
|
|
10
|
+
* feeds them through the SAME `capture() -> publish()` path the skill lane
|
|
11
|
+
* uses (`episode-execute.ts`). The resulting envelope remains identifiable
|
|
12
|
+
* as a seed on the wire through `provenance: 'imported'`, the seed importer
|
|
13
|
+
* harness/session identity, the `seed-import` tag, and `seed:*` steps. The
|
|
14
|
+
* selected scrub profile is a local implementation fact proved by code and
|
|
15
|
+
* tests; `TraceEnvelopeV0` does not publish a scrub-component manifest.
|
|
16
|
+
*
|
|
17
|
+
* Step convention (so a future retrieval-side packet projector — rescope
|
|
18
|
+
* plan §3.2 `projectKnowledgePacket`, R1/#1770 — has an obvious, discoverable
|
|
19
|
+
* shape to read): every content step's `attributes` carries
|
|
20
|
+
* 'seed.step.label' — one of the §3.2 excerpt labels
|
|
21
|
+
* ('failure' | 'fix' | 'command' | 'diff' | 'note')
|
|
22
|
+
* 'seed.step.title' — a short human title
|
|
23
|
+
* 'seed.step.text' — the excerpt content itself
|
|
24
|
+
* and a final step carries the episode-level metadata:
|
|
25
|
+
* 'seed.synthesis' — the authored how-it-was-solved text (§3.2 `synthesis`)
|
|
26
|
+
* 'seed.attribution' — { repo, baseCommit?, origin, sourceUrl?, supersedes? }
|
|
27
|
+
* No bespoke fixture format: this is exactly the frozen jinn.trace-envelope.v0
|
|
28
|
+
* step shape (spec §3.1), the same free-form-attribute convention the
|
|
29
|
+
* existing `seed:skill-md` skill-seed step already uses.
|
|
30
|
+
*/
|
|
31
|
+
import { z } from 'zod';
|
|
32
|
+
/** Rescope plan §3.2 excerpt labels — the knowledge-packet projection's vocabulary. */
|
|
33
|
+
export declare const SEED_EPISODE_EXCERPT_LABELS: readonly ["failure", "fix", "command", "diff", "note"];
|
|
34
|
+
export declare const SeedEpisodeExcerptLabelSchema: z.ZodEnum<{
|
|
35
|
+
command: "command";
|
|
36
|
+
failure: "failure";
|
|
37
|
+
fix: "fix";
|
|
38
|
+
diff: "diff";
|
|
39
|
+
note: "note";
|
|
40
|
+
}>;
|
|
41
|
+
export type SeedEpisodeExcerptLabel = z.infer<typeof SeedEpisodeExcerptLabelSchema>;
|
|
42
|
+
export declare const SeedEpisodeStepSchema: z.ZodObject<{
|
|
43
|
+
label: z.ZodEnum<{
|
|
44
|
+
command: "command";
|
|
45
|
+
failure: "failure";
|
|
46
|
+
fix: "fix";
|
|
47
|
+
diff: "diff";
|
|
48
|
+
note: "note";
|
|
49
|
+
}>;
|
|
50
|
+
title: z.ZodString;
|
|
51
|
+
text: z.ZodString;
|
|
52
|
+
}, z.core.$strict>;
|
|
53
|
+
export type SeedEpisodeStep = z.infer<typeof SeedEpisodeStepSchema>;
|
|
54
|
+
/**
|
|
55
|
+
* A seed-episode file — the canonical captured-evidence contract (issue
|
|
56
|
+
* #1771): task summary, ordered steps with real commands/outputs (a source
|
|
57
|
+
* episode carries at least one `failure` and its `fix`; distractors are
|
|
58
|
+
* freeform), outcome + verifiability tier, tags, attribution, and an
|
|
59
|
+
* authored synthesis. Strict: an unknown field is a malformed fixture, not
|
|
60
|
+
* silently tolerated.
|
|
61
|
+
*/
|
|
62
|
+
export declare const SeedEpisodeSchema: z.ZodObject<{
|
|
63
|
+
id: z.ZodString;
|
|
64
|
+
repo: z.ZodString;
|
|
65
|
+
baseCommit: z.ZodOptional<z.ZodString>;
|
|
66
|
+
taskSummary: z.ZodString;
|
|
67
|
+
tags: z.ZodArray<z.ZodString>;
|
|
68
|
+
steps: z.ZodArray<z.ZodObject<{
|
|
69
|
+
label: z.ZodEnum<{
|
|
70
|
+
command: "command";
|
|
71
|
+
failure: "failure";
|
|
72
|
+
fix: "fix";
|
|
73
|
+
diff: "diff";
|
|
74
|
+
note: "note";
|
|
75
|
+
}>;
|
|
76
|
+
title: z.ZodString;
|
|
77
|
+
text: z.ZodString;
|
|
78
|
+
}, z.core.$strict>>;
|
|
79
|
+
outcome: z.ZodObject<{
|
|
80
|
+
status: z.ZodEnum<{
|
|
81
|
+
completed: "completed";
|
|
82
|
+
failed: "failed";
|
|
83
|
+
abandoned: "abandoned";
|
|
84
|
+
}>;
|
|
85
|
+
verifiabilityTier: z.ZodEnum<{
|
|
86
|
+
"user-accepted": "user-accepted";
|
|
87
|
+
"tests-passed": "tests-passed";
|
|
88
|
+
"evaluator-verified": "evaluator-verified";
|
|
89
|
+
}>;
|
|
90
|
+
}, z.core.$strict>;
|
|
91
|
+
synthesis: z.ZodString;
|
|
92
|
+
attribution: z.ZodObject<{
|
|
93
|
+
origin: z.ZodString;
|
|
94
|
+
sourceUrl: z.ZodOptional<z.ZodString>;
|
|
95
|
+
}, z.core.$strict>;
|
|
96
|
+
}, z.core.$strict>;
|
|
97
|
+
export type SeedEpisode = z.infer<typeof SeedEpisodeSchema>;
|
|
98
|
+
/** Canonical identity and content approved by an episode report row. */
|
|
99
|
+
export declare function episodeContentDigest(episode: SeedEpisode): string;
|
|
100
|
+
/** Parse an untrusted value (a seed-episode JSON file) as a SeedEpisode; throws ZodError. */
|
|
101
|
+
export declare function parseSeedEpisode(input: unknown): SeedEpisode;
|
|
102
|
+
export interface EpisodeSource {
|
|
103
|
+
name: string;
|
|
104
|
+
list(): Promise<SeedEpisode[]>;
|
|
105
|
+
}
|
|
106
|
+
export interface LocalEpisodeSeedSourceOptions {
|
|
107
|
+
maxFiles?: number;
|
|
108
|
+
maxFileBytes?: number;
|
|
109
|
+
rejectSymlinks?: boolean;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Local directory seed source: every `*.episode.json` file in `dir`,
|
|
113
|
+
* validated against `SeedEpisodeSchema`. Sorted by filename for
|
|
114
|
+
* deterministic plan/execute ordering (reproducible report files; the
|
|
115
|
+
* fixture-lint test pins this too). Files not matching the `.episode.json`
|
|
116
|
+
* suffix (e.g. the skill-shaped distractor fixtures) are ignored by
|
|
117
|
+
* construction — episodes and skills are separate lanes.
|
|
118
|
+
*/
|
|
119
|
+
export declare function createLocalEpisodeSeedSource(dir: string, options?: LocalEpisodeSeedSourceOptions): EpisodeSource;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Evidence-episode seed source (issue #1771, rescope plan §4.2-§4.4).
|
|
3
|
+
*
|
|
4
|
+
* Stage 1's rescoped retrieval serves evidence records, not skill records
|
|
5
|
+
* (docs/superpowers/plans/2026-07-16-jinn-plugin-stage-1-rescope-plan.md
|
|
6
|
+
* §3.1). The shared testnet corpus had almost no evidence to serve — skills.sh
|
|
7
|
+
* skill seeds only. This source reads a directory of hand-authored
|
|
8
|
+
* seed-episode JSON files (each transformed offline from a REAL completed
|
|
9
|
+
* session per the runbook, docs/runbooks/stage1-evidence-seeding.md) and
|
|
10
|
+
* feeds them through the SAME `capture() -> publish()` path the skill lane
|
|
11
|
+
* uses (`episode-execute.ts`). The resulting envelope remains identifiable
|
|
12
|
+
* as a seed on the wire through `provenance: 'imported'`, the seed importer
|
|
13
|
+
* harness/session identity, the `seed-import` tag, and `seed:*` steps. The
|
|
14
|
+
* selected scrub profile is a local implementation fact proved by code and
|
|
15
|
+
* tests; `TraceEnvelopeV0` does not publish a scrub-component manifest.
|
|
16
|
+
*
|
|
17
|
+
* Step convention (so a future retrieval-side packet projector — rescope
|
|
18
|
+
* plan §3.2 `projectKnowledgePacket`, R1/#1770 — has an obvious, discoverable
|
|
19
|
+
* shape to read): every content step's `attributes` carries
|
|
20
|
+
* 'seed.step.label' — one of the §3.2 excerpt labels
|
|
21
|
+
* ('failure' | 'fix' | 'command' | 'diff' | 'note')
|
|
22
|
+
* 'seed.step.title' — a short human title
|
|
23
|
+
* 'seed.step.text' — the excerpt content itself
|
|
24
|
+
* and a final step carries the episode-level metadata:
|
|
25
|
+
* 'seed.synthesis' — the authored how-it-was-solved text (§3.2 `synthesis`)
|
|
26
|
+
* 'seed.attribution' — { repo, baseCommit?, origin, sourceUrl?, supersedes? }
|
|
27
|
+
* No bespoke fixture format: this is exactly the frozen jinn.trace-envelope.v0
|
|
28
|
+
* step shape (spec §3.1), the same free-form-attribute convention the
|
|
29
|
+
* existing `seed:skill-md` skill-seed step already uses.
|
|
30
|
+
*/
|
|
31
|
+
import { lstatSync, readFileSync, readdirSync } from 'node:fs';
|
|
32
|
+
import { join } from 'node:path';
|
|
33
|
+
import { z } from 'zod';
|
|
34
|
+
import { OutcomeStatusSchema, VerifiabilityTierSchema } from '../envelope.js';
|
|
35
|
+
import { hashSeedContent } from './state.js';
|
|
36
|
+
/** Rescope plan §3.2 excerpt labels — the knowledge-packet projection's vocabulary. */
|
|
37
|
+
export const SEED_EPISODE_EXCERPT_LABELS = ['failure', 'fix', 'command', 'diff', 'note'];
|
|
38
|
+
export const SeedEpisodeExcerptLabelSchema = z.enum(SEED_EPISODE_EXCERPT_LABELS);
|
|
39
|
+
export const SeedEpisodeStepSchema = z.strictObject({
|
|
40
|
+
label: SeedEpisodeExcerptLabelSchema,
|
|
41
|
+
title: z.string().min(1),
|
|
42
|
+
text: z.string().min(1),
|
|
43
|
+
});
|
|
44
|
+
/**
|
|
45
|
+
* A seed-episode file — the canonical captured-evidence contract (issue
|
|
46
|
+
* #1771): task summary, ordered steps with real commands/outputs (a source
|
|
47
|
+
* episode carries at least one `failure` and its `fix`; distractors are
|
|
48
|
+
* freeform), outcome + verifiability tier, tags, attribution, and an
|
|
49
|
+
* authored synthesis. Strict: an unknown field is a malformed fixture, not
|
|
50
|
+
* silently tolerated.
|
|
51
|
+
*/
|
|
52
|
+
export const SeedEpisodeSchema = z.strictObject({
|
|
53
|
+
/** Stable seed identity — the idempotency key (state.ts) and filename stem. */
|
|
54
|
+
id: z.string().min(1),
|
|
55
|
+
/** `owner/repo` the episode re-performs work in/against. */
|
|
56
|
+
repo: z.string().min(1),
|
|
57
|
+
/** Full 40-char commit sha the episode was re-performed AT (pre-fix state), when applicable. */
|
|
58
|
+
baseCommit: z.string().regex(/^[0-9a-f]{40}$/, 'full 40-char commit sha').optional(),
|
|
59
|
+
taskSummary: z.string().min(1).max(500),
|
|
60
|
+
tags: z.array(z.string().min(1)).min(1),
|
|
61
|
+
steps: z.array(SeedEpisodeStepSchema).min(1),
|
|
62
|
+
outcome: z.strictObject({
|
|
63
|
+
status: OutcomeStatusSchema,
|
|
64
|
+
verifiabilityTier: VerifiabilityTierSchema,
|
|
65
|
+
}),
|
|
66
|
+
/** 3-6 sentence how-it-was-solved text (rescope plan §3.2 `synthesis`). */
|
|
67
|
+
synthesis: z.string().min(1),
|
|
68
|
+
attribution: z.strictObject({
|
|
69
|
+
/**
|
|
70
|
+
* How this episode entered the corpus — stated honestly. Vocabulary in
|
|
71
|
+
* use (freeform by schema; document new values in
|
|
72
|
+
* docs/runbooks/stage1-evidence-seeding.md):
|
|
73
|
+
* - 'operator-recorded-session' — transformed from a real recorded
|
|
74
|
+
* session; pair with `sourceUrl` when it re-performs a merged fix.
|
|
75
|
+
* - 'synthetic-selection-distractor' — hand-authored negative/contrast
|
|
76
|
+
* material, explicitly synthetic and paired with the unverified
|
|
77
|
+
* `user-accepted` tier (PR #1779 review).
|
|
78
|
+
*/
|
|
79
|
+
origin: z.string().min(1),
|
|
80
|
+
/** Link to the real merged fix, when the episode re-performs one. */
|
|
81
|
+
sourceUrl: z.string().min(1).optional(),
|
|
82
|
+
}),
|
|
83
|
+
});
|
|
84
|
+
/** Canonical identity and content approved by an episode report row. */
|
|
85
|
+
export function episodeContentDigest(episode) {
|
|
86
|
+
return hashSeedContent({
|
|
87
|
+
id: episode.id,
|
|
88
|
+
repo: episode.repo,
|
|
89
|
+
baseCommit: episode.baseCommit ?? null,
|
|
90
|
+
taskSummary: episode.taskSummary,
|
|
91
|
+
tags: episode.tags,
|
|
92
|
+
steps: episode.steps,
|
|
93
|
+
outcome: episode.outcome,
|
|
94
|
+
synthesis: episode.synthesis,
|
|
95
|
+
attribution: episode.attribution,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
/** Parse an untrusted value (a seed-episode JSON file) as a SeedEpisode; throws ZodError. */
|
|
99
|
+
export function parseSeedEpisode(input) {
|
|
100
|
+
return SeedEpisodeSchema.parse(input);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Local directory seed source: every `*.episode.json` file in `dir`,
|
|
104
|
+
* validated against `SeedEpisodeSchema`. Sorted by filename for
|
|
105
|
+
* deterministic plan/execute ordering (reproducible report files; the
|
|
106
|
+
* fixture-lint test pins this too). Files not matching the `.episode.json`
|
|
107
|
+
* suffix (e.g. the skill-shaped distractor fixtures) are ignored by
|
|
108
|
+
* construction — episodes and skills are separate lanes.
|
|
109
|
+
*/
|
|
110
|
+
export function createLocalEpisodeSeedSource(dir, options = {}) {
|
|
111
|
+
return {
|
|
112
|
+
name: `local-episodes:${dir}`,
|
|
113
|
+
async list() {
|
|
114
|
+
const files = readdirSync(dir)
|
|
115
|
+
.filter((file) => file.endsWith('.episode.json'))
|
|
116
|
+
.sort();
|
|
117
|
+
if (options.maxFiles !== undefined && files.length > options.maxFiles) {
|
|
118
|
+
throw new Error(`episode directory contains ${files.length} candidate files; maximum is ${options.maxFiles}`);
|
|
119
|
+
}
|
|
120
|
+
return files.map((file) => {
|
|
121
|
+
const path = join(dir, file);
|
|
122
|
+
const stats = lstatSync(path);
|
|
123
|
+
if (options.rejectSymlinks && stats.isSymbolicLink()) {
|
|
124
|
+
throw new Error(`episode candidate must not be a symbolic link: ${file}`);
|
|
125
|
+
}
|
|
126
|
+
if (options.rejectSymlinks && !stats.isFile()) {
|
|
127
|
+
throw new Error(`episode candidate must be a regular file: ${file}`);
|
|
128
|
+
}
|
|
129
|
+
if (options.maxFileBytes !== undefined &&
|
|
130
|
+
stats.size > options.maxFileBytes) {
|
|
131
|
+
throw new Error(`episode candidate ${file} exceeds ${options.maxFileBytes} byte limit`);
|
|
132
|
+
}
|
|
133
|
+
return parseSeedEpisode(JSON.parse(readFileSync(path, 'utf-8')));
|
|
134
|
+
});
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `planEpisodes()` — list + structural validation, ZERO writes (mirrors
|
|
3
|
+
* `plan.ts` for the evidence-episode source, issue #1771). Episodes are
|
|
4
|
+
* first-party content (no licence gate); the only structural failure this
|
|
5
|
+
* catches is a duplicate `id` within one batch — a same-identity collision
|
|
6
|
+
* would otherwise make idempotency (state.ts) apply to whichever file lists
|
|
7
|
+
* second, silently dropping the first from the report.
|
|
8
|
+
*/
|
|
9
|
+
import { type EpisodeSource } from './episode-fetch.js';
|
|
10
|
+
import type { EpisodeImportReport } from './episode-report.js';
|
|
11
|
+
export declare function planEpisodes(source: EpisodeSource): Promise<EpisodeImportReport>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `planEpisodes()` — list + structural validation, ZERO writes (mirrors
|
|
3
|
+
* `plan.ts` for the evidence-episode source, issue #1771). Episodes are
|
|
4
|
+
* first-party content (no licence gate); the only structural failure this
|
|
5
|
+
* catches is a duplicate `id` within one batch — a same-identity collision
|
|
6
|
+
* would otherwise make idempotency (state.ts) apply to whichever file lists
|
|
7
|
+
* second, silently dropping the first from the report.
|
|
8
|
+
*/
|
|
9
|
+
import { episodeContentDigest } from './episode-fetch.js';
|
|
10
|
+
export async function planEpisodes(source) {
|
|
11
|
+
const episodes = await source.list();
|
|
12
|
+
const seen = new Set();
|
|
13
|
+
return episodes.map((episode) => {
|
|
14
|
+
const duplicate = seen.has(episode.id);
|
|
15
|
+
seen.add(episode.id);
|
|
16
|
+
return {
|
|
17
|
+
id: episode.id,
|
|
18
|
+
taskSummary: episode.taskSummary,
|
|
19
|
+
tags: episode.tags,
|
|
20
|
+
contentDigest: episodeContentDigest(episode),
|
|
21
|
+
verdict: duplicate ? 'skip' : 'import',
|
|
22
|
+
reason: duplicate
|
|
23
|
+
? `duplicate id "${episode.id}" in source — first occurrence wins`
|
|
24
|
+
: 'first-party recorded episode',
|
|
25
|
+
};
|
|
26
|
+
});
|
|
27
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The EpisodeImportReport — the evidence-episode analogue of `report.ts`'s
|
|
3
|
+
* ImportReport (issue #1771). Episodes are first-party authored content (no
|
|
4
|
+
* upstream licence to gate on, unlike the skills.sh skill lane) so `verdict`
|
|
5
|
+
* here is structural, not licence-based: 'skip' only for a duplicate `id`
|
|
6
|
+
* within the same batch (see `episode-plan.ts`). `seed execute --episodes-dir`
|
|
7
|
+
* still reads this report as the approval-gate artifact — nothing publishes
|
|
8
|
+
* that was not on the list a human reviewed, and `contentDigest` binds each
|
|
9
|
+
* row to the exact canonical episode content present at planning time.
|
|
10
|
+
*/
|
|
11
|
+
import { z } from 'zod';
|
|
12
|
+
export declare const EpisodeImportReportRowSchema: z.ZodObject<{
|
|
13
|
+
id: z.ZodString;
|
|
14
|
+
taskSummary: z.ZodString;
|
|
15
|
+
tags: z.ZodArray<z.ZodString>;
|
|
16
|
+
contentDigest: z.ZodString;
|
|
17
|
+
verdict: z.ZodEnum<{
|
|
18
|
+
import: "import";
|
|
19
|
+
skip: "skip";
|
|
20
|
+
}>;
|
|
21
|
+
reason: z.ZodString;
|
|
22
|
+
}, z.core.$strict>;
|
|
23
|
+
export type EpisodeImportReportRow = z.infer<typeof EpisodeImportReportRowSchema>;
|
|
24
|
+
export declare const EpisodeImportReportSchema: z.ZodArray<z.ZodObject<{
|
|
25
|
+
id: z.ZodString;
|
|
26
|
+
taskSummary: z.ZodString;
|
|
27
|
+
tags: z.ZodArray<z.ZodString>;
|
|
28
|
+
contentDigest: z.ZodString;
|
|
29
|
+
verdict: z.ZodEnum<{
|
|
30
|
+
import: "import";
|
|
31
|
+
skip: "skip";
|
|
32
|
+
}>;
|
|
33
|
+
reason: z.ZodString;
|
|
34
|
+
}, z.core.$strict>>;
|
|
35
|
+
export type EpisodeImportReport = z.infer<typeof EpisodeImportReportSchema>;
|
|
36
|
+
/** Parse an untrusted value (e.g. an edited report file); throws ZodError. */
|
|
37
|
+
export declare function parseEpisodeImportReport(input: unknown): EpisodeImportReport;
|
|
38
|
+
/** Human-readable report table for the terminal. */
|
|
39
|
+
export declare function renderEpisodeImportReport(report: EpisodeImportReport): string;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The EpisodeImportReport — the evidence-episode analogue of `report.ts`'s
|
|
3
|
+
* ImportReport (issue #1771). Episodes are first-party authored content (no
|
|
4
|
+
* upstream licence to gate on, unlike the skills.sh skill lane) so `verdict`
|
|
5
|
+
* here is structural, not licence-based: 'skip' only for a duplicate `id`
|
|
6
|
+
* within the same batch (see `episode-plan.ts`). `seed execute --episodes-dir`
|
|
7
|
+
* still reads this report as the approval-gate artifact — nothing publishes
|
|
8
|
+
* that was not on the list a human reviewed, and `contentDigest` binds each
|
|
9
|
+
* row to the exact canonical episode content present at planning time.
|
|
10
|
+
*/
|
|
11
|
+
import { z } from 'zod';
|
|
12
|
+
export const EpisodeImportReportRowSchema = z.strictObject({
|
|
13
|
+
id: z.string().min(1),
|
|
14
|
+
taskSummary: z.string().min(1),
|
|
15
|
+
tags: z.array(z.string().min(1)),
|
|
16
|
+
/** SHA-256 over the canonical episode content the operator approved. */
|
|
17
|
+
contentDigest: z.string().regex(/^[0-9a-f]{64}$/),
|
|
18
|
+
verdict: z.enum(['import', 'skip']),
|
|
19
|
+
reason: z.string().min(1),
|
|
20
|
+
});
|
|
21
|
+
export const EpisodeImportReportSchema = z.array(EpisodeImportReportRowSchema);
|
|
22
|
+
/** Parse an untrusted value (e.g. an edited report file); throws ZodError. */
|
|
23
|
+
export function parseEpisodeImportReport(input) {
|
|
24
|
+
return EpisodeImportReportSchema.parse(input);
|
|
25
|
+
}
|
|
26
|
+
/** Human-readable report table for the terminal. */
|
|
27
|
+
export function renderEpisodeImportReport(report) {
|
|
28
|
+
if (report.length === 0)
|
|
29
|
+
return 'Empty report — no episodes listed.';
|
|
30
|
+
const lines = [`${report.length} episode(s)`, ''];
|
|
31
|
+
for (const row of report) {
|
|
32
|
+
lines.push(`${row.verdict === 'import' ? 'IMPORT' : 'skip '} ${row.id}`, ` summary ${row.taskSummary}`, ` tags ${row.tags.join(', ')}`, ` digest ${row.contentDigest}`, ` reason ${row.reason}`, '');
|
|
33
|
+
}
|
|
34
|
+
lines.pop();
|
|
35
|
+
return lines.join('\n');
|
|
36
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `execute()` — publish the approved import rows through the Task-4 path
|
|
3
|
+
* (plan Task 6, spec §7).
|
|
4
|
+
*
|
|
5
|
+
* Each imported skill becomes a synthetic captured task carrying the
|
|
6
|
+
* SKILL.md as its single step, runs through the SAME mandatory scrub
|
|
7
|
+
* (`capture()`) and the SAME publish/anchor path (`publish()`) as a real
|
|
8
|
+
* contribution — one publish path, per the 2026-07-02 schema review — with
|
|
9
|
+
* `provenance: 'imported'` and attribution metadata in the step attributes.
|
|
10
|
+
* Seeds are thereby excluded from the demand signal and from emissions
|
|
11
|
+
* eligibility by every provenance-aware reader.
|
|
12
|
+
*
|
|
13
|
+
* The licence gate is structural, not advisory: execute() re-checks each
|
|
14
|
+
* row's licence from the freshly-fetched source. A verdict hand-edited to
|
|
15
|
+
* `import` over a non-allowlisted licence is refused — widening the gate is
|
|
16
|
+
* a code change to the disclosed allowlist, reviewable in a PR, not a
|
|
17
|
+
* report-file edit.
|
|
18
|
+
*
|
|
19
|
+
* Idempotent by seed identity (issue #1771, `state.ts`): re-running execute
|
|
20
|
+
* over an unchanged skill publishes nothing new (a `skipped` row instead);
|
|
21
|
+
* re-running over a CHANGED skill (different skillMd/description/licence/
|
|
22
|
+
* source) republishes and sets the new record's
|
|
23
|
+
* `provenance.supersedes` to the prior envelopeRef. `opts.state` defaults to
|
|
24
|
+
* a fresh in-memory store per call, so existing callers that never pass one
|
|
25
|
+
* see unchanged behaviour (every row always publishes) — the CLI wires a
|
|
26
|
+
* file-backed store for real runs (cli.ts, `seed execute`).
|
|
27
|
+
*/
|
|
28
|
+
import { type HarnessPublishDeps } from '../publish.js';
|
|
29
|
+
import type { SeedSource } from './fetch.js';
|
|
30
|
+
import type { ImportReport } from './report.js';
|
|
31
|
+
import { type SeedImportStateStore } from './state.js';
|
|
32
|
+
export interface ImportResult {
|
|
33
|
+
imported: Array<{
|
|
34
|
+
skill: string;
|
|
35
|
+
envelopeRef: string;
|
|
36
|
+
anchorTx: string | null;
|
|
37
|
+
/** Publication succeeded, but local lineage state needs operator recovery. */
|
|
38
|
+
stateWarning?: string;
|
|
39
|
+
/** Anchor succeeded, but the local publication ledger needs recovery. */
|
|
40
|
+
ledgerWarning?: string;
|
|
41
|
+
}>;
|
|
42
|
+
skipped: Array<{
|
|
43
|
+
skill: string;
|
|
44
|
+
reason: string;
|
|
45
|
+
}>;
|
|
46
|
+
errors: Array<{
|
|
47
|
+
skill: string;
|
|
48
|
+
error: string;
|
|
49
|
+
}>;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Extract `name:` from a SKILL.md YAML frontmatter block, if present.
|
|
53
|
+
* Deliberately a line-parser, not a YAML dependency — skill frontmatter
|
|
54
|
+
* names are plain scalars; anything more exotic simply yields no tag.
|
|
55
|
+
*/
|
|
56
|
+
export declare function frontmatterName(skillMd: string): string | null;
|
|
57
|
+
/** Last path segment of a registry-style skill id (`owner/repo/slug`). */
|
|
58
|
+
export declare function skillSlug(skillId: string): string;
|
|
59
|
+
export declare function execute(report: ImportReport, source: SeedSource, deps: HarnessPublishDeps, opts?: {
|
|
60
|
+
state?: SeedImportStateStore;
|
|
61
|
+
}): Promise<ImportResult>;
|