@dtmd/temper 0.0.9 → 0.0.11

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.
@@ -29,11 +29,11 @@ export interface Admission {
29
29
  readonly admits: readonly KindDefinition<never>[];
30
30
  }
31
31
  /**
32
- * The declared enforcement-mode vocabulary — how firmly the `PreToolUse` guard
33
- * binds a tool call, split by where the finding goes.
34
- * `block`: denies the call.
35
- * `warn` (default): allows the call and surfaces the finding in-band, into the
36
- * live context. `note`: allows the call and records the finding out-of-band
32
+ * The declared enforcement-mode vocabulary — how firmly a guard binds an
33
+ * intercepted action, split by where the finding goes.
34
+ * `block`: denies the action.
35
+ * `warn` (default): allows the action and surfaces the finding in-band, into the
36
+ * live context. `note`: allows the action and records the finding out-of-band
37
37
  * only — the next report, never the session.
38
38
  */
39
39
  export type EnforcementMode = "note" | "warn" | "block";
@@ -300,6 +300,12 @@ export interface Hook {
300
300
  /** The tool-name filter a tool-scoped event fires on (`"*"`/`""`/absent = all). */
301
301
  readonly matcher?: string;
302
302
  }
303
+ /**
304
+ * The manifest Claude Code's harness-level settings reside in — the file the assembly's
305
+ * residual settings keys fold into as opaque residue, the same manifest the `hook` kind's
306
+ * registrations surface inside (code.claude.com/docs/en/settings, retrieved 2026-07-16).
307
+ */
308
+ export declare const SETTINGS_MANIFEST = "settings.json";
303
309
  /**
304
310
  * `hook` — a `settings.json` `hooks.<Event>` registration member: a fields-only kind (no
305
311
  * body slot), its members discovered off the `.claude/settings.json` manifest at the
@@ -569,11 +575,6 @@ export declare const pluginManifest: KindDefinition<PluginManifest>;
569
575
  *
570
576
  * Deliberately absent as undecidable: whether the `description` reads well, whether
571
577
  * `keywords` aid discovery, whether `name` names the pack aptly.
572
- *
573
- * Authoring notes the clauses cannot carry: leave `version` unset while iterating, so the
574
- * commit SHA drives updates and users are not stranded on a stale pin; set it once the
575
- * plugin has a release cycle, and bump it every time — pushing commits without bumping is
576
- * a no-op. Reach for `defaultEnabled: false` when the plugin costs money or scope on load.
577
578
  */
578
579
  export declare const pluginManifestDefaultContract: readonly Clause[];
579
580
  /**
@@ -713,14 +714,6 @@ export declare const marketplace: KindDefinition<Marketplace>;
713
714
  * there is no predicate that decides it, and a clause that guessed would fire on true
714
715
  * negatives. The enumerated deny list is the decidable subset; the impersonation rule rides
715
716
  * as guidance below.
716
- *
717
- * Authoring notes the clauses cannot carry: a relative-path `source` resolves against a
718
- * *local copy* of the marketplace, so it silently fails to resolve for users who added the
719
- * marketplace by direct URL to `marketplace.json` — only that one file is downloaded. Reach
720
- * for `github`, `url`, or `npm` when the catalog is distributed by URL. Where a git source
721
- * pins both `ref` and `sha`, the `sha` is the effective pin. A marketplace entry's
722
- * `defaultEnabled` beats the same field in the plugin's own `plugin.json`, while `version`
723
- * runs the other way — `plugin.json` wins.
724
717
  */
725
718
  export declare const marketplaceDefaultContract: readonly Clause[];
726
719
  /**
@@ -803,13 +796,6 @@ export declare const settingsLocalDefaultContract: readonly Clause[];
803
796
  * vagueness/no-op detection (semantic); gerund naming (judgment). Nothing
804
797
  * decidable is held: the name's hyphen placement and the platform's "no XML
805
798
  * tags in the description" are the two `shape` clauses below.
806
- *
807
- * Authoring notes the clauses cannot carry: prefer gerund or noun-phrase
808
- * names (`processing-pdfs`, `pdf-processing`) over vague ones (`helper`,
809
- * `utils`); `disable-model-invocation: true` for side-effectful workflows you
810
- * want to time yourself; `user-invocable: false` for background knowledge
811
- * that is not a command; `metadata` is the sanctioned home for versioning —
812
- * there is no top-level `version` field.
813
799
  */
814
800
  export declare const skillDefaultContract: readonly Clause[];
815
801
  /**
@@ -865,16 +851,6 @@ export declare const agentDefaultContract: readonly Clause[];
865
851
  * own: `required` is the one
866
852
  * presence predicate, and its absence is not itself a predicate.)
867
853
  * https://code.claude.com/docs/en/memory#path-specific-rules (retrieved 2026-07-15)
868
- *
869
- * What the clauses cannot carry, as guidance: keep a rule to facts Claude
870
- * should hold whenever the rule is in scope — concrete enough to verify ("use
871
- * 2-space indentation", not "format code properly"). If an entry is a
872
- * multi-step procedure or only matters occasionally, it belongs in a skill
873
- * (on-demand) rather than a rule (always-on). Prefer path-scoped rules when
874
- * one convention governs scattered paths; prefer per-directory CLAUDE.md when
875
- * directory owners maintain their own. Treat rules like code: prune them when
876
- * behavior drifts, and test a change by watching whether Claude's behavior
877
- * actually shifts.
878
854
  */
