@dtmd/temper 0.0.6 → 0.0.7

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/bin/temper.js ADDED
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * The engine launcher — channel 2's npm face (`specs/distribution.md`,
4
+ * "What ships"): resolve the platform's prebuilt engine binary from its
5
+ * `optionalDependencies` package and exec it. Fail-loud invariant: a
6
+ * missing platform binary is an install error with instructions, never a
7
+ * silent skip — if it cannot check, it fails loud.
8
+ */
9
+ import { createRequire } from "node:module";
10
+ import { spawnSync } from "node:child_process";
11
+
12
+ const require = createRequire(import.meta.url);
13
+
14
+ /** platform+arch → [platform package, binary path inside it]. */
15
+ const PLATFORMS = {
16
+ "linux x64": ["@dtmd/temper-linux-x64", "bin/temper"],
17
+ "win32 x64": ["@dtmd/temper-win32-x64", "bin/temper.exe"],
18
+ };
19
+
20
+ const key = `${process.platform} ${process.arch}`;
21
+ const entry = PLATFORMS[key];
22
+
23
+ if (!entry) {
24
+ const supported = Object.keys(PLATFORMS).join(", ");
25
+ process.stderr.write(
26
+ `temper: no prebuilt engine binary for ${key} yet (prebuilt: ${supported}).\n` +
27
+ `Build from source with a Rust 1.96+ toolchain instead:\n` +
28
+ ` cargo install --git https://github.com/duct-tape-and-markdown/temper\n`,
29
+ );
30
+ process.exit(1);
31
+ }
32
+
33
+ const [pkg, binPath] = entry;
34
+ let bin;
35
+ try {
36
+ bin = require.resolve(`${pkg}/${binPath}`);
37
+ } catch {
38
+ process.stderr.write(
39
+ `temper: the platform engine package ${pkg} is not installed.\n` +
40
+ `It ships as an optionalDependency of @dtmd/temper — an installer run\n` +
41
+ `with optional dependencies disabled skips it. Restore it with:\n` +
42
+ ` npm install ${pkg}\n`,
43
+ );
44
+ process.exit(1);
45
+ }
46
+
47
+ const result = spawnSync(bin, process.argv.slice(2), { stdio: "inherit" });
48
+ if (result.error) {
49
+ process.stderr.write(`temper: failed to run ${bin}: ${result.error.message}\n`);
50
+ process.exit(1);
51
+ }
52
+ process.exit(result.status ?? 1);
@@ -97,6 +97,71 @@ export interface Memory {
97
97
  * a module-carried memory projects the root file.
98
98
  */
99
99
  export declare const memory: KindDefinition<Memory>;
100
+ /**
101
+ * A Claude Code hook — a fields-only registration member surfacing inside
102
+ * `settings.json`, keyed under its lifecycle event. It owns no artifact of its own; a
103
+ * handler names how it fires (`command`/`http`/`mcp_tool`/`prompt`/`agent`) plus the
104
+ * documented common fields (code.claude.com/docs/en/hooks, retrieved 2026-07-10).
105
+ * Authoring `hook(...)` builds a member whose typed fields fold into its manifest entry;
106
+ * emit erases it into a registration write fact (`emit.ts`).
107
+ */
108
+ export interface Hook {
109
+ /** The handler kind — how the hook fires when its event matches. */
110
+ readonly type?: "command" | "http" | "mcp_tool" | "prompt" | "agent";
111
+ /** The shell command or executable a `command` handler runs. */
112
+ readonly command?: string;
113
+ /** Seconds before the handler is canceled. */
114
+ readonly timeout?: number;
115
+ /** The tool-name filter a tool-scoped event fires on (`"*"`/`""`/absent = all). */
116
+ readonly matcher?: string;
117
+ }
118
+ /**
119
+ * `hook` — a `settings.json` `hooks.<Event>` registration member: a fields-only kind (no
120
+ * body slot), its members discovered off the `.claude/settings.json` manifest at the
121
+ * `hooks.<Event>` collection address, keyed by lifecycle event; registers on the `event`
122
+ * channel (code.claude.com/docs/en/hooks, retrieved 2026-07-10). The first manifest kind
123
+ * temper ships — the read side of 0021's manifest-authoring surface.
124
+ */
125
+ export declare const hook: KindDefinition<Hook>;
126
+ /**
127
+ * A Claude Code MCP server — a fields-only registration member surfacing inside
128
+ * `.mcp.json`, keyed by name under `mcpServers`. It owns no artifact of its own; its
129
+ * `type` names the transport (`stdio` default, or `http`/`streamable-http`/`sse`/`ws`),
130
+ * and each transport reads a different field set — `command`/`args`/`env` for a local
131
+ * stdio process, `url`/`headers` for a remote connection
132
+ * (code.claude.com/docs/en/mcp, retrieved 2026-07-10). Authoring `mcpServer(...)` builds a
133
+ * member whose typed fields fold into its `mcpServers.*` entry; emit erases it into a
134
+ * registration write fact (`emit.ts`).
135
+ */
136
+ export interface McpServer {
137
+ /**
138
+ * The transport. Absent reads as `stdio`, so an entry that carries a `url` but no
139
+ * `type` is a configuration error — Claude Code treats it as a stdio server and skips
140
+ * it. `streamable-http` is an alias for `http`.
141
+ */
142
+ readonly type?: "stdio" | "http" | "streamable-http" | "sse" | "ws";
143
+ /** The executable a stdio server runs. */
144
+ readonly command?: string;
145
+ /** The arguments passed to a stdio server's `command`. */
146
+ readonly args?: readonly string[];
147
+ /** Environment variables set in a stdio server's process. */
148
+ readonly env?: Readonly<Record<string, string>>;
149
+ /** The endpoint a remote (`http`/`sse`/`ws`) server connects to. */
150
+ readonly url?: string;
151
+ /** Static headers sent to a remote server. */
152
+ readonly headers?: Readonly<Record<string, string>>;
153
+ /** Milliseconds before a tool call to this server aborts. */
154
+ readonly timeout?: number;
155
+ }
156
+ /**
157
+ * `mcpServer` — a `.mcp.json` `mcpServers.*` registration member: a fields-only kind (no
158
+ * body slot), its members discovered off the `.mcp.json` manifest at the `mcpServers.*`
159
+ * collection address, keyed by server name; registers on the `connection` channel
160
+ * (code.claude.com/docs/en/mcp, retrieved 2026-07-10). The second manifest kind temper
161
+ * ships, and the first whose entries are objects — each server's fields fold into the
162
+ * member the read surfaces.
163
+ */
164
+ export declare const mcpServer: KindDefinition<McpServer>;
100
165
  /**
101
166
  * The default contract for `skill` — Anthropic's documented skill contract: the Agent
102
167
  * Skills open standard (agentskills.io), Anthropic's platform upload
@@ -225,3 +290,39 @@ export declare const memoryAnthropicDefaultContract: readonly Clause[];
225
290
  * (code.claude.com/docs/en/memory, retrieved 2026-07-09).
226
291
  */
227
292
  export declare const memoryAgentsMdDefaultContract: readonly Clause[];
293
+ /**
294
+ * The default contract for `hook` — Anthropic's documented hooks contract
295
+ * (code.claude.com/docs/en/hooks, retrieved 2026-07-10). A hook surfaces at
296
+ * `hooks.<Event>`, so the member the gate reads is the lifecycle event itself, its name
297
+ * carried as the `event` field off the collection key. The one decidable, cited property
298
+ * of that member is its event: a key outside the documented set is dead configuration —
299
+ * Claude Code silently never fires a hook under an unrecognized event, so the strictest
300
+ * documented profile is that the event is one temper's cited docs name.
301
+ *
302
+ * Deliberately absent — the handler's own schema (`type`/`command`/`url`/`timeout`, the
303
+ * matcher grammar) lives one array level deeper than `hooks.<Event>`, inside each event's
304
+ * matcher-group list, which the collection address does not walk into; a clause over it
305
+ * would range over a field the read never surfaces, so it is no clause at all. What the
306
+ * clauses cannot carry, as guidance: keep a handler's `type` among
307
+ * `command`/`http`/`mcp_tool`/`prompt`/`agent`; a `command` handler needs a `command`, an
308
+ * `http` handler a `url`; the `matcher` filters tool-scoped events and is inert on events
309
+ * that carry no tool (`UserPromptSubmit`, `Stop`, and their siblings).
310
+ */
311
+ export declare const hookDefaultContract: readonly Clause[];
312
+ /**
313
+ * The default contract for `mcpServer` — Anthropic's documented `.mcp.json` contract
314
+ * (code.claude.com/docs/en/mcp, retrieved 2026-07-10). A server surfaces at `mcpServers.*`,
315
+ * keyed by name, its transport-specific fields folded into the member. The one decidable,
316
+ * cited property that holds across every transport is `type`: a value outside the
317
+ * documented set is a transport Claude Code cannot honor, so the strictest documented
318
+ * profile is that a present `type` names one temper's cited docs carry. An absent `type`
319
+ * passes — Claude Code reads it as `stdio`, the documented default.
320
+ *
321
+ * Deliberately absent — the per-transport requirements are conditional on `type`, which no
322
+ * single-field clause can decide: a `url` with no `type` is a configuration error (Claude
323
+ * Code reads it as a stdio server and skips it), a stdio server needs a `command`, and a
324
+ * remote server needs a `url` — each a two-field implication the closed predicate
325
+ * vocabulary cannot express, so it rides guidance rather than a clause that would range
326
+ * over a field the shape of the check cannot see.
327
+ */
328
+ export declare const mcpServerDefaultContract: readonly Clause[];
@@ -10,7 +10,7 @@
10
10
  * — never from the root.
11
11
  */
12
12
  import { kind } from "./kind.js";
13
- import { allowedChars, clause, deny, forbiddenKeys, maxLen, maxLines, minLen, nameMatchesDir, required, uniqueName, } from "./contract.js";
13
+ import { allowedChars, clause, deny, enumOf, forbiddenKeys, maxLen, maxLines, minLen, nameMatchesDir, required, uniqueName, } from "./contract.js";
14
14
  /**
15
15
  * `skill` — `.claude/skills/<name>/SKILL.md`, a directory unit, YAML frontmatter
16
16
  * carrying `name` then `description`; registers on both documented invocation
@@ -82,6 +82,37 @@ export const memory = kind({
82
82
  unitShape: "file",
83
83
  registration: [{ via: "always" }],
84
84
  });
85
+ /**
86
+ * `hook` — a `settings.json` `hooks.<Event>` registration member: a fields-only kind (no
87
+ * body slot), its members discovered off the `.claude/settings.json` manifest at the
88
+ * `hooks.<Event>` collection address, keyed by lifecycle event; registers on the `event`
89
+ * channel (code.claude.com/docs/en/hooks, retrieved 2026-07-10). The first manifest kind
90
+ * temper ships — the read side of 0021's manifest-authoring surface.
91
+ */
92
+ export const hook = kind({
93
+ name: "hook",
94
+ locus: { kind: "at", root: ".claude", glob: "settings.json" },
95
+ unitShape: "file",
96
+ registration: [{ via: "event", field: "event" }],
97
+ shape: "fields",
98
+ collectionAddress: { manifest: "settings.json", keyPath: "hooks.<Event>" },
99
+ });
100
+ /**
101
+ * `mcpServer` — a `.mcp.json` `mcpServers.*` registration member: a fields-only kind (no
102
+ * body slot), its members discovered off the `.mcp.json` manifest at the `mcpServers.*`
103
+ * collection address, keyed by server name; registers on the `connection` channel
104
+ * (code.claude.com/docs/en/mcp, retrieved 2026-07-10). The second manifest kind temper
105
+ * ships, and the first whose entries are objects — each server's fields fold into the
106
+ * member the read surfaces.
107
+ */
108
+ export const mcpServer = kind({
109
+ name: "mcp-server",
110
+ locus: { kind: "at", root: ".", glob: ".mcp.json" },
111
+ unitShape: "file",
112
+ registration: [{ via: "connection" }],
113
+ shape: "fields",
114
+ collectionAddress: { manifest: ".mcp.json", keyPath: "mcpServers.*" },
115
+ });
85
116
  /**
86
117
  * The default contract for `skill` — Anthropic's documented skill contract: the Agent
87
118
  * Skills open standard (agentskills.io), Anthropic's platform upload
@@ -309,3 +340,99 @@ export const memoryAnthropicDefaultContract = [
309
340
  * (code.claude.com/docs/en/memory, retrieved 2026-07-09).
310
341
  */
311
342
  export const memoryAgentsMdDefaultContract = [];
343
+ /**
344
+ * Every documented Claude Code hook lifecycle event — the closed set a `hooks.<Event>`
345
+ * key is drawn from (code.claude.com/docs/en/hooks, "Hook events", retrieved 2026-07-10).
346
+ * The allowlist the `hook` default contract's one decidable clause ranges over; the
347
+ * update ritual when the docs add an event is to re-fetch and extend this set, never to
348
+ * re-derive from memory.
349
+ */
350
+ const DOCUMENTED_HOOK_EVENTS = [
351
+ "SessionStart",
352
+ "Setup",
353
+ "UserPromptSubmit",
354
+ "UserPromptExpansion",
355
+ "PreToolUse",
356
+ "PermissionRequest",
357
+ "PermissionDenied",
358
+ "PostToolUse",
359
+ "PostToolUseFailure",
360
+ "PostToolBatch",
361
+ "Notification",
362
+ "MessageDisplay",
363
+ "SubagentStart",
364
+ "SubagentStop",
365
+ "TaskCreated",
366
+ "TaskCompleted",
367
+ "Stop",
368
+ "StopFailure",
369
+ "TeammateIdle",
370
+ "InstructionsLoaded",
371
+ "ConfigChange",
372
+ "CwdChanged",
373
+ "FileChanged",
374
+ "WorktreeCreate",
375
+ "WorktreeRemove",
376
+ "PreCompact",
377
+ "PostCompact",
378
+ "Elicitation",
379
+ "ElicitationResult",
380
+ "SessionEnd",
381
+ ];
382
+ /**
383
+ * The default contract for `hook` — Anthropic's documented hooks contract
384
+ * (code.claude.com/docs/en/hooks, retrieved 2026-07-10). A hook surfaces at
385
+ * `hooks.<Event>`, so the member the gate reads is the lifecycle event itself, its name
386
+ * carried as the `event` field off the collection key. The one decidable, cited property
387
+ * of that member is its event: a key outside the documented set is dead configuration —
388
+ * Claude Code silently never fires a hook under an unrecognized event, so the strictest
389
+ * documented profile is that the event is one temper's cited docs name.
390
+ *
391
+ * Deliberately absent — the handler's own schema (`type`/`command`/`url`/`timeout`, the
392
+ * matcher grammar) lives one array level deeper than `hooks.<Event>`, inside each event's
393
+ * matcher-group list, which the collection address does not walk into; a clause over it
394
+ * would range over a field the read never surfaces, so it is no clause at all. What the
395
+ * clauses cannot carry, as guidance: keep a handler's `type` among
396
+ * `command`/`http`/`mcp_tool`/`prompt`/`agent`; a `command` handler needs a `command`, an
397
+ * `http` handler a `url`; the `matcher` filters tool-scoped events and is inert on events
398
+ * that carry no tool (`UserPromptSubmit`, `Stop`, and their siblings).
399
+ */
400
+ export const hookDefaultContract = [
401
+ clause(enumOf("event", DOCUMENTED_HOOK_EVENTS), {
402
+ severity: "required",
403
+ guidance: "A hook keys under its lifecycle event; an event outside the documented set is dead configuration — Claude Code silently never fires a hook under an unrecognized event. If this is a newly-documented event, re-fetch code.claude.com/docs/en/hooks and extend temper's cited set rather than working around the finding.",
404
+ cite: "https://code.claude.com/docs/en/hooks (retrieved 2026-07-10)",
405
+ }),
406
+ ];
407
+ /**
408
+ * Every documented `.mcp.json` server transport — the closed set a server entry's `type`
409
+ * is drawn from (code.claude.com/docs/en/mcp, retrieved 2026-07-10). `stdio` is the
410
+ * default when `type` is absent; `streamable-http` is the MCP spec's own name for `http`,
411
+ * accepted as an alias so configurations copied from server docs work unchanged; `sse` is
412
+ * documented but deprecated; `ws` is the WebSocket transport. The update ritual when the
413
+ * docs add a transport is to re-fetch and extend this set, never to re-derive from memory.
414
+ */
415
+ const DOCUMENTED_MCP_TRANSPORTS = ["stdio", "http", "streamable-http", "sse", "ws"];
416
+ /**
417
+ * The default contract for `mcpServer` — Anthropic's documented `.mcp.json` contract
418
+ * (code.claude.com/docs/en/mcp, retrieved 2026-07-10). A server surfaces at `mcpServers.*`,
419
+ * keyed by name, its transport-specific fields folded into the member. The one decidable,
420
+ * cited property that holds across every transport is `type`: a value outside the
421
+ * documented set is a transport Claude Code cannot honor, so the strictest documented
422
+ * profile is that a present `type` names one temper's cited docs carry. An absent `type`
423
+ * passes — Claude Code reads it as `stdio`, the documented default.
424
+ *
425
+ * Deliberately absent — the per-transport requirements are conditional on `type`, which no
426
+ * single-field clause can decide: a `url` with no `type` is a configuration error (Claude
427
+ * Code reads it as a stdio server and skips it), a stdio server needs a `command`, and a
428
+ * remote server needs a `url` — each a two-field implication the closed predicate
429
+ * vocabulary cannot express, so it rides guidance rather than a clause that would range
430
+ * over a field the shape of the check cannot see.
431
+ */
432
+ export const mcpServerDefaultContract = [
433
+ clause(enumOf("type", DOCUMENTED_MCP_TRANSPORTS), {
434
+ severity: "required",
435
+ guidance: "A server's `type` names its transport; a value outside the documented set is one Claude Code cannot honor. Absent reads as `stdio` — but an entry that carries a `url` with no `type` is then a configuration error, because Claude Code treats it as a stdio server and skips it: add `type: \"http\"` (or `sse`/`ws`). A stdio server needs a `command`; a remote server needs a `url`. If this is a newly-documented transport, re-fetch code.claude.com/docs/en/mcp and extend temper's cited set rather than working around the finding.",
436
+ cite: "https://code.claude.com/docs/en/mcp (retrieved 2026-07-10)",
437
+ }),
438
+ ];
@@ -7,7 +7,7 @@
7
7
  * specifier like any other. The built-in default contracts join the kinds here
8
8
  * too: adoption is `import { skill, skillDefaultContract } from "@dtmd/temper/claude-code"`.
9
9
  */
10
- export type { Agent, Memory, Rule, Skill } from "./builtins.js";
11
- export { agent, agentDefaultContract, command, commandDefaultContract, memory, memoryAgentsMdDefaultContract, memoryAnthropicDefaultContract, rule, ruleDefaultContract, skill, skillDefaultContract, } from "./builtins.js";
10
+ export type { Agent, Hook, McpServer, Memory, Rule, Skill } from "./builtins.js";
11
+ export { agent, agentDefaultContract, command, commandDefaultContract, hook, hookDefaultContract, mcpServer, mcpServerDefaultContract, memory, memoryAgentsMdDefaultContract, memoryAnthropicDefaultContract, rule, ruleDefaultContract, skill, skillDefaultContract, } from "./builtins.js";
12
12
  export type { Blocks, File, Prose, Text } from "./prose.js";
13
13
  export { blocks, file, text } from "./prose.js";
@@ -7,5 +7,5 @@
7
7
  * specifier like any other. The built-in default contracts join the kinds here
8
8
  * too: adoption is `import { skill, skillDefaultContract } from "@dtmd/temper/claude-code"`.
9
9
  */
10
- export { agent, agentDefaultContract, command, commandDefaultContract, memory, memoryAgentsMdDefaultContract, memoryAnthropicDefaultContract, rule, ruleDefaultContract, skill, skillDefaultContract, } from "./builtins.js";
10
+ export { agent, agentDefaultContract, command, commandDefaultContract, hook, hookDefaultContract, mcpServer, mcpServerDefaultContract, memory, memoryAgentsMdDefaultContract, memoryAnthropicDefaultContract, rule, ruleDefaultContract, skill, skillDefaultContract, } from "./builtins.js";
11
11
  export { blocks, file, text } from "./prose.js";
@@ -9,10 +9,29 @@
9
9
  * in lockstep.
10
10
  */
11
11
  import type { Harness } from "./assembly.js";
12
- import type { Declarations, Payload } from "./generated/index.js";
12
+ import type { Declarations, Payload, RegistrationRow, SettingsRow } from "./generated/index.js";
13
13
  export type { AssemblyFactRow, ClauseRow, Declarations, KindFactRow, RequirementRow, SatisfiesRow, } from "./generated/index.js";
14
14
  /** The stable-sort ordering every declaration row family shares. */
15
15
  export declare function compareStrings(a: string, b: string): number;
16
+ /**
17
+ * The `registration` rows — every fields-only registration member (a hook, an MCP server)
18
+ * erased for the manifest write face, kind-then-key sorted so double emit is byte-stable.
19
+ * Each carries its identity (`kind`/`key`), its collection address (`manifest`/`keyPath`,
20
+ * the wire's snake_case `key_path`), and its folded typed fields — the entry value the
21
+ * engine's write face places under `key`. The one source `emit.ts`'s public
22
+ * {@link RegistrationFact} view also maps from, so the seam and the `EmitResult` sibling
23
+ * cannot disagree on what a manifest carries.
24
+ *
25
+ * # Throws
26
+ * If a fields-only member declares no collection address — it surfaces in no host manifest.
27
+ */
28
+ export declare function registrationRows(harness: Harness): RegistrationRow[];
29
+ /**
30
+ * The `settings` rows — the assembly's harness-level residual settings keys, each folded
31
+ * into the settings.json manifest's opaque residue at emit. Key-sorted so double emit is
32
+ * byte-stable. Seam-inbound: the value lives in the projected manifest, never the lock.
33
+ */
34
+ export declare function settingsRows(harness: Harness): SettingsRow[];
16
35
  /** Every requirement name a `satisfies` claim may fill — assembly `require` ∪ member `requires`. */
17
36
  export declare function declaredRequirements(harness: Harness): Set<string>;
18
37
  /**
@@ -152,10 +152,21 @@ function contentRow(content) {
152
152
  });
153
153
  return { regions };
154
154
  }
155
+ /**
156
+ * Lower a kind's declared {@link CollectionAddress} into its `collection_address` row —
157
+ * `keyPath` spelled as the wire's snake_case `key_path`. `undefined` for a file-locus
158
+ * kind, so its row omits the column and stays byte-identical.
159
+ */
160
+ function collectionAddressRow(facts) {
161
+ if (facts.collectionAddress === undefined)
162
+ return undefined;
163
+ return { manifest: facts.collectionAddress.manifest, key_path: facts.collectionAddress.keyPath };
164
+ }
155
165
  /**
156
166
  * One kind's fact row — the `at` locus supplies `governs_root`/`governs_glob`,
157
167
  * `templates` names the embedded kinds (among `allKinds`) declared within it, and
158
- * `content` lowers a declared layout (absent for a `file`-content kind).
168
+ * `content` lowers a declared layout (absent for a `file`-content kind). A registration
169
+ * kind extends the row with its `shape` marker and `collection_address`.
159
170
  */
160
171
  function kindFactRow(facts, allKinds) {
161
172
  if (facts.locus.kind !== "at") {
@@ -173,6 +184,8 @@ function kindFactRow(facts, allKinds) {
173
184
  registration: registrationLabels(facts.registration),
174
185
  templates: templatesFor(facts.name, allKinds),
175
186
  content: contentRow(facts.content),
187
+ shape: facts.shape,
188
+ collection_address: collectionAddressRow(facts),
176
189
  };
177
190
  }
178
191
  /** Every kind in play, at any locus — member kinds ∪ expect kinds ∪ their embedded children. */
@@ -402,6 +415,55 @@ function nestedMemberRows(harness, mentionable) {
402
415
  }
403
416
  return rows.sort((a, b) => compareStrings(a.host, b.host) || compareStrings(a.kind, b.kind) || compareStrings(a.key, b.key));
404
417
  }
418
+ /**
419
+ * The `registration` rows — every fields-only registration member (a hook, an MCP server)
420
+ * erased for the manifest write face, kind-then-key sorted so double emit is byte-stable.
421
+ * Each carries its identity (`kind`/`key`), its collection address (`manifest`/`keyPath`,
422
+ * the wire's snake_case `key_path`), and its folded typed fields — the entry value the
423
+ * engine's write face places under `key`. The one source `emit.ts`'s public
424
+ * {@link RegistrationFact} view also maps from, so the seam and the `EmitResult` sibling
425
+ * cannot disagree on what a manifest carries.
426
+ *
427
+ * # Throws
428
+ * If a fields-only member declares no collection address — it surfaces in no host manifest.
429
+ */
430
+ export function registrationRows(harness) {
431
+ return harness.members
432
+ .filter((member) => member.facts.shape === "fields")
433
+ .map((member) => {
434
+ const address = member.facts.collectionAddress;
435
+ if (address === undefined) {
436
+ throw new Error(`member \`${member.name}\`: a fields-only registration kind declares no ` +
437
+ `collection address — it surfaces in no host manifest (specs/model/pipeline.md, "The SDK").`);
438
+ }
439
+ return {
440
+ kind: member.kind,
441
+ key: member.name,
442
+ manifest: address.manifest,
443
+ key_path: address.keyPath,
444
+ // The generated row carries a mutable field list; the member's is read-only, so
445
+ // copy each pair into a fresh tuple — the same values, a shape the row accepts.
446
+ fields: member.fields.map(([name, value]) => [name, value]),
447
+ };
448
+ })
449
+ .sort((a, b) => compareStrings(a.kind, b.kind) || compareStrings(a.key, b.key));
450
+ }
451
+ /**
452
+ * The manifest Claude Code's harness-level settings reside in — the file the assembly's
453
+ * residual settings keys fold into as opaque residue, the same manifest the `hook` kind's
454
+ * registrations surface inside (code.claude.com/docs/en/settings, retrieved 2026-07-10).
455
+ */
456
+ const SETTINGS_MANIFEST = "settings.json";
457
+ /**
458
+ * The `settings` rows — the assembly's harness-level residual settings keys, each folded
459
+ * into the settings.json manifest's opaque residue at emit. Key-sorted so double emit is
460
+ * byte-stable. Seam-inbound: the value lives in the projected manifest, never the lock.
461
+ */
462
+ export function settingsRows(harness) {
463
+ return Object.entries(harness.settings)
464
+ .map(([key, value]) => ({ manifest: SETTINGS_MANIFEST, key, value }))
465
+ .sort((a, b) => compareStrings(a.key, b.key));
466
+ }
405
467
  /** Every requirement name a `satisfies` claim may fill — assembly `require` ∪ member `requires`. */
406
468
  export function declaredRequirements(harness) {
407
469
  const set = new Set();
@@ -444,6 +506,8 @@ export function compileDeclarations(harness) {
444
506
  mentions: mentionRows(harness),
445
507
  includes: includeRows(harness),
446
508
  nested_members: nestedMemberRows(harness, declaredAddresses(harness)),
509
+ registrations: registrationRows(harness),
510
+ settings: settingsRows(harness),
447
511
  };
448
512
  }
449
513
  /** The SDK's pinned engine/interchange version — the JSON pipe rides it in lockstep. */
@@ -17,6 +17,40 @@ export interface ResolveOptions {
17
17
  /** The addresses a mention may name — resolution-checked; a mention cannot dangle. */
18
18
  readonly mentionable?: ReadonlySet<string>;
19
19
  }
20
+ /**
21
+ * One fields-only registration member erased for the manifest write face: its key
22
+ * (a hook's lifecycle event, an MCP server's name), the collection address it keys
23
+ * at, and its folded typed fields — the same declaration-row shape the engine write
24
+ * face reads back off a manifest (`json_manifest.rs`'s `RegistrationMember`). Carried
25
+ * from the composing program, never mined from a projection.
26
+ */
27
+ export interface RegistrationFact {
28
+ /** The erased registration kind — `hook`, `mcp-server` — joining `declarations.kinds`. */
29
+ readonly kind: string;
30
+ /** The member's key among its collection's entries — a hook's event, a server's name. */
31
+ readonly key: string;
32
+ /** The manifest collection address the registration surfaces at. */
33
+ readonly collectionAddress: {
34
+ readonly manifest: string;
35
+ readonly keyPath: string;
36
+ };
37
+ /** The member's folded typed fields, in the author's declared order. */
38
+ readonly fields: ReadonlyArray<readonly [string, unknown]>;
39
+ }
40
+ /**
41
+ * One harness-level settings-residue key erased for the manifest write face: the manifest
42
+ * it surfaces in, its opaque key, and its value — the entry `emit` folds into the manifest's
43
+ * residue beside its registration members' collection segments. Carried from the composing
44
+ * program, never mined from a projection.
45
+ */
46
+ export interface SettingsResidue {
47
+ /** The host manifest the residue key surfaces in (`settings.json`). */
48
+ readonly manifest: string;
49
+ /** The opaque top-level manifest key with no member home. */
50
+ readonly key: string;
51
+ /** The key's opaque value, placed verbatim into the manifest's residue. */
52
+ readonly value: unknown;
53
+ }
20
54
  /**
21
55
  * A full emit's compiled outputs — the whole seam the engine reads.
22
56
  * A pure function of the harness, so [`emit`]
@@ -39,6 +73,19 @@ export interface EmitResult {
39
73
  * data until then.
40
74
  */
41
75
  readonly permissions: readonly string[];
76
+ /**
77
+ * The fields-only registration members erased for the manifest write face — each
78
+ * a name, its collection address, and its folded fields. Folds into the manifest
79
+ * artifacts once the engine write face lands; carried here as data until then, the
80
+ * way `permissions` is.
81
+ */
82
+ readonly registrations: readonly RegistrationFact[];
83
+ /**
84
+ * The harness-level settings residue erased for the manifest write face — each an opaque
85
+ * settings.json key and its value. Folds into the settings.json manifest's residue at
86
+ * emit, the way `registrations` builds its collection segments; carried here as data too.
87
+ */
88
+ readonly settings: readonly SettingsResidue[];
42
89
  }
