@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 +52 -0
- package/dist/src/builtins.d.ts +101 -0
- package/dist/src/builtins.js +128 -1
- package/dist/src/claude-code.d.ts +2 -2
- package/dist/src/claude-code.js +1 -1
- package/dist/src/declarations.d.ts +20 -1
- package/dist/src/declarations.js +65 -1
- package/dist/src/emit.d.ts +47 -0
- package/dist/src/emit.js +43 -3
- package/dist/src/generated/CollectionAddressRow.d.ts +17 -0
- package/dist/src/generated/CollectionAddressRow.js +2 -0
- package/dist/src/generated/Declarations.d.ts +17 -0
- package/dist/src/generated/KindFactRow.d.ts +14 -0
- package/dist/src/generated/RegistrationRow.d.ts +35 -0
- package/dist/src/generated/RegistrationRow.js +2 -0
- package/dist/src/generated/SettingsRow.d.ts +25 -0
- package/dist/src/generated/SettingsRow.js +2 -0
- package/dist/src/generated/index.d.ts +3 -0
- package/dist/src/index.d.ts +2 -2
- package/dist/src/kind.d.ts +29 -4
- package/dist/src/kind.js +13 -9
- package/package.json +9 -1
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);
|
package/dist/src/builtins.d.ts
CHANGED
|
@@ -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[];
|
package/dist/src/builtins.js
CHANGED
|
@@ -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";
|
package/dist/src/claude-code.js
CHANGED
|
@@ -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
|
/**
|
package/dist/src/declarations.js
CHANGED
|
@@ -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. */
|
package/dist/src/emit.d.ts
CHANGED
|
@@ -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
|
-
/**
|
|
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
|
+
};
|
|
@@ -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,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
|
+
};
|
|
@@ -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";
|
package/dist/src/index.d.ts
CHANGED
|
@@ -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";
|
package/dist/src/kind.d.ts
CHANGED
|
@@ -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.
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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.
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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
|
|
13
|
-
*
|
|
14
|
-
* (when the kind writes its name into frontmatter) followed by the
|
|
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.
|
|
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
|
}
|