879
855
  export declare const ruleDefaultContract: readonly Clause[];
880
856
  /**
@@ -887,21 +863,6 @@ export declare const ruleDefaultContract: readonly Clause[];
887
863
  * schema to gate — manufacturing a required field or a forbidden-key list
888
864
  * would fake a check the format does not carry. The single clause is a context-cost
889
865
  * budget; everything else the contract could say is guidance.
890
- *
891
- * What the clauses cannot carry, as guidance: a `paths:` frontmatter block
892
- * belongs on a `.claude/rules/*.md` file, not on `CLAUDE.md` — the memory
893
- * docs document `paths` only for rules, so a rules-style header on
894
- * `CLAUDE.md` is dead configuration. Split a large file with `@path` imports
895
- * (resolved relative to the importing file, absolute allowed, recursion
896
- * capped at four hops; wrap a path in backticks to mention it without
897
- * importing). If the repo already ships an `AGENTS.md` for other agents,
898
- * don't duplicate it — create a `CLAUDE.md` that `@AGENTS.md`-imports it (or
899
- * symlink, except on Windows where the import is the recommended bridge).
900
- * Mind the loading asymmetry: every ancestor `CLAUDE.md` loads in full at
901
- * launch, while files in subdirectories load only when Claude reads a file
902
- * there — so a rule that must always hold belongs above the working
903
- * directory, not below it. Personal, un-shared notes go in `CLAUDE.local.md`
904
- * (gitignored), appended after `CLAUDE.md` at its level.
905
866
  */
906
867
  export declare const memoryAnthropicDefaultContract: readonly Clause[];
907
868
  /**
@@ -920,12 +881,29 @@ export declare const memoryAnthropicDefaultContract: readonly Clause[];
920
881
  * vocabulary's when/enumOf/type extensions, addressing still cannot spell a path into the
921
882
  * handler array (e.g., `hooks.<Event>[0].type`). A clause over it would range over a field
922
883
  * the read never surfaces, so it is no clause at all — the addressing-reach gap remains.
923
- * What the clauses cannot carry, as guidance: keep a handler's `type` among
924
- * `command`/`http`/`mcp_tool`/`prompt`/`agent`; a `command` handler needs a `command`, an
925
- * `http` handler a `url`; the `matcher` filters tool-scoped events and is inert on events
926
- * that carry no tool (`UserPromptSubmit`, `Stop`, and their siblings).
927
884
  */
928
885
  export declare const hookDefaultContract: readonly Clause[];
886
+ /**
887
+ * The `settings.json` lifecycle event and matcher one documented telemetry event-name
888
+ * projects its tap hook at. The event-name is the author-facing token a telemetry
889
+ * verifier names (`contract.ts`'s `telemetry`, the `roster.rs` admissibility set); the
890
+ * `event` is the `hooks.<Event>` key the tap registers under, and the `matcher` scopes
891
+ * the fire to the telemetry-relevant subset — each an external fact
892
+ * (code.claude.com/docs/en/hooks, retrieved 2026-07-17):
893
+ *
894
+ * - `InstructionsLoaded` fires on a rule/memory load; its matcher filters the load
895
+ * reason, and `path_glob_match` is the lazy per-path load the coverage tap reads.
896
+ * - `SkillInvoked` is a skill invocation, surfaced under `PostToolUse` with the tool-name
897
+ * matcher `Skill` — the tap's own read of a skill call.
898
+ * - `UserPromptExpansion` fires on a command expansion; its matcher filters the command
899
+ * name, `.*` capturing every one.
900
+ * - `ToolUse` fires after any tool call; its matcher filters the tool name, `.*`
901
+ * capturing every one.
902
+ */
903
+ export declare const TELEMETRY_EVENT_HOOKS: Readonly<Record<string, {
904
+ readonly event: string;
905
+ readonly matcher: string;
906
+ }>>;
929
907
  /**
930
908
  * The default contract for `mcpServer` — Anthropic's documented `.mcp.json` contract
931
909
  * (code.claude.com/docs/en/mcp, retrieved 2026-07-15). A server surfaces at `mcpServers.*`,
@@ -48,6 +48,7 @@ export const skill = kind({
48
48
  registration: [{ via: "user-invoked" }, { via: "description-trigger", field: "description" }],
49
49
  identityField: "name",
50
50
  templates: [{ kind: supportingDoc, path: "*.md" }],
51
+ guidance: "prefer gerund or noun-phrase names (`processing-pdfs`, `pdf-processing`) over vague ones (`helper`, `utils`); `disable-model-invocation: true` for side-effectful workflows you want to time yourself; `user-invocable: false` for background knowledge that is not a command; `metadata` is the sanctioned home for versioning — there is no top-level `version` field.",
51
52
  });
52
53
  /**
53
54
  * `command` — `.claude/commands/*.md`, the skill surface's legacy file placement
@@ -90,6 +91,7 @@ export const rule = kind({
90
91
  format: "yaml-frontmatter",
91
92
  unitShape: "file",
92
93
  registration: [{ via: "paths-match", field: "paths" }],
94
+ guidance: "keep a rule to facts Claude should hold whenever the rule is in scope — concrete enough to verify ('use 2-space indentation', not 'format code properly'). If an entry is a multi-step procedure or only matters occasionally, it belongs in a skill (on-demand) rather than a rule (always-on). Prefer path-scoped rules when one convention governs scattered paths; prefer per-directory CLAUDE.md when directory owners maintain their own. Treat rules like code: prune them when behavior drifts, and test a change by watching whether Claude's behavior actually shifts.",
93
95
  });
94
96
  /**
95
97
  * `memory` — a root `<name>.md` (`CLAUDE.md`, `AGENTS.md`), a lone file loaded
@@ -105,7 +107,14 @@ export const memory = kind({
105
107
  locus: { kind: "at", root: ".", glob: "**/CLAUDE.md" },
