@dtmd/temper 0.0.14 → 0.0.16
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/dist/src/builtins.d.ts +7 -2
- package/dist/src/builtins.js +9 -3
- package/dist/src/declarations.d.ts +9 -0
- package/dist/src/declarations.js +28 -18
- package/dist/src/dial.d.ts +1 -1
- package/dist/src/dial.js +1 -1
- package/dist/src/emit.js +17 -13
- package/dist/src/kind.d.ts +4 -3
- package/dist/src/prose.js +2 -2
- package/package.json +3 -3
package/dist/src/builtins.d.ts
CHANGED
|
@@ -762,6 +762,10 @@ export interface SettingsLocal {
|
|
|
762
762
|
readonly includeCoAuthoredBy?: boolean;
|
|
763
763
|
/** The output rendering style. */
|
|
764
764
|
readonly outputStyle?: string;
|
|
765
|
+
/** Whether to enable auto-memory for this project (code.claude.com/docs/en/memory, retrieved 2026-07-26). */
|
|
766
|
+
readonly autoMemoryEnabled?: boolean;
|
|
767
|
+
/** Absolute or `~/`-prefixed path for auto-memory storage, honored at any settings scope (code.claude.com/docs/en/memory, retrieved 2026-07-26). */
|
|
768
|
+
readonly autoMemoryDirectory?: string;
|
|
765
769
|
}
|
|
766
770
|
/**
|
|
767
771
|
* `settings-local` — `.claude/settings.local.json`, a whole-file JSON document at the
|
|
@@ -903,10 +907,11 @@ export declare const hookDefaultContract: readonly Clause[];
|
|
|
903
907
|
* verifier names (`contract.ts`'s `telemetry`, the `roster.rs` admissibility set); the
|
|
904
908
|
* `event` is the `hooks.<Event>` key the tap registers under, and the `matcher` scopes
|
|
905
909
|
* the fire to the telemetry-relevant subset — each an external fact
|
|
906
|
-
* (code.claude.com/docs/en/hooks, retrieved 2026-
|
|
910
|
+
* (code.claude.com/docs/en/hooks, retrieved 2026-08-26):
|
|
907
911
|
*
|
|
908
912
|
* - `InstructionsLoaded` fires on a rule/memory load; its matcher filters the load
|
|
909
|
-
* reason
|
|
913
|
+
* reason across all documented reasons (session_start, nested_traversal, path_glob_match,
|
|
914
|
+
* include, compact), so always-on members that load at session start are recorded.
|
|
910
915
|
* - `SkillInvoked` is a skill invocation, surfaced under `PostToolUse` with the tool-name
|
|
911
916
|
* matcher `Skill` — the tap's own read of a skill call.
|
|
912
917
|
* - `UserPromptExpansion` fires on a command expansion; its matcher filters the command
|
package/dist/src/builtins.js
CHANGED
|
@@ -756,6 +756,11 @@ export const skillDefaultContract = [
|
|
|
756
756
|
guidance: "The optional `paths` scope gates every invocation channel until Claude reads a file its globs match; each entry is a glob (brace expansion supported). An unparseable pattern — an unclosed `[`, say — is invalid under globset and silently matches nothing, so the gate never opens and the skill never registers, with no error surfaced. Fix the pattern or drop the field.",
|
|
757
757
|
cite: "https://code.claude.com/docs/en/memory#path-specific-rules (retrieved 2026-07-15)",
|
|
758
758
|
}),
|
|
759
|
+
clause(mentionReachable("paths", "paths"), {
|
|
760
|
+
severity: "advisory",
|
|
761
|
+
guidance: "A mention of a gated member is actionable only where that member can be invoked. A `paths` gate removes its member from every invocation channel until Claude reads a matching file, and invoking a gated member from outside its gate hard-errors (`Unknown skill`) — the harness then tells the user it doesn't exist. So a skill that loads where its target cannot be invoked hands Claude an obligation it cannot act on. Two remedies: scope this skill's `paths` to the target's gate, or ungate the target. Advisory because the containment test is literal — every glob here must appear verbatim in the gate — so a semantically narrower glob (`src/**/*.ts` inside `src/**`) false-fires; retune or drop this clause in your own contract when it does.",
|
|
762
|
+
cite: "https://code.claude.com/docs/en/skills (retrieved 2026-07-16; gating hard-error verified against 2.1.211)",
|
|
763
|
+
}),
|
|
759
764
|
];
|
|
760
765
|
/**
|
|
761
766
|
* The default contract for `supporting-doc` — one clause, because the format documents
|
|
@@ -951,10 +956,11 @@ export const hookDefaultContract = [
|
|
|
951
956
|
* verifier names (`contract.ts`'s `telemetry`, the `roster.rs` admissibility set); the
|
|
952
957
|
* `event` is the `hooks.<Event>` key the tap registers under, and the `matcher` scopes
|
|
953
958
|
* the fire to the telemetry-relevant subset — each an external fact
|
|
954
|
-
* (code.claude.com/docs/en/hooks, retrieved 2026-
|
|
959
|
+
* (code.claude.com/docs/en/hooks, retrieved 2026-08-26):
|
|
955
960
|
*
|
|
956
961
|
* - `InstructionsLoaded` fires on a rule/memory load; its matcher filters the load
|
|
957
|
-
* reason
|
|
962
|
+
* reason across all documented reasons (session_start, nested_traversal, path_glob_match,
|
|
963
|
+
* include, compact), so always-on members that load at session start are recorded.
|
|
958
964
|
* - `SkillInvoked` is a skill invocation, surfaced under `PostToolUse` with the tool-name
|
|
959
965
|
* matcher `Skill` — the tap's own read of a skill call.
|
|
960
966
|
* - `UserPromptExpansion` fires on a command expansion; its matcher filters the command
|
|
@@ -963,7 +969,7 @@ export const hookDefaultContract = [
|
|
|
963
969
|
* capturing every one.
|
|
964
970
|
*/
|
|
965
971
|
export const TELEMETRY_EVENT_HOOKS = {
|
|
966
|
-
InstructionsLoaded: { event: "InstructionsLoaded", matcher: "
|
|
972
|
+
InstructionsLoaded: { event: "InstructionsLoaded", matcher: ".*" },
|
|
967
973
|
SkillInvoked: { event: "PostToolUse", matcher: "Skill" },
|
|
968
974
|
UserPromptExpansion: { event: "UserPromptExpansion", matcher: ".*" },
|
|
969
975
|
ToolUse: { event: "PostToolUse", matcher: ".*" },
|
|
@@ -13,6 +13,15 @@ import type { Declarations, Payload, RegistrationRow, SettingsRow } from "./gene
|
|
|
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
|
+
* Build a `Map<K, V>` from an iterable of key-value pairs, refusing on duplicate
|
|
18
|
+
* identity keys. A map the caller builds must have unique keys; a duplicate is
|
|
19
|
+
* corruption, not a shadowing rule.
|
|
20
|
+
*
|
|
21
|
+
* @throws Error when a key appears more than once in the iterable, naming the
|
|
22
|
+
* colliding key.
|
|
23
|
+
*/
|
|
24
|
+
export declare function uniqueMap<K, V>(entries: Iterable<[K, V]>): Map<K, V>;
|
|
16
25
|
/**
|
|
17
26
|
* One composed embedded value's key in an {@link EdgePlacements} table — its host's
|
|
18
27
|
* `kind:name` address plus the value's own kind and key, the same triple the
|
package/dist/src/declarations.js
CHANGED
|
@@ -10,7 +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, tapHookRegistration } from "./builtins.js";
|
|
13
|
+
import { SETTINGS_MANIFEST, TELEMETRY_EVENT_HOOKS, hook, tapHookRegistration } from "./builtins.js";
|
|
14
14
|
/**
|
|
15
15
|
* Compile one `Clause` into its lock row: the shared `key`/`field`/`severity`/
|
|
16
16
|
* `guidance`/`cite` columns — the clause's four channels surviving erasure
|
|
@@ -162,6 +162,24 @@ function registrationLabels(registration) {
|
|
|
162
162
|
export function compareStrings(a, b) {
|
|
163
163
|
return a < b ? -1 : a > b ? 1 : 0;
|
|
164
164
|
}
|
|
165
|
+
/**
|
|
166
|
+
* Build a `Map<K, V>` from an iterable of key-value pairs, refusing on duplicate
|
|
167
|
+
* identity keys. A map the caller builds must have unique keys; a duplicate is
|
|
168
|
+
* corruption, not a shadowing rule.
|
|
169
|
+
*
|
|
170
|
+
* @throws Error when a key appears more than once in the iterable, naming the
|
|
171
|
+
* colliding key.
|
|
172
|
+
*/
|
|
173
|
+
export function uniqueMap(entries) {
|
|
174
|
+
const map = new Map();
|
|
175
|
+
for (const [key, value] of entries) {
|
|
176
|
+
if (map.has(key)) {
|
|
177
|
+
throw new Error(`duplicate identity key \`${key}\``);
|
|
178
|
+
}
|
|
179
|
+
map.set(key, value);
|
|
180
|
+
}
|
|
181
|
+
return map;
|
|
182
|
+
}
|
|
165
183
|
/**
|
|
166
184
|
* A host kind's nesting templates, from its two declaration loci. The kind's own
|
|
167
185
|
* declared templates carry each layer — child kind, plus a file layer's path pattern.
|
|
@@ -309,24 +327,16 @@ function kindFactKindsInPlay(allKinds) {
|
|
|
309
327
|
}
|
|
310
328
|
/** The requirement rows — assembly `require` and every member's `requires`, one namespace. */
|
|
311
329
|
function requirementRows(harness) {
|
|
312
|
-
const
|
|
313
|
-
const
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
// One namespace, one fill mechanism; a cross-publisher name collision is an
|
|
317
|
-
// admissibility finding, never a shadowing rule.
|
|
318
|
-
throw new Error(`requirement \`${name}\` is published twice (${source} collides with an earlier ` +
|
|
319
|
-
`publisher) — a name collision across publishers is an admissibility finding.`);
|
|
320
|
-
}
|
|
321
|
-
merged.set(name, requirement);
|
|
322
|
-
};
|
|
323
|
-
for (const [name, requirement] of Object.entries(harness.require))
|
|
324
|
-
publish(name, requirement, "the assembly");
|
|
330
|
+
const entries = [];
|
|
331
|
+
for (const [name, requirement] of Object.entries(harness.require)) {
|
|
332
|
+
entries.push([name, requirement]);
|
|
333
|
+
}
|
|
325
334
|
for (const member of harness.members) {
|
|
326
335
|
for (const [name, requirement] of Object.entries(member.requires)) {
|
|
327
|
-
|
|
336
|
+
entries.push([name, requirement]);
|
|
328
337
|
}
|
|
329
338
|
}
|
|
339
|
+
const merged = uniqueMap(entries);
|
|
330
340
|
return [...merged.entries()]
|
|
331
341
|
.sort(([a], [b]) => compareStrings(a, b))
|
|
332
342
|
.map(([name, requirement]) => ({
|
|
@@ -615,7 +625,7 @@ export function registrationRows(harness) {
|
|
|
615
625
|
const address = member.facts.collectionAddress;
|
|
616
626
|
if (address === undefined) {
|
|
617
627
|
throw new Error(`member \`${member.name}\`: a fields-only registration kind declares no ` +
|
|
618
|
-
`collection address — it surfaces in no host manifest (specs/model/
|
|
628
|
+
`collection address — it surfaces in no host manifest (specs/model/authoring.md, "The SDK").`);
|
|
619
629
|
}
|
|
620
630
|
return {
|
|
621
631
|
kind: member.kind,
|
|
@@ -632,7 +642,7 @@ export function registrationRows(harness) {
|
|
|
632
642
|
/** The tap invocation the synthesized telemetry hooks run — the sibling verb of the
|
|
633
643
|
* session-start reporter, appending one event record to the per-machine log
|
|
634
644
|
* (`src/tap.rs`). Every synthesized hook's `command` field carries it verbatim. */
|
|
635
|
-
const TAP_COMMAND = "temper tap";
|
|
645
|
+
const TAP_COMMAND = "temper tap \"$CLAUDE_PROJECT_DIR\"";
|
|
636
646
|
/**
|
|
637
647
|
* Builds a collision-safe dedup key for a tap hook (event, matcher) pair.
|
|
638
648
|
* Two distinct pairs cannot produce the same key (e.g., ("Foo", "BarBaz") ≠ ("FooBar", "Baz")).
|
|
@@ -677,7 +687,7 @@ export function tapHookRows(harness) {
|
|
|
677
687
|
.map(({ event, matcher }) => {
|
|
678
688
|
const { keyPath, fields } = tapHookRegistration(TAP_COMMAND, matcher);
|
|
679
689
|
return {
|
|
680
|
-
kind:
|
|
690
|
+
kind: hook.key,
|
|
681
691
|
key: event,
|
|
682
692
|
manifest: SETTINGS_MANIFEST,
|
|
683
693
|
key_path: keyPath,
|
package/dist/src/dial.d.ts
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* a layer's *effect* is held structurally — by the shape of a kind, which an author
|
|
20
20
|
* cannot spell their way past. The other half of the bound is the engine's:
|
|
21
21
|
* dialed softening is inert in block mode, so a block-mode pass on any machine implies
|
|
22
|
-
* the shared gate's pass
|
|
22
|
+
* the shared gate's pass.
|
|
23
23
|
*/
|
|
24
24
|
import type { Clause, Severity } from "./contract.js";
|
|
25
25
|
import type { KindDefinition } from "./kind.js";
|
package/dist/src/dial.js
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* a layer's *effect* is held structurally — by the shape of a kind, which an author
|
|
20
20
|
* cannot spell their way past. The other half of the bound is the engine's:
|
|
21
21
|
* dialed softening is inert in block mode, so a block-mode pass on any machine implies
|
|
22
|
-
* the shared gate's pass
|
|
22
|
+
* the shared gate's pass.
|
|
23
23
|
*/
|
|
24
24
|
import { clause, closedKeys, enumOf, required, type } from "./contract.js";
|
|
25
25
|
import { kind } from "./kind.js";
|
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 { checkMentions, isTextSpan, renderText, resolveLeaf } from "./prose.js";
|
|
14
14
|
import { permissionUnion } from "./needs.js";
|
|
15
|
-
import { compareStrings, compileDeclarations, declaredAddresses, declaredAtLocusKinds, declaredRequirements, encodeSeam, placementKey, registrationRows, settingsRows, tapHookRows, } from "./declarations.js";
|
|
15
|
+
import { compareStrings, compileDeclarations, declaredAddresses, declaredAtLocusKinds, declaredRequirements, encodeSeam, placementKey, registrationRows, settingsRows, tapHookRows, uniqueMap, } from "./declarations.js";
|
|
16
16
|
/** The {@link MentionScope} a set of {@link ResolveOptions} names — its two sets, each defaulting to empty. */
|
|
17
17
|
function scopeOf(options) {
|
|
18
18
|
return {
|
|
@@ -209,6 +209,7 @@ function edgeTargetFacts(host, value, leaves, options) {
|
|
|
209
209
|
address: lookup,
|
|
210
210
|
kind: target.kind,
|
|
211
211
|
path: relativeProjection(projectionPath(host), projectionPath(target)),
|
|
212
|
+
repoRootedPath: projectionPath(target),
|
|
212
213
|
};
|
|
213
214
|
}
|
|
214
215
|
return targets;
|
|
@@ -349,7 +350,7 @@ function placedEdges(host, value, options) {
|
|
|
349
350
|
* values `nestedMemberRows` does, so every edge-bearing row it builds has an observation.
|
|
350
351
|
*/
|
|
351
352
|
function edgePlacements(harness, options) {
|
|
352
|
-
const
|
|
353
|
+
const entries = [];
|
|
353
354
|
for (const member of harness.members) {
|
|
354
355
|
if (member.prose?.kind !== "blocks")
|
|
355
356
|
continue;
|
|
@@ -358,11 +359,11 @@ function edgePlacements(harness, options) {
|
|
|
358
359
|
continue;
|
|
359
360
|
const placed = placedEdges(member, value, options);
|
|
360
361
|
if (placed !== undefined) {
|
|
361
|
-
|
|
362
|
+
entries.push([placementKey(`${member.kind}:${member.name}`, value.kind, value.key), placed]);
|
|
362
363
|
}
|
|
363
364
|
}
|
|
364
365
|
}
|
|
365
|
-
return
|
|
366
|
+
return uniqueMap(entries);
|
|
366
367
|
}
|
|
367
368
|
/**
|
|
368
369
|
* The line count of a rendered block, matching the engine's `str::lines()`: a single
|
|
@@ -390,7 +391,7 @@ function renderedLineCount(block) {
|
|
|
390
391
|
* distinction between an observed empty and an unobserved absence).
|
|
391
392
|
*/
|
|
392
393
|
function renderedExtents(harness, options) {
|
|
393
|
-
const
|
|
394
|
+
const entries = [];
|
|
394
395
|
for (const member of harness.members) {
|
|
395
396
|
if (member.prose?.kind !== "blocks")
|
|
396
397
|
continue;
|
|
@@ -398,15 +399,18 @@ function renderedExtents(harness, options) {
|
|
|
398
399
|
if (isTextSpan(value))
|
|
399
400
|
continue;
|
|
400
401
|
const block = renderMemberBlock(member, value, options);
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
402
|
+
entries.push([
|
|
403
|
+
placementKey(`${member.kind}:${member.name}`, value.kind, value.key),
|
|
404
|
+
{
|
|
405
|
+
lines: renderedLineCount(block),
|
|
406
|
+
// Unicode scalar values, matching Rust's `chars().count()` — iterating a string
|
|
407
|
+
// yields code points, so a surrogate pair counts once, the way it does file-side.
|
|
408
|
+
chars: [...block].length,
|
|
409
|
+
},
|
|
410
|
+
]);
|
|
407
411
|
}
|
|
408
412
|
}
|
|
409
|
-
return
|
|
413
|
+
return uniqueMap(entries);
|
|
410
414
|
}
|
|
411
415
|
/**
|
|
412
416
|
* Render a member-level `Text` body to its final bytes: its mentions are
|
|
@@ -550,7 +554,7 @@ function settingsResidue(harness) {
|
|
|
550
554
|
* spells a member address, so an edge field and a mention name a member the same way.
|
|
551
555
|
*/
|
|
552
556
|
function memberTable(harness) {
|
|
553
|
-
return
|
|
557
|
+
return uniqueMap(harness.members.map((member) => [`${member.kind}:${member.name}`, member]));
|
|
554
558
|
}
|
|
555
559
|
/** The harness's projected members as payload members, deterministically kind-then-name ordered. */
|
|
556
560
|
function orderedMembers(harness, options) {
|
package/dist/src/kind.d.ts
CHANGED
|
@@ -345,8 +345,7 @@ export interface ResolvedEmbeddedMemberCollectionEntry {
|
|
|
345
345
|
* The closed set of facts an embedded format may place about one edge field's
|
|
346
346
|
* target — derived at emit off the resolved target member, never authored at the
|
|
347
347
|
* instance and never fabricated, so a rendered reference is true by construction.
|
|
348
|
-
* The set is exactly these
|
|
349
|
-
* convenience.
|
|
348
|
+
* The set is exactly these five facts; no convenience additions beyond.
|
|
350
349
|
*/
|
|
351
350
|
export interface EdgeTargetFacts {
|
|
352
351
|
/** The target member's identity within its kind. */
|
|
@@ -355,8 +354,10 @@ export interface EdgeTargetFacts {
|
|
|
355
354
|
readonly address: string;
|
|
356
355
|
/** The target member's kind. */
|
|
357
356
|
readonly kind: string;
|
|
358
|
-
/** The target's projection, relative to the host member's own projection. */
|
|
357
|
+
/** The target's projection, relative to the host member's own projection — used by a render hook to spell a link from the host body. */
|
|
359
358
|
readonly path: string;
|
|
359
|
+
/** The target's projection rooted at the repository — used by a render hook consuming edge targets from repo root. */
|
|
360
|
+
readonly repoRootedPath: string;
|
|
360
361
|
}
|
|
361
362
|
/**
|
|
362
363
|
* An {@link EmbeddedMemberValue} after every leaf (top-level and each
|
package/dist/src/prose.js
CHANGED
|
@@ -145,7 +145,7 @@ export function blocks(...values) {
|
|
|
145
145
|
throw new Error(`blocks(): block ${index} is a \`file()\` value — a composed body admits a ` +
|
|
146
146
|
`\`text\` span or an embedded member value. A document is a member's whole ` +
|
|
147
147
|
`\`prose\` body (\`prose: file(…)\`); to pull its bytes into a composed body, ` +
|
|
148
|
-
`interpolate \`include()\` into a \`text\` span (specs/model/
|
|
148
|
+
`interpolate \`include()\` into a \`text\` span (specs/model/authoring.md, "The SDK").`);
|
|
149
149
|
}
|
|
150
150
|
});
|
|
151
151
|
return { kind: "blocks", values };
|
|
@@ -197,7 +197,7 @@ export function resolveLeaf(value, scope, context) {
|
|
|
197
197
|
return value;
|
|
198
198
|
if (value.includes.length > 0) {
|
|
199
199
|
throw new Error(`${context}: an embedded-member leaf cannot carry an include — a content pull is a ` +
|
|
200
|
-
`member-body intent, not a fenced-value one (specs/model/
|
|
200
|
+
`member-body intent, not a fenced-value one (specs/model/authoring.md, "The SDK").`);
|
|
201
201
|
}
|
|
202
202
|
checkMentions(value.mentions, scope, context);
|
|
203
203
|
return renderText(value);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dtmd/temper",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.16",
|
|
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.
|
|
56
|
-
"@dtmd/temper-win32-x64": "0.0.
|
|
55
|
+
"@dtmd/temper-linux-x64": "0.0.16",
|
|
56
|
+
"@dtmd/temper-win32-x64": "0.0.16"
|
|
57
57
|
}
|
|
58
58
|
}
|