@dtmd/temper 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,57 @@
1
+ # temper-sdk — the authoring face
2
+
3
+ The typed module library the ratified corpus names as temper's authoring
4
+ medium (`specs/intent/00-intent.md`, the SDK Decision; `specs/architecture/20-surface.md`,
5
+ "The seam — one implementation"). A harness author composes members as typed
6
+ values in the **six-noun model**; `emit` compiles the whole into the declaration
7
+ rows the engine reads, a byte-faithful `.claude/**` projection, and the lock. The
8
+ SDK implements **no semantics** — every type erases at the seam, and the engine
9
+ consumes only declared data, offline, no Node.
10
+
11
+ ## The six-noun face
12
+
13
+ - **`harness()`** — the assembly as one typed value: `members · expect ·
14
+ require · settings · reachability` (`specs/architecture/40-composition.md`).
15
+ - **`kind<T>()` / `genre<T>()`** — the engine room: a kind is a typed
16
+ constructor plus five facts of runtime residue (label, locus, layout,
17
+ registration, edge fields — `specs/architecture/15-kinds.md`). The built-in
18
+ Claude Code kinds `rule` / `skill` / `memory` are ordinary `kind<T>()` values.
19
+ - **Clause values** — `clause(predicate, { severity, guidance, cite })` over the
20
+ closed predicate algebra (`required`, `maxLines`, …); a floor is an exported
21
+ clause array, adopted by spread in `expect` (`specs/architecture/10-contracts.md`).
22
+ - **`needs`** — the capabilities a member uses (`bash("git diff")`); emit derives
23
+ the permission union, so a permission is never authored twice.
24
+ - **`file()` / `` text`…` `` / `blocks()`** — the three prose constructors, one
25
+ field type; the author's words land byte-identical to their authored text.
26
+
27
+ ## What `emit` produces
28
+
29
+ One deterministic pass over the harness, double-emit verified (law 5):
30
+
31
+ - **Declaration rows** — the erased program (kind facts, clauses, requirements,
32
+ assembly facts) on the internal versioned JSON pipe and in the lock's
33
+ `[declaration]` families, byte-matching the Rust lock shape (`src/drift.rs`) —
34
+ the byte-parity lockstep two writers keep until single-writer lands.
35
+ - **A byte-faithful projection** — each `rule` / `skill` / `memory` member
36
+ compiled whole to its `.claude/**` locus; install's placement lines round-trip.
37
+ - **The lock** — rollup provenance/emit fingerprints plus the declaration rows.
38
+
39
+ Emit is **total** (members are the only source), **refuses** before it writes on
40
+ a broken source (a dangling `satisfies`, an unfilled `required`, an unresolved
41
+ mention), and is **byte-reproducible**. `writeEmit` lands the lock and the
42
+ projection on disk; the JSON pipe is in-flight, not a committed artifact.
43
+
44
+ ## Stated bounds — each a named follow-on, never silently faked
45
+
46
+ - **`blocks()` composes now, renders later** — the shared genre fence format is
47
+ `(genre-fence-format)`, deferred until its first consumer; emit refuses to
48
+ project a `blocks()` body until then.
49
+ - **The permission union is carried as data** — the fold into the settings
50
+ artifact lands with the hook/MCP kinds it folds many-to-one.
51
+ - **Publish name/scope** pending the PACKAGING-CHANNELS ruling; `private: true`.
52
+
53
+ ## Tests
54
+
55
+ `pnpm --dir sdk test` — `tsc` (the keystroke wall) then `node --test`, including
56
+ projection byte-parity and lock fingerprints against real Rust output, and the
57
+ declaration-row byte shape against the Rust `[declaration]` families.
@@ -0,0 +1,45 @@
1
+ /**
2
+ * The assembly — `harness()` takes the whole as one typed value
3
+ * (`specs/architecture/40-composition.md`): `Harness = members · expect · require ·
4
+ * settings · reachability`. Like every SDK type it erases at the seam — the
5
+ * engine never sees the constructor, only the declaration rows it compiles to
6
+ * (`20-surface.md`). There is no second authoring surface: no `temper.toml`, no
7
+ * roster/bindings dialect (the Decision rejects both); composing partial
8
+ * harnesses is ordinary code.
9
+ */
10
+ import type { Member, KindDefinition } from "./kind.js";
11
+ import type { Clause, Requirement, Severity } from "./contract.js";
12
+ /**
13
+ * One `expect` binding — universal: every member of `kind` owes these clauses
14
+ * (`40-composition.md`). Keyed by the kind **value**, an import never a string;
15
+ * binding is implicit — a floor is just a clause array spread into `clauses`.
16
+ */
17
+ export interface ExpectBinding {
18
+ readonly kind: KindDefinition<object>;
19
+ readonly clauses: readonly Clause[];
20
+ }
21
+ /** The composed harness — the five fields, erased to rows at the seam. */
22
+ export interface Harness {
23
+ /** The member roster — the assembly's imports (`40-composition.md`, "`members`"). */
24
+ readonly members: readonly Member[];
25
+ /** Universal clause bindings, keyed by kind value. */
26
+ readonly expect: readonly ExpectBinding[];
27
+ /** Existential obligations the harness must contain a fill for, keyed by name. */
28
+ readonly require: Readonly<Record<string, Requirement>>;
29
+ /** The residual harness-level settings with no member home (a shrinking list). */
30
+ readonly settings: Readonly<Record<string, unknown>>;
31
+ /** The dead-registration dial — the severity of an unreachable member, or absent (no findings). */
32
+ readonly reachability?: Severity;
33
+ }
34
+ /**
35
+ * Compose the harness from its five fields — ordinary code, Turing-completeness
36
+ * quarantined at authoring time (`00-intent.md`, the SDK Decision). Absent
37
+ * fields default empty; the member list is the only required part.
38
+ */
39
+ export declare function harness(init: {
40
+ members: readonly Member[];
41
+ expect?: readonly ExpectBinding[];
42
+ require?: Readonly<Record<string, Requirement>>;
43
+ settings?: Readonly<Record<string, unknown>>;
44
+ reachability?: Severity;
45
+ }): Harness;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * The assembly — `harness()` takes the whole as one typed value
3
+ * (`specs/architecture/40-composition.md`): `Harness = members · expect · require ·
4
+ * settings · reachability`. Like every SDK type it erases at the seam — the
5
+ * engine never sees the constructor, only the declaration rows it compiles to
6
+ * (`20-surface.md`). There is no second authoring surface: no `temper.toml`, no
7
+ * roster/bindings dialect (the Decision rejects both); composing partial
8
+ * harnesses is ordinary code.
9
+ */
10
+ /**
11
+ * Compose the harness from its five fields — ordinary code, Turing-completeness
12
+ * quarantined at authoring time (`00-intent.md`, the SDK Decision). Absent
13
+ * fields default empty; the member list is the only required part.
14
+ */
15
+ export function harness(init) {
16
+ return {
17
+ members: init.members,
18
+ expect: init.expect ?? [],
19
+ require: init.require ?? {},
20
+ settings: init.settings ?? {},
21
+ reachability: init.reachability,
22
+ };
23
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * The built-in Claude Code kinds — the face nouns a harness author imports
3
+ * (`specs/architecture/15-kinds.md`, "Built-in and custom kinds"). Each is an
4
+ * ordinary `kind<T>()` value built with the same constructor every provider uses
5
+ * (ownership not privilege). Their five facts are external facts about the Claude
6
+ * Code harness, cited at the point of claim.
7
+ *
8
+ * In the shipped product these live in the published `@temper/claude-code` module
9
+ * (`50-distribution.md`); here they are the SDK's own exports, the first dogfood.
10
+ */
11
+ import type { KindDefinition } from "./kind.js";
12
+ import type { Prose } from "./prose.js";
13
+ /** A Claude Code skill — a directory whose entry file is `SKILL.md` with YAML frontmatter. */
14
+ export interface Skill {
15
+ /**
16
+ * The description trigger — always in context; the body loads on invocation
17
+ * (code.claude.com/docs/en/skills, retrieved 2026-07-02).
18
+ */
19
+ readonly description: string;
20
+ /** The optional license field the skill spec carries (agentskills.io/specification). */
21
+ readonly license?: string;
22
+ readonly prose?: Prose;
23
+ }
24
+ /**
25
+ * `skill` — `.claude/skills/<name>/SKILL.md`, a directory unit, YAML frontmatter
26
+ * carrying `name` then `description`; registers a description trigger
27
+ * (code.claude.com/docs/en/skills, agentskills.io/specification, retrieved
28
+ * 2026-07-02).
29
+ */
30
+ export declare const skill: KindDefinition<Skill>;
31
+ /** A Claude Code rule — a flat markdown file with an optional `paths` scope. */
32
+ export interface Rule {
33
+ /**
34
+ * The path scope — a present list matching zero files is a dead edge; an absent
35
+ * one loads unconditionally (code.claude.com/docs/en/memory, retrieved 2026-07-02).
36
+ */
37
+ readonly paths?: readonly string[];
38
+ readonly prose?: Prose;
39
+ }
40
+ /**
41
+ * `rule` — `.claude/rules/<name>.md`, a lone file (identity from the stem), YAML
42
+ * frontmatter; registers a path scope (code.claude.com/docs/en/memory, retrieved
43
+ * 2026-07-02).
44
+ */
45
+ export declare const rule: KindDefinition<Rule>;
46
+ /** A Claude Code memory file — `CLAUDE.md`, loaded in full at launch, no frontmatter. */
47
+ export interface Memory {
48
+ readonly prose?: Prose;
49
+ }
50
+ /**
51
+ * `memory` — a root `<name>.md` (`CLAUDE.md`, `AGENTS.md`), a lone file loaded
52
+ * unconditionally, with **no frontmatter** (code.claude.com/docs/en/memory,
53
+ * retrieved 2026-07-02): the whole file is the body, so the kind declares no
54
+ * `format`. Its discovery locus is any-depth (a `CLAUDE.md` at any directory);
55
+ * a module-carried memory projects the root file.
56
+ */
57
+ export declare const memory: KindDefinition<Memory>;
@@ -0,0 +1,50 @@
1
+ /**
2
+ * The built-in Claude Code kinds — the face nouns a harness author imports
3
+ * (`specs/architecture/15-kinds.md`, "Built-in and custom kinds"). Each is an
4
+ * ordinary `kind<T>()` value built with the same constructor every provider uses
5
+ * (ownership not privilege). Their five facts are external facts about the Claude
6
+ * Code harness, cited at the point of claim.
7
+ *
8
+ * In the shipped product these live in the published `@temper/claude-code` module
9
+ * (`50-distribution.md`); here they are the SDK's own exports, the first dogfood.
10
+ */
11
+ import { kind } from "./kind.js";
12
+ /**
13
+ * `skill` — `.claude/skills/<name>/SKILL.md`, a directory unit, YAML frontmatter
14
+ * carrying `name` then `description`; registers a description trigger
15
+ * (code.claude.com/docs/en/skills, agentskills.io/specification, retrieved
16
+ * 2026-07-02).
17
+ */
18
+ export const skill = kind({
19
+ name: "skill",
20
+ locus: { kind: "at", root: ".claude/skills", glob: "*/SKILL.md" },
21
+ format: "yaml-frontmatter",
22
+ unitShape: "directory",
23
+ registration: { via: "description-trigger", field: "description" },
24
+ identityField: "name",
25
+ });
26
+ /**
27
+ * `rule` — `.claude/rules/<name>.md`, a lone file (identity from the stem), YAML
28
+ * frontmatter; registers a path scope (code.claude.com/docs/en/memory, retrieved
29
+ * 2026-07-02).
30
+ */
31
+ export const rule = kind({
32
+ name: "rule",
33
+ locus: { kind: "at", root: ".claude/rules", glob: "*.md" },
34
+ format: "yaml-frontmatter",
35
+ unitShape: "file",
36
+ registration: { via: "paths-match", field: "paths" },
37
+ });
38
+ /**
39
+ * `memory` — a root `<name>.md` (`CLAUDE.md`, `AGENTS.md`), a lone file loaded
40
+ * unconditionally, with **no frontmatter** (code.claude.com/docs/en/memory,
41
+ * retrieved 2026-07-02): the whole file is the body, so the kind declares no
42
+ * `format`. Its discovery locus is any-depth (a `CLAUDE.md` at any directory);
43
+ * a module-carried memory projects the root file.
44
+ */
45
+ export const memory = kind({
46
+ name: "memory",
47
+ locus: { kind: "at", root: ".", glob: "**/CLAUDE.md" },
48
+ unitShape: "file",
49
+ registration: { via: "always" },
50
+ });
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Contracts — clauses and requirements as typed values (`specs/architecture/10-contracts.md`).
3
+ * A clause is `predicate · severity · guidance · cite`; a requirement is
4
+ * `means · kind · required · count? · unique? · membership? · degree? · verifiedBy?`.
5
+ * Both erase to compiled data at the seam (`20-surface.md`): the author composes
6
+ * typed objects, the engine consumes their rows. The predicate vocabulary is the
7
+ * closed algebra — a clause outside it is a squiggle, not a runtime rejection
8
+ * (`10-contracts.md`, the two walls).
9
+ */
10
+ import type { KindDefinition } from "./kind.js";
11
+ /** A clause's delivery posture: `required` gate-blocks, `advisory` reports. */
12
+ export type Severity = "required" | "advisory";
13
+ /**
14
+ * A member of the closed predicate algebra (`10-contracts.md`). The compiled
15
+ * clause row records only `key`, the targeted `field` (when it names one), and
16
+ * severity — the reduced shape the lock and the JSON pipe both carry; a
17
+ * predicate's scalar bounds (`args`) stay author-side until the fuller
18
+ * interchange lands its consumer (the entry gate, `20-surface.md`).
19
+ */
20
+ export interface Predicate {
21
+ /** The predicate's clause key (`required`, `max_len`, `max_lines`, …). */
22
+ readonly key: string;
23
+ /** The field (or marker) the predicate constrains, when it names one. */
24
+ readonly field?: string;
25
+ /** The predicate's scalar bounds — author-side; not yet in the erased row. */
26
+ readonly args?: Readonly<Record<string, number>>;
27
+ }
28
+ /** A field or marker is present. */
29
+ export declare const required: (field: string) => Predicate;
30
+ /** The field's parsed scalar type is as declared. */
31
+ export declare const type: (field: string) => Predicate;
32
+ /** The field's value is at least `n` characters. */
33
+ export declare const minLen: (field: string, n: number) => Predicate;
34
+ /** The field's value is at most `n` characters. */
35
+ export declare const maxLen: (field: string, n: number) => Predicate;
36
+ /** The field's characters are drawn from a declared class (`allowed_chars`). */
37
+ export declare const allowedChars: (field: string) => Predicate;
38
+ /** The member's body is at most `n` lines. */
39
+ export declare const maxLines: (n: number) => Predicate;
40
+ /** The forbidden keys (e.g. the Cursor `globs`/`alwaysApply` keys) are absent. */
41
+ export declare const forbiddenKeys: () => Predicate;
42
+ /** The named headings are present. */
43
+ export declare const requireSections: () => Predicate;
44
+ /** The member's name matches its directory. */
45
+ export declare const nameMatchesDir: () => Predicate;
46
+ /**
47
+ * A clause — a predicate the author marks with a severity, the just-in-time
48
+ * guidance the predicate cannot encode, and the external-fact `cite` that makes
49
+ * a maintained floor auditable (`10-contracts.md`, "The clause").
50
+ */
51
+ export interface Clause {
52
+ readonly predicate: Predicate;
53
+ readonly severity: Severity;
54
+ readonly guidance?: string;
55
+ readonly cite?: string;
56
+ }
57
+ /** Compose a clause value — a predicate under a declared severity, with optional guidance/cite. */
58
+ export declare function clause(predicate: Predicate, opts: {
59
+ severity: Severity;
60
+ guidance?: string;
61
+ cite?: string;
62
+ }): Clause;
63
+ /**
64
+ * A requirement — a named obligation on the harness (`10-contracts.md`,
65
+ * "Requirements"). `means` is the authored intent, carried never interpreted;
66
+ * `kind` constrains what may fill it **by import** (a value, never a string);
67
+ * `required` is the posture declaration; the set-scope facets measure the
68
+ * satisfier set; `verifiedBy` wires the behavioral remainder.
69
+ */
70
+ export interface Requirement {
71
+ readonly means: string;
72
+ readonly kind?: KindDefinition<object>;
73
+ readonly required?: boolean;
74
+ readonly count?: {
75
+ readonly min?: number;
76
+ readonly max?: number;
77
+ };
78
+ readonly unique?: string;
79
+ readonly membership?: string;
80
+ readonly degree?: string;
81
+ readonly verifiedBy?: string;
82
+ }
83
+ /** An identity helper — types a requirement literal at the keystroke (`40-composition.md`). */
84
+ export declare function requirement(init: Requirement): Requirement;
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Contracts — clauses and requirements as typed values (`specs/architecture/10-contracts.md`).
3
+ * A clause is `predicate · severity · guidance · cite`; a requirement is
4
+ * `means · kind · required · count? · unique? · membership? · degree? · verifiedBy?`.
5
+ * Both erase to compiled data at the seam (`20-surface.md`): the author composes
6
+ * typed objects, the engine consumes their rows. The predicate vocabulary is the
7
+ * closed algebra — a clause outside it is a squiggle, not a runtime rejection
8
+ * (`10-contracts.md`, the two walls).
9
+ */
10
+ // Node-scope predicates (`10-contracts.md`, "The predicate algebra").
11
+ /** A field or marker is present. */
12
+ export const required = (field) => ({ key: "required", field });
13
+ /** The field's parsed scalar type is as declared. */
14
+ export const type = (field) => ({ key: "type", field });
15
+ /** The field's value is at least `n` characters. */
16
+ export const minLen = (field, n) => ({ key: "min_len", field, args: { min: n } });
17
+ /** The field's value is at most `n` characters. */
18
+ export const maxLen = (field, n) => ({ key: "max_len", field, args: { max: n } });
19
+ /** The field's characters are drawn from a declared class (`allowed_chars`). */
20
+ export const allowedChars = (field) => ({ key: "allowed_chars", field });
21
+ /** The member's body is at most `n` lines. */
22
+ export const maxLines = (n) => ({ key: "max_lines", args: { max: n } });
23
+ /** The forbidden keys (e.g. the Cursor `globs`/`alwaysApply` keys) are absent. */
24
+ export const forbiddenKeys = () => ({ key: "forbidden_keys" });
25
+ /** The named headings are present. */
26
+ export const requireSections = () => ({ key: "require_sections" });
27
+ /** The member's name matches its directory. */
28
+ export const nameMatchesDir = () => ({ key: "name-matches-dir" });
29
+ /** Compose a clause value — a predicate under a declared severity, with optional guidance/cite. */
30
+ export function clause(predicate, opts) {
31
+ return { predicate, severity: opts.severity, guidance: opts.guidance, cite: opts.cite };
32
+ }
33
+ /** An identity helper — types a requirement literal at the keystroke (`40-composition.md`). */
34
+ export function requirement(init) {
35
+ return init;
36
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Declaration rows — the composed program's erased declarations
3
+ * (`specs/architecture/20-surface.md`, "The seam — one implementation"; "The lock
4
+ * and drift"). Every type erases at the seam: kinds, clauses, requirements, and
5
+ * assembly facts compile to plain rows the engine reads. The **row shape** matches
6
+ * the Rust lock's `[declaration]` families (`src/drift.rs` `Declarations`) — the
7
+ * byte-parity lockstep two writers keep until single-writer lands
8
+ * (`SDK-RECUT-CORPUS-FACE`). The same rows ride the internal versioned JSON pipe
9
+ * ({@link declarationsToJson}) — not a designed IR, versioned in lockstep.
10
+ */
11
+ import type { Harness } from "./assembly.js";
12
+ /** One kind's declaration row — its identity and declared runtime facts. */
13
+ export interface KindFactRow {
14
+ readonly name: string;
15
+ readonly provider?: string;
16
+ readonly governs_root: string;
17
+ readonly governs_glob: string;
18
+ readonly format?: string;
19
+ readonly unit_shape?: string;
20
+ readonly activation?: string;
21
+ }
22
+ /** One clause of a kind's effective contract, reduced to the lock's columns. */
23
+ export interface ClauseRow {
24
+ readonly kind: string;
25
+ readonly predicate: string;
26
+ readonly field?: string;
27
+ readonly severity: string;
28
+ }
29
+ /** One named requirement's declaration row, reduced to the scalar facets the lock records. */
30
+ export interface RequirementRow {
31
+ readonly name: string;
32
+ readonly kind?: string;
33
+ readonly package?: string;
34
+ readonly required: boolean;
35
+ readonly verified_by?: string;
36
+ }
37
+ /** One assembly-scope fact — authority, reachability, or an edge. */
38
+ export interface AssemblyFactRow {
39
+ readonly fact: string;
40
+ readonly value?: string;
41
+ readonly from?: string;
42
+ readonly field?: string;
43
+ readonly to?: string;
44
+ }
45
+ /** The four declaration families — the whole erased program the lock and pipe carry. */
46
+ export interface Declarations {
47
+ readonly kinds: readonly KindFactRow[];
48
+ readonly clauses: readonly ClauseRow[];
49
+ readonly requirements: readonly RequirementRow[];
50
+ readonly assembly: readonly AssemblyFactRow[];
51
+ }
52
+ /** Compile a harness into its four declaration families — the erased program. */
53
+ export declare function compileDeclarations(harness: Harness): Declarations;
54
+ /** The SDK's pinned engine/interchange version — the JSON pipe rides it in lockstep. */
55
+ export declare const SEAM_VERSION = 1;
56
+ /**
57
+ * Serialize the declaration rows to the internal versioned JSON pipe
58
+ * (`20-surface.md`, "The seam"). Not a designed IR — a stable public interchange
59
+ * is admitted only when its consumer lands. Deterministic: insertion-ordered keys
60
+ * and a trailing newline, so a re-emit is byte-identical (law 5).
61
+ */
62
+ export declare function declarationsToJson(declarations: Declarations): string;
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Declaration rows — the composed program's erased declarations
3
+ * (`specs/architecture/20-surface.md`, "The seam — one implementation"; "The lock
4
+ * and drift"). Every type erases at the seam: kinds, clauses, requirements, and
5
+ * assembly facts compile to plain rows the engine reads. The **row shape** matches
6
+ * the Rust lock's `[declaration]` families (`src/drift.rs` `Declarations`) — the
7
+ * byte-parity lockstep two writers keep until single-writer lands
8
+ * (`SDK-RECUT-CORPUS-FACE`). The same rows ride the internal versioned JSON pipe
9
+ * ({@link declarationsToJson}) — not a designed IR, versioned in lockstep.
10
+ */
11
+ /** The lock label for a kind's declared registration — the activation spelling. */
12
+ function activationLabel(registration) {
13
+ switch (registration.via) {
14
+ case "always":
15
+ return "always";
16
+ case "description-trigger":
17
+ return `description-trigger(${registration.field})`;
18
+ case "paths-match":
19
+ return `paths-match(${registration.field})`;
20
+ case "event":
21
+ return `event(${registration.field})`;
22
+ case "connection":
23
+ return "connection";
24
+ }
25
+ }
26
+ /** One kind's fact row — the `at` locus supplies `governs_root`/`governs_glob`. */
27
+ function kindFactRow(facts) {
28
+ if (facts.locus.kind !== "at") {
29
+ // A genre inherits its world residue through its host; it carries no `at`
30
+ // locus, so it takes no kind-fact row (`15-kinds.md`). Callers filter these
31
+ // out before this point.
32
+ throw new Error(`kind \`${facts.name}\` is a genre — it carries no locus-bearing kind fact.`);
33
+ }
34
+ return {
35
+ name: facts.name,
36
+ provider: facts.provider,
37
+ governs_root: facts.locus.root,
38
+ governs_glob: facts.locus.glob,
39
+ format: facts.format,
40
+ unit_shape: facts.unitShape,
41
+ activation: activationLabel(facts.registration),
42
+ };
43
+ }
44
+ /** The distinct locus-bearing kinds in play — member kinds ∪ expect kinds, name-sorted. */
45
+ function kindsInPlay(harness) {
46
+ const byName = new Map();
47
+ for (const member of harness.members)
48
+ byName.set(member.facts.name, member.facts);
49
+ for (const binding of harness.expect)
50
+ byName.set(binding.kind.facts.name, binding.kind.facts);
51
+ return [...byName.values()]
52
+ .filter((facts) => facts.locus.kind === "at")
53
+ .sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
54
+ }
55
+ /** The requirement rows — assembly `require` and every member's `requires`, one namespace. */
56
+ function requirementRows(harness) {
57
+ const merged = new Map();
58
+ const publish = (name, requirement, source) => {
59
+ const existing = merged.get(name);
60
+ if (existing !== undefined && existing !== requirement) {
61
+ // One namespace, one fill mechanism; a cross-publisher name collision is an
62
+ // admissibility finding, never a shadowing rule (`10-contracts.md`).
63
+ throw new Error(`requirement \`${name}\` is published twice (${source} collides with an earlier ` +
64
+ `publisher) — a name collision across publishers is an admissibility finding.`);
65
+ }
66
+ merged.set(name, requirement);
67
+ };
68
+ for (const [name, requirement] of Object.entries(harness.require))
69
+ publish(name, requirement, "the assembly");
70
+ for (const member of harness.members) {
71
+ for (const [name, requirement] of Object.entries(member.requires)) {
72
+ publish(name, requirement, `member \`${member.name}\``);
73
+ }
74
+ }
75
+ return [...merged.entries()]
76
+ .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
77
+ .map(([name, requirement]) => ({
78
+ name,
79
+ kind: requirement.kind?.key,
80
+ required: requirement.required ?? false,
81
+ verified_by: requirement.verifiedBy,
82
+ }));
83
+ }
84
+ /**
85
+ * The assembly-scope facts, in a stable order: authority (always declared — the
86
+ * `shared` default anchors every harness until a surface-authority posture is
87
+ * authored), reachability when the dial is set, then one edge row per kind edge
88
+ * field (`40-composition.md`; `45-governance.md`).
89
+ */
90
+ function assemblyFactRows(harness, kinds) {
91
+ const facts = [{ fact: "authority", value: "shared" }];
92
+ if (harness.reachability !== undefined) {
93
+ facts.push({ fact: "reachability", value: harness.reachability });
94
+ }
95
+ for (const kind of kinds) {
96
+ for (const edge of kind.edgeFields ?? []) {
97
+ facts.push({ fact: "edge", from: kind.name, field: edge.field, to: edge.to });
98
+ }
99
+ }
100
+ return facts;
101
+ }
102
+ /** Compile a harness into its four declaration families — the erased program. */
103
+ export function compileDeclarations(harness) {
104
+ const kinds = kindsInPlay(harness);
105
+ const clauses = [];
106
+ for (const binding of [...harness.expect].sort((a, b) => (a.kind.key < b.kind.key ? -1 : a.kind.key > b.kind.key ? 1 : 0))) {
107
+ for (const clause of binding.clauses) {
108
+ clauses.push({
109
+ kind: binding.kind.key,
110
+ predicate: clause.predicate.key,
111
+ field: clause.predicate.field,
112
+ severity: clause.severity,
113
+ });
114
+ }
115
+ }
116
+ return {
117
+ kinds: kinds.map(kindFactRow),
118
+ clauses,
119
+ requirements: requirementRows(harness),
120
+ assembly: assemblyFactRows(harness, kinds),
121
+ };
122
+ }
123
+ /** The SDK's pinned engine/interchange version — the JSON pipe rides it in lockstep. */
124
+ export const SEAM_VERSION = 1;
125
+ /**
126
+ * Serialize the declaration rows to the internal versioned JSON pipe
127
+ * (`20-surface.md`, "The seam"). Not a designed IR — a stable public interchange
128
+ * is admitted only when its consumer lands. Deterministic: insertion-ordered keys
129
+ * and a trailing newline, so a re-emit is byte-identical (law 5).
130
+ */
131
+ export function declarationsToJson(declarations) {
132
+ return JSON.stringify({ version: SEAM_VERSION, ...declarations }, null, 2) + "\n";
133
+ }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Emit — the compile from the six-noun face to the committed seam
3
+ * (`specs/architecture/20-surface.md`, "Emit — total, byte-reproducible, refusing";
4
+ * "The seam — one implementation"). The SDK implements **no semantics**: emit
5
+ * produces plain data — the declaration rows the engine reads (the internal
6
+ * versioned JSON pipe and the lock's `[declaration]` families), a byte-faithful
7
+ * `.claude/**` projection, and the lock. Emit is total (members are the only
8
+ * source), refuses before it writes a byte on a broken source, and is
9
+ * byte-reproducible — double-emit verified at every run (law 5).
10
+ */
11
+ import type { Harness } from "./assembly.js";
12
+ import type { Projection } from "./project.js";
13
+ import type { Declarations } from "./declarations.js";
14
+ /** How a `file()` asset's module-relative path resolves at emit. */
15
+ export interface ResolveOptions {
16
+ /** Base dir a `file()` module-relative path resolves against (default: cwd). */
17
+ readonly baseDir?: string;
18
+ /** The addresses a mention may name — resolution-checked; a mention cannot dangle. */
19
+ readonly mentionable?: ReadonlySet<string>;
20
+ }
21
+ /** Emit-time inputs beyond the harness — asset base dir and the committed-projection read root. */
22
+ export interface EmitOptions {
23
+ /** Base dir a `file()` asset's module-relative path resolves against (default: cwd). */
24
+ readonly baseDir?: string;
25
+ /**
26
+ * The harness root the committed projection is read from so a re-emit carries
27
+ * install's placement lines through the whole-file re-emit (`20-surface.md`, the
28
+ * two-projectors seam). Absent reads no committed projection; [`writeEmit`]
29
+ * passes its `targetDir` here so a re-emit preserves them.
30
+ */
31
+ readonly projectionDir?: string;
32
+ }
33
+ /**
34
+ * A full emit's compiled outputs — the seam the engine reads plus the on-disk
35
+ * projection and lock (`20-surface.md`, "The seam"). All are a pure function of
36
+ * the harness, so [`emit`] double-verifies them and [`writeEmit`] lands them.
37
+ */
38
+ export interface EmitResult {
39
+ /** The projection files, one per projected (rule/skill/memory) member. */
40
+ readonly projections: readonly Projection[];
41
+ /** The `lock.toml` bytes — rollup rows plus the `[declaration]` families. */
42
+ readonly lock: string;
43
+ /** The declaration rows — the erased program the lock and JSON pipe both carry. */
44
+ readonly declarations: Declarations;
45
+ /** The internal versioned JSON pipe to the engine — not a designed IR. */
46
+ readonly seam: string;
47
+ /**
48
+ * The derived permission list — the union of every member's `needs`, deduped and
49
+ * sorted (`20-surface.md`, "The permission list is derived, never authored").
50
+ * Folds into the settings artifact once hook/MCP members land; carried here as
51
+ * data until then.
52
+ */
53
+ readonly permissions: readonly string[];
54
+ }
55
+ /**
56
+ * Compile the whole face in one deterministic pass: the projection, the lock (its
57
+ * rollup and its declaration rows), the JSON pipe, and the derived permission
58
+ * union. Prose resolves once (`file()` assets read in, mentions resolution-checked
59
+ * against the harness's declared values). Double-emit verified — nondeterministic
60
+ * authoring is a loud failure, never a silent churn (law 5).
61
+ */
62
+ export declare function emit(harness: Harness, options?: EmitOptions): EmitResult;
63
+ /**
64
+ * Run a full [`emit`] and write its committed artifacts under `targetDir`: the
65
+ * lock to `lock.toml` and each projection to its `.claude/**` path (parent
66
+ * directories created). Whole-file writes — a projection is regenerated, never
67
+ * patched. The JSON pipe is in-flight, not a committed artifact, so it is not
68
+ * written (`20-surface.md`, "the committed seam" is artifacts plus lock).
69
+ */
70
+ export declare function writeEmit(harness: Harness, targetDir: string, options?: EmitOptions): EmitResult;