43
90
  /**
44
91
  * Compile the whole face in one deterministic pass: the declaration rows (its
package/dist/src/emit.js CHANGED
@@ -12,7 +12,7 @@ import { fileURLToPath } from "node:url";
12
12
  import { readFileSync } from "node:fs";
13
13
  import { renderText, resolveLeaf } from "./prose.js";
14
14
  import { permissionUnion } from "./needs.js";
15
- import { compareStrings, compileDeclarations, declaredAddresses, declaredRequirements, encodeSeam, } from "./declarations.js";
15
+ import { compareStrings, compileDeclarations, declaredAddresses, declaredRequirements, encodeSeam, registrationRows, settingsRows, } from "./declarations.js";
16
16
  /**
17
17
  * TOML-quote a leaf's authored text into a basic-string literal — the escapes
18
18
  * `toml_edit`'s parser reads back (backslash, quote, and the C0 control set),
@@ -191,9 +191,21 @@ function refuseBrokenSource(harness) {
191
191
  }
192
192
  }
193
193
  }
194
- /** A member is projected iff its kind lives at a path locus (an embedded member is not). */
194
+ /**
195
+ * A fields-only registration member (a hook, an MCP server) surfaces embedded in a
196
+ * host manifest, so it owns no standalone artifact — its facts erase into a
197
+ * {@link RegistrationFact} for the manifest write face, never a projected member.
198
+ */
199
+ function isRegistration(member) {
200
+ return member.facts.shape === "fields";
201
+ }
202
+ /**
203
+ * A member is projected iff its kind lives at a path locus and is not a fields-only
204
+ * registration member — an embedded member and a registration member each carry no
205
+ * standalone projection.
206
+ */
195
207
  function isProjected(member) {
196
- return member.facts.locus.kind === "at";
208
+ return member.facts.locus.kind === "at" && !isRegistration(member);
197
209
  }