106
108
  unitShape: "file",
107
109
  registration: [{ via: "always" }],
110
+ guidance: "a `paths:` frontmatter block belongs on a `.claude/rules/*.md` file, not on `CLAUDE.md` — the memory docs document `paths` only for rules, so a rules-style header on `CLAUDE.md` is dead configuration. Split a large file with `@path` imports (resolved relative to the importing file, absolute allowed, recursion capped at four hops; wrap a path in backticks to mention it without importing). If the repo already ships an `AGENTS.md` for other agents, don't duplicate it — create a `CLAUDE.md` that `@AGENTS.md`-imports it (or symlink, except on Windows where the import is the recommended bridge). Mind the loading asymmetry: every ancestor `CLAUDE.md` loads in full at launch, while files in subdirectories load only when Claude reads a file there — so a rule that must always hold belongs above the working directory, not below it. Personal, un-shared notes go in `CLAUDE.local.md` (gitignored), appended after `CLAUDE.md` at its level.",
108
111
  });
112
+ /**
113
+ * The manifest Claude Code's harness-level settings reside in — the file the assembly's
114
+ * residual settings keys fold into as opaque residue, the same manifest the `hook` kind's
115
+ * registrations surface inside (code.claude.com/docs/en/settings, retrieved 2026-07-16).
116
+ */
117
+ export const SETTINGS_MANIFEST = "settings.json";
109
118
  /**
110
119
  * `hook` — a `settings.json` `hooks.<Event>` registration member: a fields-only kind (no
111
120
  * body slot), its members discovered off the `.claude/settings.json` manifest at the
@@ -115,11 +124,12 @@ export const memory = kind({
115
124
  */
116
125
  export const hook = kind({
117
126
  name: "hook",
118
- locus: { kind: "at", root: ".claude", glob: "settings.json" },
127
+ locus: { kind: "at", root: ".claude", glob: SETTINGS_MANIFEST },
119
128
  unitShape: "file",
120
129
  registration: [{ via: "event", field: "event" }],
121
130
  shape: "fields",
122
- collectionAddress: { manifest: "settings.json", keyPath: "hooks.<Event>", entryShape: "group-array(hooks;matcher)" },
131
+ collectionAddress: { manifest: SETTINGS_MANIFEST, keyPath: "hooks.<Event>", entryShape: "group-array(hooks;matcher)" },
132
+ guidance: "keep a handler's `type` among `command`/`http`/`mcp_tool`/`prompt`/`agent`; a `command` handler needs a `command`, an `http` handler a `url`; the `matcher` filters tool-scoped events and is inert on events that carry no tool (`UserPromptSubmit`, `Stop`, and their siblings).",
123
133
  });
124
134
  /**
125
135
  * `mcpServer` — a `.mcp.json` `mcpServers.*` registration member: a fields-only kind (no
@@ -147,11 +157,11 @@ export const mcpServer = kind({
147
157
  */
