@dtmd/temper 0.0.1 → 0.0.3

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.
@@ -5,8 +5,9 @@
5
5
  * (ownership not privilege). Their five facts are external facts about the Claude
6
6
  * Code harness, cited at the point of claim.
7
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.
8
+ * These are the SDK's own provider-face exports, surfaced through the
9
+ * `@dtmd/temper/claude-code` subpath (`50-distribution.md`, "Decision: one SDK
10
+ * package — the provider face is a subpath export") — never from the root.
10
11
  */
11
12
  import type { KindDefinition } from "./kind.js";
12
13
  import type { Prose } from "./prose.js";
@@ -5,8 +5,9 @@
5
5
  * (ownership not privilege). Their five facts are external facts about the Claude
6
6
  * Code harness, cited at the point of claim.
7
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.
8
+ * These are the SDK's own provider-face exports, surfaced through the
9
+ * `@dtmd/temper/claude-code` subpath (`50-distribution.md`, "Decision: one SDK
10
+ * package — the provider face is a subpath export") — never from the root.
10
11
  */
11
12
  import { kind } from "./kind.js";
12
13
  /**
@@ -0,0 +1,15 @@
1
+ /**
2
+ * The `@dtmd/temper/claude-code` subpath — the first-party Claude Code
3
+ * provider face (`specs/architecture/50-distribution.md`, "Decision: one SDK
4
+ * package — the provider face is a subpath export"). A harness author who
5
+ * targets Claude Code imports the built-in kinds from here, never the root:
6
+ * the root carries only the six-noun core, and identity travels by import
7
+ * (`specs/architecture/15-kinds.md`), so a subpath specifier is a full module
8
+ * specifier like any other. The built-in floors (exported clause arrays) join
9
+ * this entry when they are authored in `sdk/src`; today only the three kinds
10
+ * exist.
11
+ */
12
+ export type { Memory, Rule, Skill } from "./builtins.js";
13
+ export { memory, rule, skill } from "./builtins.js";
14
+ export type { Blocks, File, Prose, Text } from "./prose.js";
15
+ export { blocks, file, text } from "./prose.js";
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The `@dtmd/temper/claude-code` subpath — the first-party Claude Code
3
+ * provider face (`specs/architecture/50-distribution.md`, "Decision: one SDK
4
+ * package — the provider face is a subpath export"). A harness author who
5
+ * targets Claude Code imports the built-in kinds from here, never the root:
6
+ * the root carries only the six-noun core, and identity travels by import
7
+ * (`specs/architecture/15-kinds.md`), so a subpath specifier is a full module
8
+ * specifier like any other. The built-in floors (exported clause arrays) join
9
+ * this entry when they are authored in `sdk/src`; today only the three kinds
10
+ * exist.
11
+ */
12
+ export { memory, rule, skill } from "./builtins.js";
13
+ export { blocks, file, text } from "./prose.js";
@@ -42,14 +42,20 @@ export interface AssemblyFactRow {
42
42
  readonly field?: string;
43
43
  readonly to?: string;
44
44
  }
45
- /** The four declaration families — the whole erased program the lock and pipe carry. */
45
+ /** One member→requirement fill edge — a resolved `satisfies` key. */
46
+ export interface SatisfiesRow {
47
+ readonly member: string;
48
+ readonly requirement: string;
49
+ }
50
+ /** The five declaration families — the whole erased program the lock and pipe carry. */
46
51
  export interface Declarations {
47
52
  readonly kinds: readonly KindFactRow[];
48
53
  readonly clauses: readonly ClauseRow[];
49
54
  readonly requirements: readonly RequirementRow[];
50
55
  readonly assembly: readonly AssemblyFactRow[];
56
+ readonly satisfies: readonly SatisfiesRow[];
51
57
  }
52
- /** Compile a harness into its four declaration families — the erased program. */
58
+ /** Compile a harness into its five declaration families — the erased program. */
53
59
  export declare function compileDeclarations(harness: Harness): Declarations;
54
60
  /** The SDK's pinned engine/interchange version — the JSON pipe rides it in lockstep. */
55
61
  export declare const SEAM_VERSION = 1;
@@ -99,7 +99,18 @@ function assemblyFactRows(harness, kinds) {
99
99
  }
100
100
  return facts;
101
101
  }
102
- /** Compile a harness into its four declaration families — the erased program. */
102
+ /** The `satisfies` rows — every member's fill claims, member-then-requirement sorted. */
103
+ function satisfiesRows(harness) {
104
+ const rows = [];
105
+ for (const member of harness.members) {
106
+ for (const requirement of member.satisfies) {
107
+ rows.push({ member: member.name, requirement });
108
+ }
109
+ }
110
+ return rows.sort((a, b) => (a.member < b.member ? -1 : a.member > b.member ? 1 : 0) ||
111
+ (a.requirement < b.requirement ? -1 : a.requirement > b.requirement ? 1 : 0));
112
+ }
113
+ /** Compile a harness into its five declaration families — the erased program. */
103
114
  export function compileDeclarations(harness) {
104
115
  const kinds = kindsInPlay(harness);
105
116
  const clauses = [];
@@ -118,6 +129,7 @@ export function compileDeclarations(harness) {
118
129
  clauses,
119
130
  requirements: requirementRows(harness),
120
131
  assembly: assemblyFactRows(harness, kinds),
132
+ satisfies: satisfiesRows(harness),
121
133
  };
122
134
  }
123
135
  /** The SDK's pinned engine/interchange version — the JSON pipe rides it in lockstep. */
@@ -1,15 +1,15 @@
1
1
  /**
2
- * Emit — the compile from the six-noun face to the committed seam
2
+ * Emit — the compile from the six-noun face to the seam's JSON pipe
3
3
  * (`specs/architecture/20-surface.md`, "Emit — total, byte-reproducible, refusing";
4
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).
5
+ * produces plain data — the declaration rows the engine reads and, per projected
6
+ * member, its ordered typed fields and resolved prose body. The engine is the
7
+ * sole compiler of every projection and the whole lock; the SDK writes neither.
8
+ * Emit is total (members are the only source), refuses before it produces a byte
9
+ * on a broken source, and is byte-reproducible — double-emit verified at every
10
+ * run (law 5).
10
11
  */
11
12
  import type { Harness } from "./assembly.js";
12
- import type { Projection } from "./project.js";
13
13
  import type { Declarations } from "./declarations.js";
14
14
  /** How a `file()` asset's module-relative path resolves at emit. */
15
15
  export interface ResolveOptions {
@@ -18,31 +18,38 @@ export interface ResolveOptions {
18
18
  /** The addresses a mention may name — resolution-checked; a mention cannot dangle. */
19
19
  readonly mentionable?: ReadonlySet<string>;
20
20
  }
21
- /** Emit-time inputs beyond the harness — asset base dir and the committed-projection read root. */
21
+ /** One projected member's erased payload — the engine derives its locus from the kind's own declaration row. */
22
+ export interface PayloadMember {
23
+ /** The kind's bare name — joins the payload's `declarations.kinds` family. */
24
+ readonly kind: string;
25
+ /** Identity within the kind. */
26
+ readonly name: string;
27
+ /** The kind's typed fields, flat and ordered — the projected frontmatter. */
28
+ readonly fields: ReadonlyArray<readonly [string, unknown]>;
29
+ /** The resolved prose body, byte-faithful. */
30
+ readonly body: string;
31
+ /** The resolved `file()` asset's absolute path; absent for `text`/`blocks` prose. */
32
+ readonly source_path?: string;
33
+ }
34
+ /** Emit-time inputs beyond the harness — where a `file()` asset's module-relative path resolves against. */
22
35
  export interface EmitOptions {
23
36
  /** Base dir a `file()` asset's module-relative path resolves against (default: cwd). */
24
37
  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
38
  }
33
39
  /**
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.
40
+ * A full emit's compiled outputs — the whole seam the engine reads
41
+ * (`20-surface.md`, "The seam"). A pure function of the harness, so [`emit`]
42
+ * double-verifies it.
37
43
  */
38
44
  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. */
45
+ /** The declaration rows — the erased program the lock's five families carry. */
44
46
  readonly declarations: Declarations;
45
- /** The internal versioned JSON pipe to the engine — not a designed IR. */
47
+ /** The projected members — the engine's sole input for every projection. */
48
+ readonly members: readonly PayloadMember[];
49
+ /**
50
+ * The internal versioned JSON pipe to the engine — not a designed IR. The
51
+ * SDK's whole output surface: printed to stdout, never written to a file.
52
+ */
46
53
  readonly seam: string;
47
54
  /**
48
55
  * The derived permission list — the union of every member's `needs`, deduped and
@@ -53,18 +60,10 @@ export interface EmitResult {
53
60
  readonly permissions: readonly string[];
54
61
  }
55
62
  /**
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
63
+ * Compile the whole face in one deterministic pass: the declaration rows (its
64
+ * rollup and its five families) and every projected member's erased payload.
65
+ * Prose resolves once (`file()` assets read in, mentions resolution-checked
59
66
  * against the harness's declared values). Double-emit verified — nondeterministic
60
67
  * authoring is a loud failure, never a silent churn (law 5).
61
68
  */
62
69
  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;
package/dist/src/emit.js CHANGED
@@ -1,20 +1,19 @@
1
1
  /**
2
- * Emit — the compile from the six-noun face to the committed seam
2
+ * Emit — the compile from the six-noun face to the seam's JSON pipe
3
3
  * (`specs/architecture/20-surface.md`, "Emit — total, byte-reproducible, refusing";
4
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).
5
+ * produces plain data — the declaration rows the engine reads and, per projected
6
+ * member, its ordered typed fields and resolved prose body. The engine is the
7
+ * sole compiler of every projection and the whole lock; the SDK writes neither.
8
+ * Emit is total (members are the only source), refuses before it produces a byte
9
+ * on a broken source, and is byte-reproducible — double-emit verified at every
10
+ * run (law 5).
10
11
  */
11
- import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
12
- import { dirname, join, resolve as resolvePath } from "node:path";
12
+ import { resolve as resolvePath } from "node:path";
13
+ import { readFileSync } from "node:fs";
13
14
  import { renderText } from "./prose.js";
14
15
  import { permissionUnion } from "./needs.js";
15
- import { projectMember } from "./project.js";
16
- import { lockRow, stampLock } from "./lock.js";
17
- import { compileDeclarations, declarationsToJson } from "./declarations.js";
16
+ import { SEAM_VERSION, compileDeclarations } from "./declarations.js";
18
17
  /**
19
18
  * Resolve a member's prose to its final body bytes: a `file()` asset is read in
20
19
  * byte-for-byte; a `text` body's mentions are resolution-checked (loud on a
@@ -30,7 +29,7 @@ function resolveBody(member, options) {
30
29
  if (prose === undefined)
31
30
  return "";
32
31
  if (prose.kind === "file") {
33
- const assetPath = resolvePath(options.baseDir ?? process.cwd(), prose.path);
32
+ const assetPath = fileSourcePath(member, options);
34
33
  try {
35
34
  return readFileSync(assetPath, "utf8");
36
35
  }
@@ -71,7 +70,7 @@ function declaredAddresses(harness) {
71
70
  return set;
72
71
  }
73
72
  /**
74
- * The two declare-side refusals emit runs before it compiles a byte
73
+ * The two declare-side refusals emit runs before it produces a byte
75
74
  * (`20-surface.md`, "Emit refuses before it writes"): a `satisfies` claim naming
76
75
  * no declared requirement (a dangling join), and a `required` requirement no
77
76
  * member fills (an unfilled required requirement).
@@ -115,23 +114,38 @@ function refuseBrokenSource(harness) {
115
114
  function isProjected(member) {
116
115
  return member.facts.locus.kind === "at";
117
116
  }
118
- /** The harness's projected members as projection inputs, deterministically kind-then-name ordered. */
119
- function orderedProjectionInputs(harness, options) {
117
+ /**
118
+ * The resolved absolute path of a `file()` prose asset, or `undefined` for
119
+ * `text`/`blocks` prose (or no prose) — the lift's own-path detection
120
+ * (`specs/architecture/20-surface.md`, "surface authority is a declared
121
+ * posture": the lock is what names a path a projection, so the engine needs
122
+ * each `file()` member's true source path to tell a lifted member's own file
123
+ * apart from a generated one).
124
+ */
125
+ function fileSourcePath(member, options) {
126
+ const prose = member.prose;
127
+ if (prose?.kind !== "file")
128
+ return undefined;
129
+ return resolvePath(options.baseDir ?? process.cwd(), prose.path);
130
+ }
131
+ /** The harness's projected members as payload members, deterministically kind-then-name ordered. */
132
+ function orderedMembers(harness, options) {
120
133
  return [...harness.members]
121
134
  .filter(isProjected)
122
135
  .sort((a, b) => (a.kind < b.kind ? -1 : a.kind > b.kind ? 1 : 0) ||
123
136
  (a.name < b.name ? -1 : a.name > b.name ? 1 : 0))
124
137
  .map((member) => ({
125
- facts: member.facts,
138
+ kind: member.kind,
126
139
  name: member.name,
127
140
  fields: member.fields,
128
141
  body: resolveBody(member, options),
142
+ source_path: fileSourcePath(member, options),
129
143
  }));
130
144
  }
131
145
  /**
132
- * Compile the whole face in one deterministic pass: the projection, the lock (its
133
- * rollup and its declaration rows), the JSON pipe, and the derived permission
134
- * union. Prose resolves once (`file()` assets read in, mentions resolution-checked
146
+ * Compile the whole face in one deterministic pass: the declaration rows (its
147
+ * rollup and its five families) and every projected member's erased payload.
148
+ * Prose resolves once (`file()` assets read in, mentions resolution-checked
135
149
  * against the harness's declared values). Double-emit verified — nondeterministic
136
150
  * authoring is a loud failure, never a silent churn (law 5).
137
151
  */
@@ -142,46 +156,20 @@ export function emit(harness, options = {}) {
142
156
  baseDir: options.baseDir,
143
157
  };
144
158
  const compile = () => {
145
- const inputs = orderedProjectionInputs(harness, resolve);
146
- const projections = inputs.map((input) => projectMember(input, { projectionDir: options.projectionDir }));
147
- const rows = inputs.map((input, i) => lockRow(input.facts.name, projections[i]));
159
+ const members = orderedMembers(harness, resolve);
148
160
  const declarations = compileDeclarations(harness);
149
161
  return {
150
- projections,
151
- lock: stampLock(rows, declarations),
152
162
  declarations,
153
- seam: declarationsToJson(declarations),
163
+ members,
164
+ seam: JSON.stringify({ version: SEAM_VERSION, declarations, members }, null, 2) + "\n",
154
165
  permissions: permissionUnion(harness.members.flatMap((member) => [...member.needs])),
155
166
  };
156
167
  };
157
168
  const first = compile();
158
169
  const second = compile();
159
- if (first.lock !== second.lock ||
160
- first.seam !== second.seam ||
161
- !sameProjections(first.projections, second.projections)) {
170
+ if (first.seam !== second.seam) {
162
171
  throw new Error("double-emit divergence: two passes over the same harness produced different bytes — " +
163
172
  "authoring code is nondeterministic (a timestamp? an unordered map?).");
164
173
  }
165
174
  return first;
166
175
  }
167
- /** Whether two projection lists are byte-identical, path and bytes both. */
168
- function sameProjections(a, b) {
169
- return a.length === b.length && a.every((p, i) => p.path === b[i].path && p.bytes === b[i].bytes);
170
- }
171
- /**
172
- * Run a full [`emit`] and write its committed artifacts under `targetDir`: the
173
- * lock to `lock.toml` and each projection to its `.claude/**` path (parent
174
- * directories created). Whole-file writes — a projection is regenerated, never
175
- * patched. The JSON pipe is in-flight, not a committed artifact, so it is not
176
- * written (`20-surface.md`, "the committed seam" is artifacts plus lock).
177
- */
178
- export function writeEmit(harness, targetDir, options = {}) {
179
- const result = emit(harness, { ...options, projectionDir: options.projectionDir ?? targetDir });
180
- writeFileSync(join(targetDir, "lock.toml"), result.lock);
181
- for (const projection of result.projections) {
182
- const path = join(targetDir, projection.path);
183
- mkdirSync(dirname(path), { recursive: true });
184
- writeFileSync(path, projection.bytes);
185
- }
186
- return result;
187
- }
@@ -3,12 +3,14 @@
3
3
  * "A genre is a kind at the block locus"; ratified `specs/intent/00-intent.md`, the
4
4
  * genre Decision). A genre value's meaning-carrying fields are prose leaves —
5
5
  * authored strings, law-5 protected one by one — plus keyed sibling collections.
6
- * These constructors carry the **shape only**: any predicate over a genre value
7
- * (a decision names at least one rejected alternative) is a clause some module
8
- * ships, never here (`15-kinds.md`, the genre Decision).
6
+ * This constructor carries the **shape only**: any predicate over a genre value
7
+ * is a clause some module ships, never here (`15-kinds.md`, the genre Decision).
8
+ * There is no prescribed genre ontology — a corpus that argues differently
9
+ * declares its own genres with the same machinery (`15-kinds.md`, "a genre is
10
+ * a full kind, and genre checks are data, never engine").
9
11
  *
10
- * They are the posture-3 spelling — fully composed values passed to `blocks()`
11
- * (`20-surface.md`). The byte-identical posture-2 fence render awaits
12
+ * `genreValue()` is the posture-3 spelling — a fully composed value passed to
13
+ * `blocks()` (`20-surface.md`). The byte-identical posture-2 fence render awaits
12
14
  * `(genre-fence-format)`, deferred until its first consumer lands.
13
15
  */
14
16
  /**
@@ -18,7 +20,7 @@
18
20
  * the leaf-address Decision).
19
21
  */
20
22
  export interface GenreValue {
21
- /** The genre name — `decision`, `law`, `bound`, or a project's own. */
23
+ /** The genre name — a project's own, never a built-in prescribed ontology. */
22
24
  readonly genre: string;
23
25
  /** The value's key — the identity a leaf address carries (`surface-authority`). */
24
26
  readonly key: string;
@@ -27,35 +29,6 @@ export interface GenreValue {
27
29
  /** Keyed sibling collections: collection → entry key → field → authored string. */
28
30
  readonly collections: Readonly<Record<string, Readonly<Record<string, Readonly<Record<string, string>>>>>>;
29
31
  }
30
- /** A rejected alternative: keyed by option slug, its rationale a prose leaf. */
31
- export interface Alternative {
32
- readonly because: string;
33
- }
34
- /**
35
- * The `decision` genre — the Chosen/Rejected convention, typed. Sibling
36
- * collections are keyed by option slug, never positional: positional addresses
37
- * die on insertion and reorder, which is exactly when impact must survive.
38
- */
39
- export declare function decision(init: {
40
- key: string;
41
- chosen: string;
42
- rejected?: Readonly<Record<string, Alternative>>;
43
- }): GenreValue;
44
- /** The `law` genre — a numbered law's statement with its named bounds. */
45
- export declare function law(init: {
46
- key: string;
47
- statement: string;
48
- bounds?: Readonly<Record<string, {
49
- claim: string;
50
- }>>;
51
- }): GenreValue;
52
- /** The `bound` genre — the honest bound: claim, deferral, unlock condition. */
53
- export declare function bound(init: {
54
- key: string;
55
- claim: string;
56
- deferred: string;
57
- unlock: string;
58
- }): GenreValue;
59
32
  /** A project's own genre — the same machinery, an author-declared shape. */
60
33
  export declare function genreValue(init: {
61
34
  genre: string;
@@ -3,49 +3,16 @@
3
3
  * "A genre is a kind at the block locus"; ratified `specs/intent/00-intent.md`, the
4
4
  * genre Decision). A genre value's meaning-carrying fields are prose leaves —
5
5
  * authored strings, law-5 protected one by one — plus keyed sibling collections.
6
- * These constructors carry the **shape only**: any predicate over a genre value
7
- * (a decision names at least one rejected alternative) is a clause some module
8
- * ships, never here (`15-kinds.md`, the genre Decision).
6
+ * This constructor carries the **shape only**: any predicate over a genre value
7
+ * is a clause some module ships, never here (`15-kinds.md`, the genre Decision).
8
+ * There is no prescribed genre ontology — a corpus that argues differently
9
+ * declares its own genres with the same machinery (`15-kinds.md`, "a genre is
10
+ * a full kind, and genre checks are data, never engine").
9
11
  *
10
- * They are the posture-3 spelling — fully composed values passed to `blocks()`
11
- * (`20-surface.md`). The byte-identical posture-2 fence render awaits
12
+ * `genreValue()` is the posture-3 spelling — a fully composed value passed to
13
+ * `blocks()` (`20-surface.md`). The byte-identical posture-2 fence render awaits
12
14
  * `(genre-fence-format)`, deferred until its first consumer lands.
13
15
  */
14
- /**
15
- * The `decision` genre — the Chosen/Rejected convention, typed. Sibling
16
- * collections are keyed by option slug, never positional: positional addresses
17
- * die on insertion and reorder, which is exactly when impact must survive.
18
- */
19
- export function decision(init) {
20
- return {
21
- genre: "decision",
22
- key: init.key,
23
- leaves: { chosen: init.chosen },
24
- collections: {
25
- rejected: Object.fromEntries(Object.entries(init.rejected ?? {}).map(([slug, alt]) => [slug, { because: alt.because }])),
26
- },
27
- };
28
- }
29
- /** The `law` genre — a numbered law's statement with its named bounds. */
30
- export function law(init) {
31
- return {
32
- genre: "law",
33
- key: init.key,
34
- leaves: { statement: init.statement },
35
- collections: {
36
- bounds: Object.fromEntries(Object.entries(init.bounds ?? {}).map(([slug, bound]) => [slug, { claim: bound.claim }])),
37
- },
38
- };
39
- }
40
- /** The `bound` genre — the honest bound: claim, deferral, unlock condition. */
41
- export function bound(init) {
42
- return {
43
- genre: "bound",
44
- key: init.key,
45
- leaves: { claim: init.claim, deferred: init.deferred, unlock: init.unlock },
46
- collections: {},
47
- };
48
- }
49
16
  /** A project's own genre — the same machinery, an author-declared shape. */
50
17
  export function genreValue(init) {
51
18
  return {
@@ -1,32 +1,32 @@
1
1
  /**
2
- * temper's authoring face — the six-noun model as a typed module library
2
+ * temper's authoring face — the six-noun core as a typed module library
3
3
  * (`specs/intent/00-intent.md`, the SDK Decision; `specs/architecture/20-surface.md`).
4
- * A harness author imports plain nouns — the built-in kinds, `harness()`, the
5
- * clause and requirement constructors, `needs`, and the three prose constructors —
6
- * and composes members as typed values. `emit` compiles the whole into the
7
- * declaration rows the engine reads, a byte-faithful projection, and the lock;
8
- * every type erases at the seam, and Turing-completeness stays quarantined at
9
- * authoring time.
4
+ * A harness author imports plain nouns — `harness()`, the generic `kind`/`genre`
5
+ * constructors, the clause and requirement constructors, `needs`, and the three
6
+ * prose constructors — and composes members as typed values. `emit` compiles the
7
+ * whole into the declaration rows and the projected members' erased payload —
8
+ * the JSON pipe printed to stdout; the engine is the sole compiler of every
9
+ * projection and the whole lock (`20-surface.md`, "The seam — one
10
+ * implementation"). Every type erases at the seam, and Turing-completeness
11
+ * stays quarantined at authoring time.
12
+ *
13
+ * The first-party Claude Code provider face — the built-in `skill`/`rule`/
14
+ * `memory` kinds — lives at the `./claude-code` subpath, never here
15
+ * (`specs/architecture/50-distribution.md`, "Decision: one SDK package").
10
16
  */
11
17
  export type { Blocks, File, Mention, Mentionable, Prose, Text } from "./prose.js";
12
18
  export { blocks, file, renderText, text } from "./prose.js";
13
- export type { Alternative, GenreValue } from "./genres.js";
14
- export { bound, decision, genreValue, law } from "./genres.js";
19
+ export type { GenreValue } from "./genres.js";
20
+ export { genreValue } from "./genres.js";
15
21
  export type { Capability } from "./needs.js";
16
22
  export { bash, capability, permissionUnion } from "./needs.js";
17
23
  export type { Clause, Predicate, Requirement, Severity } from "./contract.js";
18
24
  export { allowedChars, clause, forbiddenKeys, maxLen, maxLines, minLen, nameMatchesDir, required, requireSections, requirement, type, } from "./contract.js";
19
25
  export type { EdgeField, Format, KindDefinition, KindFacts, Locus, Member, MemberInit, Registration, UnitShape, } from "./kind.js";
20
26
  export { genre, kind } from "./kind.js";
21
- export type { Memory, Rule, Skill } from "./builtins.js";
22
- export { memory, rule, skill } from "./builtins.js";
23
27
  export type { ExpectBinding, Harness } from "./assembly.js";
24
28
  export { harness } from "./assembly.js";
25
- export type { AssemblyFactRow, ClauseRow, Declarations, KindFactRow, RequirementRow, } from "./declarations.js";
29
+ export type { AssemblyFactRow, ClauseRow, Declarations, KindFactRow, RequirementRow, SatisfiesRow, } from "./declarations.js";
26
30
  export { SEAM_VERSION, compileDeclarations, declarationsToJson } from "./declarations.js";
27
- export type { Projection, ProjectionInput, ProjectOptions } from "./project.js";
28
- export { placementLines, projectBytes, projectMember, projectionPath, renderField } from "./project.js";
29
- export type { LockRow } from "./lock.js";
30
- export { lockRow, sha256Hex, stampLock } from "./lock.js";
31
- export type { EmitOptions, EmitResult, ResolveOptions } from "./emit.js";
32
- export { emit, writeEmit } from "./emit.js";
31
+ export type { EmitOptions, EmitResult, PayloadMember, ResolveOptions } from "./emit.js";
32
+ export { emit } from "./emit.js";
package/dist/src/index.js CHANGED
@@ -1,21 +1,24 @@
1
1
  /**
2
- * temper's authoring face — the six-noun model as a typed module library
2
+ * temper's authoring face — the six-noun core as a typed module library
3
3
  * (`specs/intent/00-intent.md`, the SDK Decision; `specs/architecture/20-surface.md`).
4
- * A harness author imports plain nouns — the built-in kinds, `harness()`, the
5
- * clause and requirement constructors, `needs`, and the three prose constructors —
6
- * and composes members as typed values. `emit` compiles the whole into the
7
- * declaration rows the engine reads, a byte-faithful projection, and the lock;
8
- * every type erases at the seam, and Turing-completeness stays quarantined at
9
- * authoring time.
4
+ * A harness author imports plain nouns — `harness()`, the generic `kind`/`genre`
5
+ * constructors, the clause and requirement constructors, `needs`, and the three
6
+ * prose constructors — and composes members as typed values. `emit` compiles the
7
+ * whole into the declaration rows and the projected members' erased payload —
8
+ * the JSON pipe printed to stdout; the engine is the sole compiler of every
9
+ * projection and the whole lock (`20-surface.md`, "The seam — one
10
+ * implementation"). Every type erases at the seam, and Turing-completeness
11
+ * stays quarantined at authoring time.
12
+ *
13
+ * The first-party Claude Code provider face — the built-in `skill`/`rule`/
14
+ * `memory` kinds — lives at the `./claude-code` subpath, never here
15
+ * (`specs/architecture/50-distribution.md`, "Decision: one SDK package").
10
16
  */
11
17
  export { blocks, file, renderText, text } from "./prose.js";
12
- export { bound, decision, genreValue, law } from "./genres.js";
18
+ export { genreValue } from "./genres.js";
13
19
  export { bash, capability, permissionUnion } from "./needs.js";
14
20
  export { allowedChars, clause, forbiddenKeys, maxLen, maxLines, minLen, nameMatchesDir, required, requireSections, requirement, type, } from "./contract.js";
15
21
  export { genre, kind } from "./kind.js";
16
- export { memory, rule, skill } from "./builtins.js";
17
22
  export { harness } from "./assembly.js";
18
23
  export { SEAM_VERSION, compileDeclarations, declarationsToJson } from "./declarations.js";
19
- export { placementLines, projectBytes, projectMember, projectionPath, renderField } from "./project.js";
20
- export { lockRow, sha256Hex, stampLock } from "./lock.js";
21
- export { emit, writeEmit } from "./emit.js";
24
+ export { emit } from "./emit.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dtmd/temper",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "The temper authoring face — the six-noun model as typed modules: harness(), kind<T>(), clause values, needs, and file()/text/blocks(). Emit compiles to the declaration rows the engine reads, a byte-faithful projection, and the lock.",
5
5
  "license": "(MIT OR Apache-2.0)",
6
6
  "repository": {
@@ -20,6 +20,16 @@
20
20
  "type": "module",
21
21
  "main": "./dist/src/index.js",
22
22
  "types": "./dist/src/index.d.ts",
23
+ "exports": {
24
+ ".": {
25
+ "types": "./dist/src/index.d.ts",
26
+ "default": "./dist/src/index.js"
27
+ },
28
+ "./claude-code": {
29
+ "types": "./dist/src/claude-code.d.ts",
30
+ "default": "./dist/src/claude-code.js"
31
+ }
32
+ },
23
33
  "files": [
24
34
  "dist/src",
25
35
  "README.md"
@@ -1,43 +0,0 @@
1
- /**
2
- * The lock — tool-written provenance, emit fingerprints, and the program's
3
- * declaration rows (`specs/architecture/20-surface.md`, "The lock and drift").
4
- * Two row families: a per-member `[[<kind>]]` rollup (`name`, `source_path`,
5
- * `source_hash`, `emit_hash`) and the `[declaration]` table's four sub-families
6
- * (`[[declaration.kind]]`, `[[declaration.clause]]`, `[[declaration.requirement]]`,
7
- * `[[declaration.assembly]]`). Both are byte-identical to the Rust lock
8
- * (`src/import.rs` `write_rollup`, `src/drift.rs` `Declarations::write_into`) — the
9
- * byte-parity lockstep two writers keep until single-writer lands.
10
- *
11
- * Fingerprints are SHA-256 hex over raw UTF-8 (`hash::sha256_hex`), so an
12
- * SDK-emitted lock and a Rust-emitted lock agree for the same harness.
13
- */
14
- import type { Projection } from "./project.js";
15
- import type { Declarations } from "./declarations.js";
16
- /** Lowercase hex SHA-256 of `text`'s UTF-8 bytes — the Rust `sha256_hex` port. */
17
- export declare function sha256Hex(text: string): string;
18
- /** One rollup row: a member's identity and the two freshness fingerprints. */
19
- export interface LockRow {
20
- /** The bare kind name — the `[[<kind>]]` array key (`rule`, `skill`, `memory`). */
21
- readonly kind: string;
22
- /** The member id — its `[[<kind>]]` `name` column. */
23
- readonly name: string;
24
- /** The projection's harness path — the source-of-record the fingerprints anchor. */
25
- readonly sourcePath: string;
26
- /** SHA-256 of the authored source bytes (the projection, for a module-carried member). */
27
- readonly sourceHash: string;
28
- /** SHA-256 of the last emitted projection — the `config.stale` baseline. */
29
- readonly emitHash: string;
30
- }
31
- /**
32
- * The rollup row a projection stamps: both fingerprints are `sha256(projection
33
- * bytes)`, the fresh-emit baseline (`source_hash == emit_hash`) a Rust import then
34
- * emit lands on for a byte-identical projection.
35
- */
36
- export declare function lockRow(kind: string, projection: Projection): LockRow;
37
- /**
38
- * Serialize the lock — the rollup rows then the declaration families, joined the
39
- * `toml_edit` way (exactly one blank line before every table header but the
40
- * document's first). An all-empty declaration set contributes no sections, so a
41
- * memberless lock is empty and a rollup-only lock carries no `[declaration]` rows.
42
- */
43
- export declare function stampLock(rows: readonly LockRow[], declarations?: Declarations): string;
package/dist/src/lock.js DELETED
@@ -1,127 +0,0 @@
1
- /**
2
- * The lock — tool-written provenance, emit fingerprints, and the program's
3
- * declaration rows (`specs/architecture/20-surface.md`, "The lock and drift").
4
- * Two row families: a per-member `[[<kind>]]` rollup (`name`, `source_path`,
5
- * `source_hash`, `emit_hash`) and the `[declaration]` table's four sub-families
6
- * (`[[declaration.kind]]`, `[[declaration.clause]]`, `[[declaration.requirement]]`,
7
- * `[[declaration.assembly]]`). Both are byte-identical to the Rust lock
8
- * (`src/import.rs` `write_rollup`, `src/drift.rs` `Declarations::write_into`) — the
9
- * byte-parity lockstep two writers keep until single-writer lands.
10
- *
11
- * Fingerprints are SHA-256 hex over raw UTF-8 (`hash::sha256_hex`), so an
12
- * SDK-emitted lock and a Rust-emitted lock agree for the same harness.
13
- */
14
- import { createHash } from "node:crypto";
15
- import { joinSections, keyValue, encodeString } from "./toml.js";
16
- /** Lowercase hex SHA-256 of `text`'s UTF-8 bytes — the Rust `sha256_hex` port. */
17
- export function sha256Hex(text) {
18
- return createHash("sha256").update(text, "utf8").digest("hex");
19
- }
20
- /** The member name a projection encodes — the path's identity segment. */
21
- function projectionName(projection) {
22
- const segments = projection.path.split("/");
23
- const last = segments[segments.length - 1];
24
- if (last === "SKILL.md")
25
- return segments[segments.length - 2];
26
- return last.replace(/\.md$/, "");
27
- }
28
- /**
29
- * The rollup row a projection stamps: both fingerprints are `sha256(projection
30
- * bytes)`, the fresh-emit baseline (`source_hash == emit_hash`) a Rust import then
31
- * emit lands on for a byte-identical projection.
32
- */
33
- export function lockRow(kind, projection) {
34
- const hash = sha256Hex(projection.bytes);
35
- return {
36
- kind,
37
- name: projectionName(projection),
38
- sourcePath: projection.path,
39
- sourceHash: hash,
40
- emitHash: hash,
41
- };
42
- }
43
- /** The rollup sections — one `[[<kind>]]` table per member, kinds then rows name-sorted. */
44
- function rollupSections(rows) {
45
- const byKind = new Map();
46
- for (const row of rows) {
47
- const bucket = byKind.get(row.kind);
48
- if (bucket)
49
- bucket.push(row);
50
- else
51
- byKind.set(row.kind, [row]);
52
- }
53
- const sections = [];
54
- for (const kind of [...byKind.keys()].sort()) {
55
- const kindRows = byKind
56
- .get(kind)
57
- .slice()
58
- .sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
59
- for (const row of kindRows) {
60
- sections.push(`[[${kind}]]\n` +
61
- keyValue("name", encodeString(row.name)) +
62
- keyValue("source_path", encodeString(row.sourcePath)) +
63
- keyValue("source_hash", encodeString(row.sourceHash)) +
64
- keyValue("emit_hash", encodeString(row.emitHash)));
65
- }
66
- }
67
- return sections;
68
- }
69
- /** A `key = "value"\n` line for a present string column, or "" to omit an absent one. */
70
- function optionalColumn(key, value) {
71
- return value === undefined ? "" : keyValue(key, encodeString(value));
72
- }
73
- /**
74
- * The `[declaration]` table's sections, in the fixed family order kind · clause ·
75
- * requirement · assembly, each row a `[[declaration.<family>]]` table with its
76
- * columns in the Rust `to_table` order. An empty family writes nothing — an empty
77
- * array vanishes on the toml round-trip.
78
- */
79
- function declarationSections(declarations) {
80
- const sections = [];
81
- for (const row of declarations.kinds) {
82
- sections.push("[[declaration.kind]]\n" +
83
- keyValue("name", encodeString(row.name)) +
84
- optionalColumn("provider", row.provider) +
85
- keyValue("governs_root", encodeString(row.governs_root)) +
86
- keyValue("governs_glob", encodeString(row.governs_glob)) +
87
- optionalColumn("format", row.format) +
88
- optionalColumn("unit_shape", row.unit_shape) +
89
- optionalColumn("activation", row.activation));
90
- }
91
- for (const row of declarations.clauses) {
92
- sections.push("[[declaration.clause]]\n" +
93
- keyValue("kind", encodeString(row.kind)) +
94
- keyValue("predicate", encodeString(row.predicate)) +
95
- optionalColumn("field", row.field) +
96
- keyValue("severity", encodeString(row.severity)));
97
- }
98
- for (const row of declarations.requirements) {
99
- sections.push("[[declaration.requirement]]\n" +
100
- keyValue("name", encodeString(row.name)) +
101
- optionalColumn("kind", row.kind) +
102
- optionalColumn("package", row.package) +
103
- keyValue("required", row.required ? "true" : "false") +
104
- optionalColumn("verified_by", row.verified_by));
105
- }
106
- for (const row of declarations.assembly) {
107
- sections.push("[[declaration.assembly]]\n" +
108
- keyValue("fact", encodeString(row.fact)) +
109
- optionalColumn("value", row.value) +
110
- optionalColumn("from", row.from) +
111
- optionalColumn("field", row.field) +
112
- optionalColumn("to", row.to));
113
- }
114
- return sections;
115
- }
116
- /**
117
- * Serialize the lock — the rollup rows then the declaration families, joined the
118
- * `toml_edit` way (exactly one blank line before every table header but the
119
- * document's first). An all-empty declaration set contributes no sections, so a
120
- * memberless lock is empty and a rollup-only lock carries no `[declaration]` rows.
121
- */
122
- export function stampLock(rows, declarations) {
123
- const sections = [...rollupSections(rows)];
124
- if (declarations !== undefined)
125
- sections.push(...declarationSections(declarations));
126
- return joinSections(sections);
127
- }
@@ -1,79 +0,0 @@
1
- /**
2
- * Projection — emit compiles each member to its harness format under `.claude/**`,
3
- * whole-file and byte-faithful (`specs/architecture/20-surface.md`, "Emit —
4
- * total"). A member with frontmatter fields projects to a fresh `---`-delimited
5
- * block over its resolved body; a frontmatterless kind (memory) projects to its
6
- * body alone. The words are the author's, untouched — emit never stamps metadata
7
- * into the projection (law 5); the managed-by note and the schema modeline ride
8
- * `install`, and a re-emit round-trips them through the whole-file write.
9
- *
10
- * The locus and layout come from the kind's five facts (`15-kinds.md`), never a
11
- * hardcoded kind name: a directory unit lands its entry file under a per-member
12
- * directory, a lone file lands at the stem, a frontmatterless any-depth memory
13
- * lands the root `<name>.md`.
14
- */
15
- import type { KindFacts } from "./kind.js";
16
- /** One projected harness file: where it lands and the byte-faithful content. */
17
- export interface Projection {
18
- /** The slash path relative to the harness root (`.claude/**`, or a root memory). */
19
- readonly path: string;
20
- /** The whole-file projection bytes — frontmatter (if any) over the body. */
21
- readonly bytes: string;
22
- }
23
- /** The resolved member emit hands the projector — facts, name, ordered fields, resolved body. */
24
- export interface ProjectionInput {
25
- readonly facts: KindFacts;
26
- readonly name: string;
27
- readonly fields: ReadonlyArray<readonly [string, unknown]>;
28
- readonly body: string;
29
- }
30
- /** Emit-time inputs beyond the member — where install's placements are read from. */
31
- export interface ProjectOptions {
32
- /**
33
- * The harness root the **committed** projection is read from to carry install's
34
- * frontmatter placements (the schema modeline + managed-by note) through the
35
- * whole-file re-emit — the two-projectors seam. Absent — or an absent committed
36
- * file — carries no placements: emit writes the projection fresh.
37
- */
38
- readonly projectionDir?: string;
39
- }
40
- /**
41
- * The install-placed frontmatter comment lines present in `source`, in on-disk
42
- * order — the schema modeline and the managed-by note. `emit` round-trips these
43
- * through its whole-file re-emit so its content-faithful projection (law 5)
44
- * carries install's metadata instead of dropping it (`20-surface.md`).
45
- */
46
- export declare function placementLines(source: string): string[];
47
- /**
48
- * The harness locus a member of `facts` named `name` projects onto, derived from
49
- * the kind's locus and unit shape (`15-kinds.md`): a directory unit lands its
50
- * entry file under `<root>/<name>/`; a lone file replaces the glob's `*` with the
51
- * name (an any-depth memory lands the root `<name>.md`).
52
- *
53
- * # Throws
54
- * If the kind is a genre — a block-locus member has no standalone projection.
55
- */
56
- export declare function projectionPath(facts: KindFacts, name: string): string;
57
- /**
58
- * One frontmatter field as `key: <value>\n`, or `null` to omit a null/undefined
59
- * value. The value is compact JSON — valid YAML flow, round-tripping to the same
60
- * JSON on the next parse — matching the Rust `render_field` (`serde_json::to_string`).
61
- */
62
- export declare function renderField(key: string, value: unknown): string | null;
63
- /**
64
- * The whole-file projection bytes for one member: no surviving field ⇒ the body
65
- * alone (no frontmatter block, so no place a modeline/note could sit); one or
66
- * more ⇒ a fresh `---` frontmatter (install's preserved `placements` first, then
67
- * every field in order) over the byte-faithful body.
68
- */
69
- export declare function projectBytes(fields: ReadonlyArray<readonly [string, unknown]>, body: string, placements?: readonly string[]): string;
70
- /**
71
- * Project one resolved member onto its harness file — its locus and the whole-file
72
- * bytes. With `options.projectionDir` set, install's placement lines ride through
73
- * the re-emit (the two-projectors seam).
74
- *
75
- * # Throws
76
- * If the member's kind is a genre ([`projectionPath`]), or the committed projection
77
- * cannot be read for a reason other than absence.
78
- */
79
- export declare function projectMember(member: ProjectionInput, options?: ProjectOptions): Projection;
@@ -1,162 +0,0 @@
1
- /**
2
- * Projection — emit compiles each member to its harness format under `.claude/**`,
3
- * whole-file and byte-faithful (`specs/architecture/20-surface.md`, "Emit —
4
- * total"). A member with frontmatter fields projects to a fresh `---`-delimited
5
- * block over its resolved body; a frontmatterless kind (memory) projects to its
6
- * body alone. The words are the author's, untouched — emit never stamps metadata
7
- * into the projection (law 5); the managed-by note and the schema modeline ride
8
- * `install`, and a re-emit round-trips them through the whole-file write.
9
- *
10
- * The locus and layout come from the kind's five facts (`15-kinds.md`), never a
11
- * hardcoded kind name: a directory unit lands its entry file under a per-member
12
- * directory, a lone file lands at the stem, a frontmatterless any-depth memory
13
- * lands the root `<name>.md`.
14
- */
15
- import { readFileSync } from "node:fs";
16
- import { join } from "node:path";
17
- /** The schema modeline marker install places and emit preserves (`src/install.rs`). */
18
- const MODELINE_MARKER = "# yaml-language-server:";
19
- /** The managed-by note's stable marker (`src/install.rs`). */
20
- const NOTE_MARKER = "# temper: managed projection";
21
- /** Whether `line` is one of install's managed metadata comments. */
22
- function isPlacementComment(line) {
23
- const trimmed = line.replace(/^\s+/, "");
24
- return trimmed.startsWith(MODELINE_MARKER) || trimmed.startsWith(NOTE_MARKER);
25
- }
26
- /**
27
- * A string's lines the Rust `str::lines` way: split on `\n`, a trailing newline
28
- * opens no line, a trailing `\r` is stripped from each.
29
- */
30
- function lines(textValue) {
31
- if (textValue === "")
32
- return [];
33
- const parts = textValue.split("\n");
34
- if (parts[parts.length - 1] === "")
35
- parts.pop();
36
- return parts.map((line) => (line.endsWith("\r") ? line.slice(0, -1) : line));
37
- }
38
- /**
39
- * The frontmatter interior of `rest` — everything after the opening `---\n` up to
40
- * the closing `---` line — or `null` when there is no closing delimiter (an
41
- * opening `---` that is really prose). A port of the Rust `install::frontmatter_inner`.
42
- */
43
- function frontmatterInner(rest) {
44
- let offset = 0;
45
- let cursor = 0;
46
- while (cursor < rest.length) {
47
- const newline = rest.indexOf("\n", cursor);
48
- const end = newline === -1 ? rest.length : newline + 1;
49
- const piece = rest.slice(cursor, end);
50
- const content = piece.endsWith("\n") ? piece.slice(0, -1) : piece;
51
- if (content.replace(/\s+$/, "") === "---")
52
- return rest.slice(0, offset);
53
- offset += piece.length;
54
- cursor = end;
55
- }
56
- return null;
57
- }
58
- /**
59
- * The install-placed frontmatter comment lines present in `source`, in on-disk
60
- * order — the schema modeline and the managed-by note. `emit` round-trips these
61
- * through its whole-file re-emit so its content-faithful projection (law 5)
62
- * carries install's metadata instead of dropping it (`20-surface.md`).
63
- */
64
- export function placementLines(source) {
65
- if (!source.startsWith("---\n"))
66
- return [];
67
- const inner = frontmatterInner(source.slice("---\n".length));
68
- if (inner === null)
69
- return [];
70
- return lines(inner).filter(isPlacementComment);
71
- }
72
- /** Join non-empty, non-`.` path segments with `/` — a `.` root drops out (root memory). */
73
- function joinSlash(...parts) {
74
- return parts.filter((part) => part !== "" && part !== ".").join("/");
75
- }
76
- /**
77
- * The harness locus a member of `facts` named `name` projects onto, derived from
78
- * the kind's locus and unit shape (`15-kinds.md`): a directory unit lands its
79
- * entry file under `<root>/<name>/`; a lone file replaces the glob's `*` with the
80
- * name (an any-depth memory lands the root `<name>.md`).
81
- *
82
- * # Throws
83
- * If the kind is a genre — a block-locus member has no standalone projection.
84
- */
85
- export function projectionPath(facts, name) {
86
- if (facts.locus.kind !== "at") {
87
- throw new Error(`kind \`${facts.name}\` is a genre — its members live inside host documents ` +
88
- `and have no standalone projection (specs/architecture/15-kinds.md).`);
89
- }
90
- const { root, glob } = facts.locus;
91
- if (facts.unitShape === "directory") {
92
- // `*/SKILL.md` → the entry file after the first slash, under a per-member dir.
93
- const entry = glob.slice(glob.indexOf("/") + 1);
94
- return joinSlash(root, name, entry);
95
- }
96
- // A lone file: any-depth glob (`**/CLAUDE.md`) is the root `<name>.md`; a simple
97
- // glob (`*.md`) replaces its single star with the name.
98
- const filename = glob.includes("**") ? `${name}.md` : glob.replace("*", name);
99
- return joinSlash(root, filename);
100
- }
101
- /**
102
- * One frontmatter field as `key: <value>\n`, or `null` to omit a null/undefined
103
- * value. The value is compact JSON — valid YAML flow, round-tripping to the same
104
- * JSON on the next parse — matching the Rust `render_field` (`serde_json::to_string`).
105
- */
106
- export function renderField(key, value) {
107
- if (value === null || value === undefined)
108
- return null;
109
- return `${key}: ${JSON.stringify(value)}\n`;
110
- }
111
- /**
112
- * The whole-file projection bytes for one member: no surviving field ⇒ the body
113
- * alone (no frontmatter block, so no place a modeline/note could sit); one or
114
- * more ⇒ a fresh `---` frontmatter (install's preserved `placements` first, then
115
- * every field in order) over the byte-faithful body.
116
- */
117
- export function projectBytes(fields, body, placements = []) {
118
- const rendered = fields
119
- .map(([key, value]) => renderField(key, value))
120
- .filter((line) => line !== null);
121
- if (rendered.length === 0)
122
- return body;
123
- const frontmatter = placements.map((line) => `${line}\n`).join("") + rendered.join("");
124
- return `---\n${frontmatter}---\n${body}`;
125
- }
126
- /**
127
- * Read the install-placed frontmatter lines from the committed projection at
128
- * `projectionDir/path`, or `[]` when none is read (no `projectionDir`, or the file
129
- * is absent — emit writes it fresh). Reads are of committed bytes, never a clock,
130
- * so the double-emit purity check still holds.
131
- *
132
- * # Throws
133
- * On a read failure that is not "file absent".
134
- */
135
- function committedPlacements(projectionDir, path) {
136
- if (projectionDir === undefined)
137
- return [];
138
- try {
139
- return placementLines(readFileSync(join(projectionDir, path), "utf8"));
140
- }
141
- catch (cause) {
142
- if (cause.code === "ENOENT")
143
- return [];
144
- throw new Error(`failed to read committed projection \`${path}\` under \`${projectionDir}\`.`, {
145
- cause,
146
- });
147
- }
148
- }
149
- /**
150
- * Project one resolved member onto its harness file — its locus and the whole-file
151
- * bytes. With `options.projectionDir` set, install's placement lines ride through
152
- * the re-emit (the two-projectors seam).
153
- *
154
- * # Throws
155
- * If the member's kind is a genre ([`projectionPath`]), or the committed projection
156
- * cannot be read for a reason other than absence.
157
- */
158
- export function projectMember(member, options = {}) {
159
- const path = projectionPath(member.facts, member.name);
160
- const placements = committedPlacements(options.projectionDir, path);
161
- return { path, bytes: projectBytes(member.fields, member.body, placements) };
162
- }
@@ -1,26 +0,0 @@
1
- /**
2
- * TOML value/key encoding and table layout — a faithful port of `toml_write`
3
- * 0.1.2 (`src/string.rs`) and `toml_edit` 0.22.27's `visit_table`
4
- * (`specs/architecture/20-surface.md`, "Content-faithful, deterministically
5
- * emitted (law 5)"). Shared by the manifest emitter (`emit.ts`) and the lock
6
- * stamper (`lock.ts`) so every `key = value` line and every table header the SDK
7
- * writes is byte-identical to the Rust `toml_edit` output — the manifest, the
8
- * projection frontmatter, and the lock all agree to the byte.
9
- */
10
- /** A TOML string *value* — the exact bytes `toml_edit`'s `value(String)` emits. */
11
- export declare function encodeString(s: string): string;
12
- /** A TOML *key* — bare where it can be, else `toml_edit`'s `TomlKeyBuilder::as_default`. */
13
- export declare function encodeKey(s: string): string;
14
- /** One `key = value\n` line, the key/value decor `toml_edit` renders (`key = value`). */
15
- export declare function keyValue(key: string, valueRepr: string): string;
16
- /** A TOML string array — `["a", "b"]`, no leading space, `, ` between elements. */
17
- export declare function stringArray(values: readonly string[]): string;
18
- /** Sorted keys — the stable order `toml_edit` gets for free from its `BTreeMap`s. */
19
- export declare function sortedKeys(record: Readonly<Record<string, unknown>>): string[];
20
- /**
21
- * Join an ordered list of table sections the `toml_edit` way — exactly one blank
22
- * line before every table header but the document's first
23
- * (`DEFAULT_TABLE_DECOR = ("\n", "")`, the first table `("", …)`). Each section is
24
- * a header line plus its `key = value\n` lines, already newline-terminated.
25
- */
26
- export declare function joinSections(sections: readonly string[]): string;
package/dist/src/toml.js DELETED
@@ -1,194 +0,0 @@
1
- /**
2
- * TOML value/key encoding and table layout — a faithful port of `toml_write`
3
- * 0.1.2 (`src/string.rs`) and `toml_edit` 0.22.27's `visit_table`
4
- * (`specs/architecture/20-surface.md`, "Content-faithful, deterministically
5
- * emitted (law 5)"). Shared by the manifest emitter (`emit.ts`) and the lock
6
- * stamper (`lock.ts`) so every `key = value` line and every table header the SDK
7
- * writes is byte-identical to the Rust `toml_edit` output — the manifest, the
8
- * projection frontmatter, and the lock all agree to the byte.
9
- */
10
- /** `ValueMetrics::calculate` — the run-length and escape facts style choice reads. */
11
- function stringMetrics(s) {
12
- let maxSingle = 0;
13
- let maxDouble = 0;
14
- let escapeCodes = false;
15
- let escape = false;
16
- let newline = false;
17
- let prevSingle = 0;
18
- let prevDouble = 0;
19
- for (const ch of s) {
20
- const cp = ch.codePointAt(0);
21
- if (cp === 0x27) {
22
- prevSingle += 1;
23
- maxSingle = Math.max(maxSingle, prevSingle);
24
- }
25
- else {
26
- prevSingle = 0;
27
- }
28
- if (cp === 0x22) {
29
- prevDouble += 1;
30
- maxDouble = Math.max(maxDouble, prevDouble);
31
- }
32
- else {
33
- prevDouble = 0;
34
- }
35
- // The arm order mirrors the Rust match: `\` then `\t` (allowed) then `\n`
36
- // then the general control range.
37
- if (cp === 0x5c)
38
- escape = true;
39
- else if (cp === 0x09) {
40
- /* horizontal tab is always allowed — neutral */
41
- }
42
- else if (cp === 0x0a)
43
- newline = true;
44
- else if (cp <= 0x1f || cp === 0x7f)
45
- escapeCodes = true;
46
- }
47
- return { maxSingle, maxDouble, escapeCodes, escape, newline };
48
- }
49
- /** `TomlStringBuilder::as_default` — the fall-through style preference. */
50
- function chooseEncoding(m) {
51
- // as_basic_pretty
52
- if (!(m.escapeCodes || m.escape || m.maxDouble > 0 || m.newline))
53
- return "basic";
54
- // as_literal
55
- if (!(m.escapeCodes || m.maxSingle > 0 || m.newline))
56
- return "literal";
57
- // as_ml_basic_pretty
58
- if (!(m.escapeCodes || m.escape || m.maxDouble > 2))
59
- return "mlbasic";
60
- // as_ml_literal
61
- if (!(m.escapeCodes || m.maxSingle > 2))
62
- return "mlliteral";
63
- // fallback: the escaped forms
64
- return m.newline ? "mlbasic" : "basic";
65
- }
66
- /** The basic/multiline-basic escaper from `write_toml_value` (the `escaped` branch). */
67
- function escapeBasic(s, isMl) {
68
- const maxSeqDouble = isMl ? 2 : 0;
69
- let out = "";
70
- let seqDouble = 0;
71
- for (const ch of s) {
72
- const cp = ch.codePointAt(0);
73
- if (cp === 0x22) {
74
- seqDouble += 1;
75
- if (seqDouble > maxSeqDouble) {
76
- out += '\\"';
77
- seqDouble = 0;
78
- continue;
79
- }
80
- out += '"';
81
- continue;
82
- }
83
- seqDouble = 0;
84
- switch (cp) {
85
- case 0x08:
86
- out += "\\b";
87
- break;
88
- case 0x09:
89
- out += "\\t";
90
- break;
91
- case 0x0a:
92
- // A literal newline survives inside a multiline string; a basic string
93
- // escapes it.
94
- out += isMl ? "\n" : "\\n";
95
- break;
96
- case 0x0c:
97
- out += "\\f";
98
- break;
99
- case 0x0d:
100
- out += "\\r";
101
- break;
102
- case 0x5c:
103
- out += "\\\\";
104
- break;
105
- default:
106
- if (cp <= 0x1f || cp === 0x7f) {
107
- out += "\\u" + cp.toString(16).toUpperCase().padStart(4, "0");
108
- }
109
- else {
110
- out += ch;
111
- }
112
- }
113
- }
114
- return out;
115
- }
116
- /** A TOML string *value* — the exact bytes `toml_edit`'s `value(String)` emits. */
117
- export function encodeString(s) {
118
- const m = stringMetrics(s);
119
- const enc = chooseEncoding(m);
120
- const delimiter = enc === "literal" ? "'" : enc === "basic" ? '"' : enc === "mlliteral" ? "'''" : '"""';
121
- const isMl = enc === "mlliteral" || enc === "mlbasic";
122
- const escaped = enc === "basic" || enc === "mlbasic";
123
- let out = delimiter;
124
- if (m.newline && isMl)
125
- out += "\n";
126
- out += escaped ? escapeBasic(s, isMl) : s;
127
- out += delimiter;
128
- return out;
129
- }
130
- /** `KeyMetrics::calculate` — whether a key may be bare, and its escape facts. */
131
- function keyMetrics(s) {
132
- let unquoted = s.length > 0;
133
- let single = false;
134
- let double = false;
135
- let escapeCodes = false;
136
- let escape = false;
137
- for (const ch of s) {
138
- const cp = ch.codePointAt(0);
139
- const wordByte = (cp >= 0x61 && cp <= 0x7a) ||
140
- (cp >= 0x41 && cp <= 0x5a) ||
141
- (cp >= 0x30 && cp <= 0x39) ||
142
- cp === 0x2d ||
143
- cp === 0x5f;
144
- if (!wordByte)
145
- unquoted = false;
146
- if (cp === 0x27)
147
- single = true;
148
- else if (cp === 0x22)
149
- double = true;
150
- else if (cp === 0x5c)
151
- escape = true;
152
- else if (cp === 0x09) {
153
- /* tab allowed */
154
- }
155
- else if (cp <= 0x1f || cp === 0x7f)
156
- escapeCodes = true;
157
- }
158
- return { unquoted, single, double, escapeCodes, escape };
159
- }
160
- /** A TOML *key* — bare where it can be, else `toml_edit`'s `TomlKeyBuilder::as_default`. */
161
- export function encodeKey(s) {
162
- const m = keyMetrics(s);
163
- if (m.unquoted)
164
- return s;
165
- // as_basic_pretty
166
- if (!(m.escapeCodes || m.escape || m.double))
167
- return '"' + escapeBasic(s, false) + '"';
168
- // as_literal
169
- if (!(m.escapeCodes || m.single))
170
- return "'" + s + "'";
171
- // as_basic (fallback)
172
- return '"' + escapeBasic(s, false) + '"';
173
- }
174
- /** One `key = value\n` line, the key/value decor `toml_edit` renders (`key = value`). */
175
- export function keyValue(key, valueRepr) {
176
- return `${encodeKey(key)} = ${valueRepr}\n`;
177
- }
178
- /** A TOML string array — `["a", "b"]`, no leading space, `, ` between elements. */
179
- export function stringArray(values) {
180
- return "[" + values.map(encodeString).join(", ") + "]";
181
- }
182
- /** Sorted keys — the stable order `toml_edit` gets for free from its `BTreeMap`s. */
183
- export function sortedKeys(record) {
184
- return Object.keys(record).sort();
185
- }
186
- /**
187
- * Join an ordered list of table sections the `toml_edit` way — exactly one blank
188
- * line before every table header but the document's first
189
- * (`DEFAULT_TABLE_DECOR = ("\n", "")`, the first table `("", …)`). Each section is
190
- * a header line plus its `key = value\n` lines, already newline-terminated.
191
- */
192
- export function joinSections(sections) {
193
- return sections.map((section, i) => (i === 0 ? "" : "\n") + section).join("");
194
- }