198
210
  /**
199
211
  * The resolved absolute path of a `file()` prose asset, or `undefined` for
@@ -211,6 +223,32 @@ function fileSourcePath(member) {
211
223
  return undefined;
212
224
  return fileURLToPath(new URL(prose.path, prose.moduleUrl));
213
225
  }
226
+ /**
227
+ * The harness's fields-only registration members as the public {@link RegistrationFact}
228
+ * view — the seam's own `registration` rows ({@link registrationRows}) mapped to the
229
+ * nested `collectionAddress` shape the `EmitResult` sibling exposes, so the two cannot
230
+ * disagree on what a manifest carries. Kind-then-key ordered so double emit is byte-stable.
231
+ *
232
+ * # Throws
233
+ * If a fields-only member declares no collection address — it surfaces in no manifest.
234
+ */
235
+ function registrationFacts(harness) {
236
+ return registrationRows(harness).map((row) => ({
237
+ kind: row.kind,
238
+ key: row.key,
239
+ collectionAddress: { manifest: row.manifest, keyPath: row.key_path },
240
+ fields: row.fields,
241
+ }));
242
+ }
243
+ /**
244
+ * The harness's residual settings keys as the public {@link SettingsResidue} view — the
245
+ * seam's own `settings` rows ({@link settingsRows}) surfaced under the `EmitResult` sibling,
246
+ * so the two cannot disagree on what a manifest's residue carries. Key-sorted, the same
247
+ * byte-stable order the seam family takes.
248
+ */
249
+ function settingsResidue(harness) {
250
+ return settingsRows(harness).map((row) => ({ manifest: row.manifest, key: row.key, value: row.value }));
251
+ }
214
252
  /** The harness's projected members as payload members, deterministically kind-then-name ordered. */