148
158
  export const installedPlugin = kind({
149
159
  name: "installed-plugin",
150
- locus: { kind: "at", root: ".claude", glob: "settings.json" },
160
+ locus: { kind: "at", root: ".claude", glob: SETTINGS_MANIFEST },
151
161
  unitShape: "file",
152
162
  registration: [{ via: "enablement", field: "enabled" }],
153
163
  shape: "fields",
154
- collectionAddress: { manifest: "settings.json", keyPath: "enabledPlugins.*", entryShape: "scalar(enabled)" },
164
+ collectionAddress: { manifest: SETTINGS_MANIFEST, keyPath: "enabledPlugins.*", entryShape: "scalar(enabled)" },
155
165
  // The marketplace half of the `<plugin>@<marketplace>` key is a declared edge to the
156
166
  // `known-marketplace` member it names (decision 0039). The half is not an authored field
157
167
  // — the engine splits it off the composite key at read (`src/kind.rs`, the read-time fold
@@ -190,11 +200,11 @@ export const installedPluginDefaultContract = [];
190
200
  */
191
201
  export const knownMarketplace = kind({
192
202
  name: "known-marketplace",
193
- locus: { kind: "at", root: ".claude", glob: "settings.json" },
203
+ locus: { kind: "at", root: ".claude", glob: SETTINGS_MANIFEST },
194
204
  unitShape: "file",
195
205
  registration: [{ via: "registry" }],
196
206
  shape: "fields",
197
- collectionAddress: { manifest: "settings.json", keyPath: "extraKnownMarketplaces.*", entryShape: "object" },
207
+ collectionAddress: { manifest: SETTINGS_MANIFEST, keyPath: "extraKnownMarketplaces.*", entryShape: "object" },
198
208
  });
199
209
  /**
200
210
  * The default contract for `known-marketplace` — **deliberately empty**. The format
@@ -222,6 +232,7 @@ export const pluginManifest = kind({
222
232
  unitShape: "named-field",
223
233
  registration: [],
224
234
  identityField: "name",
235
+ guidance: "leave `version` unset while iterating, so the commit SHA drives updates and users are not stranded on a stale pin; set it once the plugin has a release cycle, and bump it every time — pushing commits without bumping is a no-op. Reach for `defaultEnabled: false` when the plugin costs money or scope on load.",
225
236
  });
226
237
  /**
227
238
  * The default contract for `plugin-manifest` — the documented profile of `claude plugin
@@ -249,11 +260,6 @@ export const pluginManifest = kind({
249
260
  *
250
261
  * Deliberately absent as undecidable: whether the `description` reads well, whether
251
262
  * `keywords` aid discovery, whether `name` names the pack aptly.
252
- *
253
- * Authoring notes the clauses cannot carry: leave `version` unset while iterating, so the
254
- * commit SHA drives updates and users are not stranded on a stale pin; set it once the
255
- * plugin has a release cycle, and bump it every time — pushing commits without bumping is
256
- * a no-op. Reach for `defaultEnabled: false` when the plugin costs money or scope on load.
257
263
  */
258
264
  export const pluginManifestDefaultContract = [
259
265
  clause(required("name"), {
@@ -441,6 +447,7 @@ export const marketplace = kind({
441
447
  unitShape: "named-field",
442
448
  registration: [],
443
449
  identityField: "name",
450
+ guidance: "a relative-path `source` resolves against a *local copy* of the marketplace, so it silently fails to resolve for users who added the marketplace by direct URL to `marketplace.json` — only that one file is downloaded. Reach for `github`, `url`, or `npm` when the catalog is distributed by URL. Where a git source pins both `ref` and `sha`, the `sha` is the effective pin. A marketplace entry's `defaultEnabled` beats the same field in the plugin's own `plugin.json`, while `version` runs the other way — `plugin.json` wins.",
444
451
  });
445
452
  /**
446
453
  * The reserved marketplace names — reserved for official Anthropic use and refused to a
@@ -490,14 +497,6 @@ const RESERVED_MARKETPLACE_NAMES = [
490
497
  * there is no predicate that decides it, and a clause that guessed would fire on true
491
498
  * negatives. The enumerated deny list is the decidable subset; the impersonation rule rides
492
499
  * as guidance below.
493
- *
494
- * Authoring notes the clauses cannot carry: a relative-path `source` resolves against a
495
- * *local copy* of the marketplace, so it silently fails to resolve for users who added the
496
- * marketplace by direct URL to `marketplace.json` — only that one file is downloaded. Reach
497
- * for `github`, `url`, or `npm` when the catalog is distributed by URL. Where a git source
498
- * pins both `ref` and `sha`, the `sha` is the effective pin. A marketplace entry's
499
- * `defaultEnabled` beats the same field in the plugin's own `plugin.json`, while `version`
500
- * runs the other way — `plugin.json` wins.
501
500
  */
502
501
  export const marketplaceDefaultContract = [
503
502
  clause(required("name"), {
@@ -661,13 +660,6 @@ export const settingsLocalDefaultContract = [
661
660
  * vagueness/no-op detection (semantic); gerund naming (judgment). Nothing
662
661
  * decidable is held: the name's hyphen placement and the platform's "no XML
663
662
  * tags in the description" are the two `shape` clauses below.
664
- *
665
- * Authoring notes the clauses cannot carry: prefer gerund or noun-phrase
666
- * names (`processing-pdfs`, `pdf-processing`) over vague ones (`helper`,
667
- * `utils`); `disable-model-invocation: true` for side-effectful workflows you
668
- * want to time yourself; `user-invocable: false` for background knowledge
669
- * that is not a command; `metadata` is the sanctioned home for versioning —
670
- * there is no top-level `version` field.
671
663
  */
672
664
  export const skillDefaultContract = [
673
665
  clause(required("name"), {
@@ -826,16 +818,6 @@ export const agentDefaultContract = [
826
818
  * own: `required` is the one
827
819
  * presence predicate, and its absence is not itself a predicate.)
828
820
  * https://code.claude.com/docs/en/memory#path-specific-rules (retrieved 2026-07-15)
829
- *
830
- * What the clauses cannot carry, as guidance: keep a rule to facts Claude
831
- * should hold whenever the rule is in scope — concrete enough to verify ("use
832
- * 2-space indentation", not "format code properly"). If an entry is a
833
- * multi-step procedure or only matters occasionally, it belongs in a skill
834
- * (on-demand) rather than a rule (always-on). Prefer path-scoped rules when
835
- * one convention governs scattered paths; prefer per-directory CLAUDE.md when
836
- * directory owners maintain their own. Treat rules like code: prune them when
837
- * behavior drifts, and test a change by watching whether Claude's behavior
838
- * actually shifts.
839
821
  */
840
822
  export const ruleDefaultContract = [
841
823
  clause(forbiddenKeys(["description", "globs", "alwaysApply"]), {
@@ -869,21 +851,6 @@ export const ruleDefaultContract = [
869
851
  * schema to gate — manufacturing a required field or a forbidden-key list
870
852
  * would fake a check the format does not carry. The single clause is a context-cost
871
853
  * budget; everything else the contract could say is guidance.
872
- *
873
- * What the clauses cannot carry, as guidance: a `paths:` frontmatter block
874
- * belongs on a `.claude/rules/*.md` file, not on `CLAUDE.md` — the memory
875
- * docs document `paths` only for rules, so a rules-style header on
876
- * `CLAUDE.md` is dead configuration. Split a large file with `@path` imports
877
- * (resolved relative to the importing file, absolute allowed, recursion
878
- * capped at four hops; wrap a path in backticks to mention it without
879
- * importing). If the repo already ships an `AGENTS.md` for other agents,
880
- * don't duplicate it — create a `CLAUDE.md` that `@AGENTS.md`-imports it (or
881
- * symlink, except on Windows where the import is the recommended bridge).
882
- * Mind the loading asymmetry: every ancestor `CLAUDE.md` loads in full at
883
- * launch, while files in subdirectories load only when Claude reads a file
884
- * there — so a rule that must always hold belongs above the working
885
- * directory, not below it. Personal, un-shared notes go in `CLAUDE.local.md`
886
- * (gitignored), appended after `CLAUDE.md` at its level.
887
854
  */
888
855
  export const memoryAnthropicDefaultContract = [
889
856
  clause(extent("lines", 200), {
@@ -947,10 +914,6 @@ const DOCUMENTED_HOOK_EVENTS = [
947
914
  * vocabulary's when/enumOf/type extensions, addressing still cannot spell a path into the
948
915
  * handler array (e.g., `hooks.<Event>[0].type`). A clause over it would range over a field
949
916
  * the read never surfaces, so it is no clause at all — the addressing-reach gap remains.
950
- * What the clauses cannot carry, as guidance: keep a handler's `type` among
951
- * `command`/`http`/`mcp_tool`/`prompt`/`agent`; a `command` handler needs a `command`, an
952
- * `http` handler a `url`; the `matcher` filters tool-scoped events and is inert on events
953
- * that carry no tool (`UserPromptSubmit`, `Stop`, and their siblings).
954
917
  */
955
918
  export const hookDefaultContract = [
956
919
  clause(enumOf("event", DOCUMENTED_HOOK_EVENTS), {
@@ -959,6 +922,29 @@ export const hookDefaultContract = [
959
922
  cite: "https://code.claude.com/docs/en/hooks (retrieved 2026-07-15)",
960
923
  }),
961
924
  ];
925
+ /**
926
+ * The `settings.json` lifecycle event and matcher one documented telemetry event-name
927
+ * projects its tap hook at. The event-name is the author-facing token a telemetry
928
+ * verifier names (`contract.ts`'s `telemetry`, the `roster.rs` admissibility set); the
929
+ * `event` is the `hooks.<Event>` key the tap registers under, and the `matcher` scopes
930
+ * the fire to the telemetry-relevant subset — each an external fact
931
+ * (code.claude.com/docs/en/hooks, retrieved 2026-07-17):
932
+ *
933
+ * - `InstructionsLoaded` fires on a rule/memory load; its matcher filters the load
934
+ * reason, and `path_glob_match` is the lazy per-path load the coverage tap reads.
935
+ * - `SkillInvoked` is a skill invocation, surfaced under `PostToolUse` with the tool-name
936
+ * matcher `Skill` — the tap's own read of a skill call.
937
+ * - `UserPromptExpansion` fires on a command expansion; its matcher filters the command
938
+ * name, `.*` capturing every one.
939
+ * - `ToolUse` fires after any tool call; its matcher filters the tool name, `.*`
940
+ * capturing every one.
941
+ */
942
+ export const TELEMETRY_EVENT_HOOKS = {
943
+ InstructionsLoaded: { event: "InstructionsLoaded", matcher: "path_glob_match" },
944
+ SkillInvoked: { event: "PostToolUse", matcher: "Skill" },
945
+ UserPromptExpansion: { event: "UserPromptExpansion", matcher: ".*" },
946
+ ToolUse: { event: "PostToolUse", matcher: ".*" },
947
+ };
962
948
  /**
963
949
  * Every documented `.mcp.json` server transport — the closed set a server entry's `type`
964
950
  * is drawn from (code.claude.com/docs/en/mcp, retrieved 2026-07-15). `stdio` is the
@@ -274,9 +274,8 @@ export declare const script: (path: string) => Verifier;
274
274
  /**
275
275
  * A telemetry verifier — the named documented harness events the emitted tap records
276
276
  * to a local-locus log, judged by reading the field record. Each name must be a
277
- * documented harness lifecycle event (`InstructionsLoaded`, `Skill`,
278
- * `UserPromptExpansion`, `PostToolUse`; code.claude.com/docs/en/hooks, retrieved
279
- * 2026-07-17) — the gate checks each resolves, never records into it.
277
+ * documented harness lifecycle event the gate checks each resolves, never records
278
+ * into it. See `TELEMETRY_EVENT_HOOKS` in `declarations.ts` for the vocabulary.
280
279
  */
281
280
  export declare const telemetry: (events: readonly string[]) => Verifier;
282
281
  /**
@@ -208,9 +208,8 @@ export const script = (path) => ({ species: "script", path });
208
208
  /**
209
209
  * A telemetry verifier — the named documented harness events the emitted tap records
210
210
  * to a local-locus log, judged by reading the field record. Each name must be a
211
- * documented harness lifecycle event (`InstructionsLoaded`, `Skill`,
212
- * `UserPromptExpansion`, `PostToolUse`; code.claude.com/docs/en/hooks, retrieved
213
- * 2026-07-17) — the gate checks each resolves, never records into it.
211
+ * documented harness lifecycle event the gate checks each resolves, never records
212
+ * into it. See `TELEMETRY_EVENT_HOOKS` in `declarations.ts` for the vocabulary.
214
213
  */
215
214
  export const telemetry = (events) => ({ species: "telemetry", events });
216
215
  /** An identity helper — types a requirement literal at the keystroke. */
@@ -9,7 +9,6 @@
9
9
  * in lockstep.
10
10
  */
11
11
  import type { Harness } from "./assembly.js";
12
- import type { MentionScope } from "./prose.js";
13
12
  import type { Declarations, Payload, RegistrationRow, SettingsRow } from "./generated/index.js";
14
13
  export type { AssemblyFactRow, ClauseRow, Declarations, KindFactRow, RequirementRow, SatisfiesRow, } from "./generated/index.js";
15
14
  /** The stable-sort ordering every declaration row family shares. */
@@ -56,6 +55,12 @@ export type RenderedExtents = ReadonlyMap<string, RenderedExtent>;
56
55
  * If a fields-only member declares no collection address — it surfaces in no host manifest.
57
56
  */
58
57
  export declare function registrationRows(harness: Harness): RegistrationRow[];
58
+ /**
59
+ * Builds a collision-safe dedup key for a tap hook (event, matcher) pair.
60
+ * Two distinct pairs cannot produce the same key (e.g., ("Foo", "BarBaz") ≠ ("FooBar", "Baz")).
61
+ * @internal
62
+ */
63
+ export declare function buildTapHookDedupeKey(event: string, matcher: string): string;
59
64
  /**
60
65
  * The synthesized tap-hook `registration` rows — one deduped `hooks.<Event>`
61
66
  * registration per (lifecycle event, matcher) any telemetry verifier names. Scans the
@@ -97,8 +102,6 @@ export declare function declaredAddresses(harness: Harness): Set<string>;
97
102
  * flat `kind:name` mention of one has no discovery locus to defer to.
98
103
  */
99
104
  export declare function declaredAtLocusKinds(harness: Harness): Set<string>;
100
- /** The full {@link MentionScope} the program resolves a mention against — its addresses and its deferral kinds. */
101
- export declare function mentionScope(harness: Harness): MentionScope;
102
105
  /**
103
106
  * Compile a harness into its seven declaration families — the erased program.
104
107
  *
@@ -110,8 +113,6 @@ export declare function mentionScope(harness: Harness): MentionScope;
110
113
  * or `extent` clause, so a whole compile goes through `emit`, never this alone.
111
114
  */
112
115
  export declare function compileDeclarations(harness: Harness, placements?: EdgePlacements, extents?: RenderedExtents): Declarations;
113
- /** The SDK's pinned engine/interchange version — the JSON pipe rides it in lockstep. */
114
- export declare const SEAM_VERSION = 2;
115
116
  /**
116
117
  * Serialize the seam payload to the internal versioned JSON pipe — `encodeSeam`
117
118
  * stamps `version`, so the caller supplies the rest of the {@link Payload}.
@@ -10,6 +10,7 @@
10
10
  */
11
11
  import { fileURLToPath } from "node:url";
12
12
  import { isTextSpan, resolveLeaf } from "./prose.js";
13
+ import { SETTINGS_MANIFEST, TELEMETRY_EVENT_HOOKS } from "./builtins.js";
13
14
  /**
14
15
  * Compile one `Clause` into its lock row: the shared `key`/`field`/`severity`/
15
16
  * `guidance`/`cite` columns — the clause's four channels surviving erasure
@@ -147,7 +148,7 @@ function registrationLabel(registration) {
147
148
  case "connection":
148
149
  return "connection";
149
150
  case "enablement":
150
- return "enablement";
151
+ return `enablement(${registration.field})`;
151
152
  case "registry":
152
153
  return "registry";
153
154
  }
@@ -228,7 +229,7 @@ function collectionAddressRow(facts) {
228
229
  * the same spelling, absent for the committed default. `templates` names the embedded kinds the
229
230
  * corpus admits over it, and `content` lowers a declared layout (absent for a
230
231
  * `file`-content kind). A registration kind extends the row with its `shape` marker and
231
- * `collection_address`.
232
+ * `collection_address`. Advisory `guidance`/`cite` pair rides alongside (decision 0045).
232
233
  */
233
234
  function kindFactRow(facts, admissions) {
234
235
  if (facts.locus.kind === "embedded") {
@@ -250,6 +251,8 @@ function kindFactRow(facts, admissions) {
250
251
  content: contentRow(facts.content),
251
252
  shape: facts.shape,
252
253
  collection_address: collectionAddressRow(facts),
254
+ guidance: facts.guidance,
255
+ cite: facts.cite,
253
256
  };
254
257
  }
255
258
  /**
@@ -629,28 +632,13 @@ export function registrationRows(harness) {
629
632
  * (`src/tap.rs`). Every synthesized hook's `command` field carries it verbatim. */
630
633
  const TAP_COMMAND = "temper tap";
631
634
  /**
632
- * The `settings.json` lifecycle event and matcher one documented telemetry event-name
633
- * projects its tap hook at. The event-name is the author-facing token a telemetry
634
- * verifier names (`contract.ts`'s `telemetry`, the `roster.rs` admissibility set); the
635
- * `event` is the `hooks.<Event>` key the tap registers under, and the `matcher` scopes
636
- * the fire to the telemetry-relevant subset — each an external fact
637
- * (code.claude.com/docs/en/hooks, retrieved 2026-07-17):
638
- *
639
- * - `InstructionsLoaded` fires on a rule/memory load; its matcher filters the load
640
- * reason, and `path_glob_match` is the lazy per-path load the coverage tap reads.
641
- * - `Skill` is a skill invocation, surfaced under `PostToolUse` with the tool-name
642
- * matcher `Skill` — the tap's own read of a skill call.
643
- * - `UserPromptExpansion` fires on a command expansion; its matcher filters the command
644
- * name, `.*` capturing every one.
645
- * - `PostToolUse` fires after any tool call; its matcher filters the tool name, `.*`
646
- * capturing every one.
635
+ * Builds a collision-safe dedup key for a tap hook (event, matcher) pair.
636
+ * Two distinct pairs cannot produce the same key (e.g., ("Foo", "BarBaz") ("FooBar", "Baz")).
637
+ * @internal
647
638
  */
648
- const TELEMETRY_EVENT_HOOKS = {
649
- InstructionsLoaded: { event: "InstructionsLoaded", matcher: "path_glob_match" },
650
- Skill: { event: "PostToolUse", matcher: "Skill" },
651
- UserPromptExpansion: { event: "UserPromptExpansion", matcher: ".*" },
652
- PostToolUse: { event: "PostToolUse", matcher: ".*" },
653
- };
639
+ export function buildTapHookDedupeKey(event, matcher) {
640
+ return JSON.stringify([event, matcher]);
641
+ }
654
642
  /**
655
643
  * The synthesized tap-hook `registration` rows — one deduped `hooks.<Event>`
656
644
  * registration per (lifecycle event, matcher) any telemetry verifier names. Scans the
@@ -671,7 +659,7 @@ export function tapHookRows(harness) {
671
659
  for (const name of requirement.verifier.events) {
672
660
  const mapping = TELEMETRY_EVENT_HOOKS[name];
673
661
  if (mapping !== undefined)
674
- deduped.set(`${mapping.event}${mapping.matcher}`, mapping);
662
+ deduped.set(buildTapHookDedupeKey(mapping.event, mapping.matcher), mapping);
675
663
  }
676
664
  };
677
665
  for (const requirement of Object.values(harness.require))
@@ -694,12 +682,6 @@ export function tapHookRows(harness) {
694
682
  ],
695
683
  }));
696
684
  }
697
- /**
698
- * The manifest Claude Code's harness-level settings reside in — the file the assembly's
699
- * residual settings keys fold into as opaque residue, the same manifest the `hook` kind's
700
- * registrations surface inside (code.claude.com/docs/en/settings, retrieved 2026-07-10).
701
- */
702
- const SETTINGS_MANIFEST = "settings.json";
703
685
  /**
704
686
  * The `settings` rows — the assembly's harness-level residual settings keys, each folded
705
687
  * into the settings.json manifest's opaque residue at emit. Key-sorted so double emit is
@@ -757,7 +739,7 @@ export function declaredAtLocusKinds(harness) {
757
739
  return new Set(atLocusKindsInPlay(kindsInPlay(harness)).map((facts) => facts.name));
758
740
  }
759
741
  /** The full {@link MentionScope} the program resolves a mention against — its addresses and its deferral kinds. */
760
- export function mentionScope(harness) {
742
+ function mentionScope(harness) {
761
743
  return { mentionable: declaredAddresses(harness), deferrableKinds: declaredAtLocusKinds(harness) };
762
744
  }
763
745
  /**
@@ -793,7 +775,7 @@ export function compileDeclarations(harness, placements, extents) {
793
775
  };
794
776
  }
795
777
  /** The SDK's pinned engine/interchange version — the JSON pipe rides it in lockstep. */
796
- export const SEAM_VERSION = 2;
778
+ const SEAM_VERSION = 2;
797
779
  /**
798
780
  * Serialize the seam payload to the internal versioned JSON pipe — `encodeSeam`
799
781
  * stamps `version`, so the caller supplies the rest of the {@link Payload}.
@@ -10,7 +10,7 @@
10
10
  */
11
11
  import type { Harness } from "./assembly.js";
12
12
  import type { Member } from "./kind.js";
13
- import type { Declarations, RenderedExtent } from "./declarations.js";
13
+ import type { Declarations } from "./declarations.js";
14
14
  import type { PayloadMember } from "./generated/index.js";
15
15
  export type { PayloadMember } from "./generated/index.js";
16
16
  /** What a mention may resolve against at emit. */
@@ -30,27 +30,6 @@ export interface ResolveOptions {
30
30
  */
31
31
  readonly members?: ReadonlyMap<string, Member>;
32
32
  }
33
- /**
34
- * Every composed embedded value's placed edge fields, keyed by the value's
35
- * {@link placementKey} — what `emit` hands {@link compileDeclarations} so each
36
- * `nested_member` row carries its own format's placement record. Iterates exactly the
37
- * values `nestedMemberRows` does, so every edge-bearing row it builds has an observation.
38
- */
39
- export declare function edgePlacements(harness: Harness, options: ResolveOptions): Map<string, string[]>;
40
- /**
41
- * Every composed embedded value's rendered extent — the line and character count of the
42
- * block `emit` projected for it — keyed by its {@link placementKey}, what `emit` hands
43
- * {@link compileDeclarations} so each `nested_member` row carries the span an `extent`
44
- * clause budgets. Iterates exactly the values {@link edgePlacements} does, rendering each
45
- * through the same {@link renderMemberBlock} the body projection uses (a hook is pure, so
46
- * the measured render and the projected one cannot disagree), never a second renderer.
47
- *
48
- * A value the SDK composes is always rendered here, so it always captures a span; a value
49
- * no format rendered — an embedded member read off a layout host's source — is lowered by
50
- * the engine, not this pass, and reaches its row with no span (the `placed_edges`
51
- * distinction between an observed empty and an unobserved absence).
52
- */
53
- export declare function renderedExtents(harness: Harness, options: ResolveOptions): Map<string, RenderedExtent>;
54
33
  /**
55
34
  * One fields-only registration member erased for the manifest write face: its key
56
35
  * (a hook's lifecycle event, an MCP server's name), the collection address it keys
package/dist/src/emit.js CHANGED
@@ -348,7 +348,7 @@ function placedEdges(host, value, options) {
348
348
  * `nested_member` row carries its own format's placement record. Iterates exactly the
349
349
  * values `nestedMemberRows` does, so every edge-bearing row it builds has an observation.
350
350
  */
351
- export function edgePlacements(harness, options) {
351
+ function edgePlacements(harness, options) {
352
352
  const placements = new Map();
353
353
  for (const member of harness.members) {
354
354
  if (member.prose?.kind !== "blocks")
@@ -389,7 +389,7 @@ function renderedLineCount(block) {
389
389
  * the engine, not this pass, and reaches its row with no span (the `placed_edges`
390
390
  * distinction between an observed empty and an unobserved absence).
391
391
  */
392
- export function renderedExtents(harness, options) {
392
+ function renderedExtents(harness, options) {
393
393
  const extents = new Map();
394
394
  for (const member of harness.members) {
395
395
  if (member.prose?.kind !== "blocks")
@@ -79,4 +79,13 @@ export type KindFactRow = {
79
79
  * file-locus kind, so an ordinary row stays byte-identical.
80
80
  */
81
81
  collection_address?: CollectionAddressRow;
82
+ /**
83
+ * Advisory authoring counsel for the kind as a whole — teaching at authoring time via
84
+ * `schema` hover or `explain`, carrying no predicate or severity (decision 0045).
85
+ */
86
+ guidance?: string;
87
+ /**
88
+ * External-fact source backing the guidance — a doc URL plus retrieved date.
89
+ */
90
+ cite?: string;
82
91
  };
@@ -210,6 +210,12 @@ export interface KindFacts {
210
210
  /** Fact 7, template — one {@link Template} per inner layer of nested members the kind
211
211
  * hosts; absent for a kind that nests nothing. */
212
212
  readonly templates?: readonly Template[];
213
+ /** Advisory authoring counsel for the kind as a whole — teaching at authoring time via
214
+ * `schema` hover or `explain`, carrying no predicate or severity (decision 0045). */
215
+ readonly guidance?: string;
216
+ /** External-fact source backing the guidance — a doc URL plus retrieved date,
217
+ * carried as data. */
218
+ readonly cite?: string;
213
219
  }
214
220
  /**
215
221
  * One authored member — a typed value in the library. Kind identity travels by
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dtmd/temper",
3
- "version": "0.0.9",
3
+ "version": "0.0.11",
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": {
@@ -52,7 +52,7 @@
52
52
  "temper": "bin/temper.js"
53
53
  },
54
54
  "optionalDependencies": {
55
- "@dtmd/temper-linux-x64": "0.0.9",
56
- "@dtmd/temper-win32-x64": "0.0.9"
55
+ "@dtmd/temper-linux-x64": "0.0.11",
56
+ "@dtmd/temper-win32-x64": "0.0.11"
57
57
  }
58
58
  }