@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 +57 -0
- package/dist/src/assembly.d.ts +45 -0
- package/dist/src/assembly.js +23 -0
- package/dist/src/builtins.d.ts +57 -0
- package/dist/src/builtins.js +50 -0
- package/dist/src/contract.d.ts +84 -0
- package/dist/src/contract.js +36 -0
- package/dist/src/declarations.d.ts +62 -0
- package/dist/src/declarations.js +133 -0
- package/dist/src/emit.d.ts +70 -0
- package/dist/src/emit.js +187 -0
- package/dist/src/genres.d.ts +65 -0
- package/dist/src/genres.js +57 -0
- package/dist/src/index.d.ts +32 -0
- package/dist/src/index.js +21 -0
- package/dist/src/kind.d.ts +138 -0
- package/dist/src/kind.js +63 -0
- package/dist/src/lock.d.ts +43 -0
- package/dist/src/lock.js +127 -0
- package/dist/src/needs.d.ts +31 -0
- package/dist/src/needs.js +28 -0
- package/dist/src/project.d.ts +79 -0
- package/dist/src/project.js +162 -0
- package/dist/src/prose.d.ts +78 -0
- package/dist/src/prose.js +0 -0
- package/dist/src/toml.d.ts +26 -0
- package/dist/src/toml.js +194 -0
- package/package.json +40 -0
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;
|