215
253
  function orderedMembers(harness, options) {
216
254
  return [...harness.members]
@@ -246,6 +284,8 @@ export function emit(harness) {
246
284
  members,
247
285
  seam: encodeSeam({ declarations, members }),
248
286
  permissions: permissionUnion(harness.members.flatMap((member) => [...member.needs])),
287
+ registrations: registrationFacts(harness),
288
+ settings: settingsResidue(harness),
249
289
  };
250
290
  };
251
291
  const first = compile();
@@ -0,0 +1,17 @@
1
+ /**
2
+ * A kind's declared **collection address** row — the manifest a registration member
3
+ * surfaces in and the key path it keys at, the presence-coupled pair
4
+ * [`KindFactRow::collection_address`] carries. Absent from a [`KindFactRow`] means the
5
+ * kind owns its own file locus.
6
+ */
7
+ export type CollectionAddressRow = {
8
+ /**
9
+ * The host manifest the registration surfaces in (`settings.json`, `.mcp.json`).
10
+ */
11
+ manifest: string;
12
+ /**
13
+ * The manifest key path the registration keys at (`hooks.<Event>`, `mcpServers.*`), a
14
+ * closed vocabulary the engine's kind lift rejects an unknown value from.
15
+ */
16
+ key_path: string;
17
+ };
@@ -0,0 +1,2 @@
1
+ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
2
+ export {};
@@ -4,8 +4,10 @@ import type { IncludeRow } from "./IncludeRow.js";
4
4
  import type { KindFactRow } from "./KindFactRow.js";
5
5
  import type { MentionRow } from "./MentionRow.js";
6
6
  import type { NestedMemberRow } from "./NestedMemberRow.js";
7
+ import type { RegistrationRow } from "./RegistrationRow.js";
7
8
  import type { RequirementRow } from "./RequirementRow.js";
8
9
  import type { SatisfiesRow } from "./SatisfiesRow.js";
10
+ import type { SettingsRow } from "./SettingsRow.js";
9
11
  /**
10
12
  * The lock's **declaration-row family** — the composed program's erased declarations,
11
13
  * beside the
@@ -69,4 +71,19 @@ export type Declarations = {
69
71
  * (0018, "the projection is not the database").
70
72
  */
71
73
  nested_members: Array<NestedMemberRow>;
74
+ /**
75
+ * The fields-only registration members the SDK erased for the manifest write face —
76
+ * seam-inbound carrying their folded fields, so `emit` routes each host manifest whole
77
+ * through the canonical write face. The lock's `registration` family records only each
78
+ * member's identity and collection address; the fields live in the projected manifest
79
+ * artifact, never a second copy read back (0018), so a lock round-trip reads them
80
+ * fieldless.
81
+ */
82
+ registrations: Array<RegistrationRow>;
83
+ /**
84
+ * The harness-level settings residue — seam-inbound opaque `settings.json` keys with
85
+ * no member home, folded into their manifest's residue at emit. Like `includes`, never
86
+ * written into this declaration table, so a lock round-trip reads none.
87
+ */
88
+ settings: Array<SettingsRow>;
72
89
  };
@@ -1,3 +1,4 @@
1
+ import type { CollectionAddressRow } from "./CollectionAddressRow.js";
1
2
  import type { LayoutRow } from "./LayoutRow.js";
2
3
  /**
3
4
  * One kind's declaration row — its identity and declared runtime facts.
@@ -48,4 +49,17 @@ export type KindFactRow = {
48
49
  * kind whose body is a declared layout over its heading tree.
49
50
  */
50
51
  content?: LayoutRow;
52
+ /**
53
+ * The **fields-only** body-shape marker — `fields` for a no-body-slot kind (a hook,
54
+ * an MCP server); absent for a body-bearing kind, whose body is `file` or a `content`
55
+ * layout. The tolerant `#[serde(default)]` round-trip the rest of the optional facts
56
+ * take, so a body-bearing kind's row stays byte-identical.
57
+ */
58
+ shape?: string;
59
+ /**
60
+ * The declared **collection address** — for a registration member surfacing inside a
61
+ * host manifest, which manifest and which key path it keys at. Absent for a
62
+ * file-locus kind, so an ordinary row stays byte-identical.
63
+ */
64
+ collection_address?: CollectionAddressRow;
51
65
  };
@@ -0,0 +1,35 @@
1
+ /**
2
+ * One fields-only registration member the SDK erased for the manifest write face — a
3
+ * hook, an MCP server — carried across the seam so `emit` routes its host manifest whole
4
+ * through the canonical write face ([`crate::json_manifest::write_manifest`]) rather than
5
+ * the unrepresented in-place splice. `kind`/`key` are the member's identity; `manifest`/
6
+ * `key_path` name the collection address it surfaces at; `fields` are its folded typed
7
+ * fields — the entry value the write face places under `key`.
8
+ *
9
+ * **Seam-inbound with `fields`.** The lock's `registration` declaration family records
10
+ * only the identity and address: the fields live in the projected manifest artifact, never
11
+ * a second copy the engine reads back (0018, "the projection is not the database"), so a
12
+ * row read back off the lock carries an empty `fields`.
13
+ */
14
+ export type RegistrationRow = {
15
+ /**
16
+ * The registration kind's bare name — `hook`, `mcp-server` — joining `declarations.kinds`.
17
+ */
18
+ kind: string;
19
+ /**
20
+ * The member's key among its collection's entries — a hook's event, a server's name.
21
+ */
22
+ key: string;
23
+ /**
24
+ * The host manifest the registration surfaces in (`settings.json`, `.mcp.json`).
25
+ */
26
+ manifest: string;
27
+ /**
28
+ * The manifest key-path label the registration keys at (`hooks.<Event>`, `mcpServers.*`).
29
+ */
30
+ key_path: string;
31
+ /**
32
+ * The member's folded typed fields — seam-inbound only, dropped from the lock row.
33
+ */
34
+ fields: Array<[string, unknown]>;
35
+ };
@@ -0,0 +1,2 @@
1
+ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
2
+ export {};
@@ -0,0 +1,25 @@
1
+ /**
2
+ * One harness-level settings-residue key the SDK erased for the manifest write face — an
3
+ * opaque top-level key of the manifest it names (Claude Code's `settings.json`) with no
4
+ * typed member kind of its own yet. Carried across the seam so `emit` folds it into that
5
+ * manifest's opaque residue beside the collection segments its registration members build.
6
+ *
7
+ * **Seam-inbound with `value`.** Like a composed-prose include, this row is consumed at
8
+ * emit and never written into the lock's declaration table: the value lives in the
9
+ * projected manifest artifact, never a second copy the engine reads back (0018), so a lock
10
+ * round-trip carries none.
11
+ */
12
+ export type SettingsRow = {
13
+ /**
14
+ * The host manifest the residue key surfaces in (`settings.json`).
15
+ */
16
+ manifest: string;
17
+ /**
18
+ * The residue key — an opaque top-level manifest key with no member home.
19
+ */
20
+ key: string;
21
+ /**
22
+ * The key's opaque JSON value, placed verbatim into the manifest's residue.
23
+ */
24
+ value: unknown;
25
+ };
@@ -0,0 +1,2 @@
1
+ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
2
+ export {};
@@ -2,6 +2,7 @@ export type { AssemblyFactRow } from "./AssemblyFactRow.js";
2
2
  export type { BoundRow } from "./BoundRow.js";
3
3
  export type { CharsetRow } from "./CharsetRow.js";
4
4
  export type { ClauseRow } from "./ClauseRow.js";
5
+ export type { CollectionAddressRow } from "./CollectionAddressRow.js";
5
6
  export type { CollectionEntryRow } from "./CollectionEntryRow.js";
6
7
  export type { CollectionEntryWire } from "./CollectionEntryWire.js";
7
8
  export type { CountBoundRow } from "./CountBoundRow.js";
@@ -22,8 +23,10 @@ export type { NestedMemberRow } from "./NestedMemberRow.js";
22
23
  export type { Payload } from "./Payload.js";
23
24
  export type { PayloadMember } from "./PayloadMember.js";
24
25
  export type { RangeBoundRow } from "./RangeBoundRow.js";
26
+ export type { RegistrationRow } from "./RegistrationRow.js";
25
27
  export type { RequirementRow } from "./RequirementRow.js";
26
28
  export type { SatisfiesRow } from "./SatisfiesRow.js";
27
29
  export type { Section } from "./Section.js";
28
30
  export type { SectionContainsRow } from "./SectionContainsRow.js";
31
+ export type { SettingsRow } from "./SettingsRow.js";
29
32
  export type { ValueType } from "./ValueType.js";
@@ -18,11 +18,11 @@ export type { Capability } from "./needs.js";
18
18
  export { bash, capability, permissionUnion } from "./needs.js";
19
19
  export type { Charset, Clause, Predicate, Requirement, Severity } from "./contract.js";
20
20
  export { allowedChars, clause, count, degree, deny, enumOf, forbiddenKeys, maxLen, maxLines, membership, minLen, mustDefine, nameMatchesDir, optional, range, required, requireSections, requirement, sectionContains, type, unique, uniqueName, } from "./contract.js";
21
- export type { EdgeField, EmbeddedMemberCollectionEntry, EmbeddedMemberValue, Format, KindDefinition, KindFacts, Layout, LayoutRegion, Locus, Member, MemberInit, Registration, UnitShape, } from "./kind.js";
21
+ export type { CollectionAddress, EdgeField, EmbeddedMemberCollectionEntry, EmbeddedMemberValue, Format, KindDefinition, KindFacts, Layout, LayoutRegion, Locus, Member, MemberInit, Registration, Shape, UnitShape, } from "./kind.js";
22
22
  export { embeddedMemberValue, kind } from "./kind.js";
23
23
  export type { EnforcementMode, ExpectBinding, Harness } from "./assembly.js";
24
24
  export { harness } from "./assembly.js";
25
25
  export type { AssemblyFactRow, ClauseRow, Declarations, KindFactRow, RequirementRow, SatisfiesRow, } from "./declarations.js";
26
26
  export { SEAM_VERSION, compileDeclarations } from "./declarations.js";
27
- export type { EmitResult, PayloadMember, ResolveOptions } from "./emit.js";
27
+ export type { EmitResult, PayloadMember, RegistrationFact, ResolveOptions } from "./emit.js";
28
28
  export { emit } from "./emit.js";
@@ -1,10 +1,12 @@
1
1
  /**
2
2
  * Kinds — the engine room. A kind is a plain typed surface — an interface
3
3
  * `T` and a constructor `kind<T>()` — plus six facts of runtime residue: label,
4
- * locus, layout, registration, edge fields, and content. `tsc` is the keystroke wall;
5
- * every type erases at the seam, and what a kind leaves behind is those six facts,
6
- * riding the lock as rows. Identity travels by import, never by string — a `kind`
7
- * reference is the imported value.
4
+ * locus, layout, registration, edge fields, and content. A registration kind (a hook,
5
+ * an MCP server) extends the content fact with a fields-only `shape` and a
6
+ * `collectionAddress` naming the host manifest it surfaces in. `tsc` is the keystroke
7
+ * wall; every type erases at the seam, and what a kind leaves behind rides the lock as
8
+ * rows. Identity travels by import, never by string — a `kind` reference is the imported
9
+ * value.
8
10
  */
9
11
  import type { Prose, Text } from "./prose.js";
10
12
  import type { Capability } from "./needs.js";
@@ -86,6 +88,23 @@ export type LayoutRegion = {
86
88
  export interface Layout {
87
89
  readonly regions: readonly LayoutRegion[];
88
90
  }
91
+ /**
92
+ * A kind's **body shape** marker — `"fields"` for a fields-only kind: no body slot at
93
+ * all, the member its typed fields and edges and nothing more (a hook, an MCP server).
94
+ * Absent leaves the kind body-bearing, its body `file` (the default) or a declared
95
+ * {@link Layout}.
96
+ */
97
+ export type Shape = "fields";
98
+ /**
99
+ * A registration member's **collection address** — where inside a host manifest its
100
+ * registration surfaces: which `manifest` (`settings.json`, `.mcp.json`) and which
101
+ * `keyPath` (`hooks.<Event>`, `mcpServers.*`) it keys at. Carried by a fields-only
102
+ * registration kind; absent for a kind that owns its own file locus.
103
+ */
104
+ export interface CollectionAddress {
105
+ readonly manifest: string;
106
+ readonly keyPath: "hooks.<Event>" | "mcpServers.*";
107
+ }
89
108
  /** The six facts of a kind's runtime residue. */
90
109
  export interface KindFacts {
91
110
  /** Fact 1, label — the compiled debug label findings speak; the kind's name. */
@@ -115,6 +134,12 @@ export interface KindFacts {
115
134
  /** Fact 6, content — a declared {@link Layout} over the body's heading tree; absent
116
135
  * leaves the kind `file`-content (one verbatim prose body, the default). */
117
136
  readonly content?: Layout;
137
+ /** Fact 6b, content — the fields-only body shape (`"fields"`, no body slot); absent
138
+ * leaves the kind body-bearing (`file` or a {@link Layout}). */
139
+ readonly shape?: Shape;
140
+ /** The registration member's {@link CollectionAddress} — which manifest and key path
141
+ * its registration surfaces at; absent for a kind that owns its own file locus. */
142
+ readonly collectionAddress?: CollectionAddress;
118
143
  }
119
144
  /**
120
145
  * One authored member — a typed value in the library. Kind identity travels by
package/dist/src/kind.js CHANGED
@@ -1,21 +1,25 @@
1
1
  /**
2
2
  * Kinds — the engine room. A kind is a plain typed surface — an interface
3
3
  * `T` and a constructor `kind<T>()` — plus six facts of runtime residue: label,
4
- * locus, layout, registration, edge fields, and content. `tsc` is the keystroke wall;
5
- * every type erases at the seam, and what a kind leaves behind is those six facts,
6
- * riding the lock as rows. Identity travels by import, never by string — a `kind`
7
- * reference is the imported value.
4
+ * locus, layout, registration, edge fields, and content. A registration kind (a hook,
5
+ * an MCP server) extends the content fact with a fields-only `shape` and a
6
+ * `collectionAddress` naming the host manifest it surfaces in. `tsc` is the keystroke
7
+ * wall; every type erases at the seam, and what a kind leaves behind rides the lock as
8
+ * rows. Identity travels by import, never by string — a `kind` reference is the imported
9
+ * value.
8
10
  */
9
11
  /** The framework keys of a member init — everything else is a typed field (flat). */
10
12
  const FRAMEWORK_KEYS = new Set(["name", "prose", "satisfies", "requires", "needs"]);
11
13
  /**
12
- * Build the ordered projected-frontmatter fields for a member: nothing for a
13
- * frontmatterless kind (memory declares no `format`), else the identity field
14
- * (when the kind writes its name into frontmatter) followed by the typed fields
15
- * in the author's declared order.
14
+ * Build the ordered projected fields for a member: nothing for a frontmatterless
15
+ * body-bearing kind (memory declares no `format` and is not fields-only), else the
16
+ * identity field (when the kind writes its name into frontmatter) followed by the
17
+ * typed fields in the author's declared order. A fields-only registration kind (a
18
+ * hook, an MCP server) carries its typed fields though it declares no `format` —
19
+ * the fields are the whole member, folded into a manifest entry, never a header.
16
20
  */
17
21
  function orderedFields(facts, init) {
18
- if (facts.format === undefined)
22
+ if (facts.format === undefined && facts.shape !== "fields")
19
23
  return [];
20
24
  const typed = [];
21
25
  for (const [key, value] of Object.entries(init)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dtmd/temper",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
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": {
@@ -32,6 +32,7 @@
32
32
  },
33
33
  "files": [
34
34
  "dist/src",
35
+ "bin",
35
36
  "README.md"
36
37
  ],
37
38
  "publishConfig": {
@@ -46,5 +47,12 @@
46
47
  "devDependencies": {
47
48
  "@types/node": "^22.0.0",
48
49
  "typescript": "^5.6.0"
50
+ },
51
+ "bin": {
52
+ "temper": "bin/temper.js"
53
+ },
54
+ "optionalDependencies": {
55
+ "@dtmd/temper-linux-x64": "0.0.7",
56
+ "@dtmd/temper-win32-x64": "0.0.7"
49
57
  }
50
58
  }