@dtmd/temper 0.0.17 → 0.0.19
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/assembly.d.ts +40 -0
- package/dist/src/assembly.js +43 -0
- package/dist/src/builtins.d.ts +110 -4
- package/dist/src/builtins.js +64 -3
- package/dist/src/claude-code.d.ts +2 -2
- package/dist/src/claude-code.js +1 -1
- package/dist/src/contract.d.ts +96 -3
- package/dist/src/contract.js +89 -2
- package/dist/src/declarations.d.ts +3 -3
- package/dist/src/declarations.js +228 -24
- package/dist/src/emit.d.ts +28 -6
- package/dist/src/emit.js +102 -29
- package/dist/src/generated/ClauseRow.d.ts +29 -7
- package/dist/src/generated/Declarations.d.ts +8 -0
- package/dist/src/generated/InputRow.d.ts +21 -0
- package/dist/src/generated/InputRow.js +2 -0
- package/dist/src/generated/KindFactRow.d.ts +12 -0
- package/dist/src/generated/index.d.ts +1 -0
- package/dist/src/index.d.ts +8 -7
- package/dist/src/index.js +9 -5
- package/dist/src/kind.d.ts +314 -15
- package/dist/src/kind.js +184 -9
- package/dist/src/member-address.d.ts +102 -0
- package/dist/src/member-address.js +114 -0
- package/dist/src/prose.d.ts +29 -12
- package/dist/src/prose.js +50 -19
- package/package.json +3 -3
package/dist/src/assembly.d.ts
CHANGED
|
@@ -49,6 +49,22 @@ export interface Harness {
|
|
|
49
49
|
readonly require: Readonly<Record<string, Requirement>>;
|
|
50
50
|
/** The residual harness-level settings with no member home (a shrinking list). */
|
|
51
51
|
readonly settings: Readonly<Record<string, unknown>>;
|
|
52
|
+
/**
|
|
53
|
+
* The **root member's own contract** — the clauses that bind to the whole governed
|
|
54
|
+
* forest rather than to one kind's population. Lowered to top-level clause rows
|
|
55
|
+
* carrying no `kind` column, the absence the engine reads as "the root's"
|
|
56
|
+
* (`specs/model/representation.md`, "The root member": the contract attaches to the
|
|
57
|
+
* harness the way it attaches to any member).
|
|
58
|
+
*
|
|
59
|
+
* Every clause here is judged at the selection grain, so the vocabulary is the
|
|
60
|
+
* selection predicates plus {@link reachable}; a member-grain clause names a field in
|
|
61
|
+
* a schema the root has no single kind to supply, and the gate refuses it at
|
|
62
|
+
* admissibility rather than leaving it unjudged.
|
|
63
|
+
*
|
|
64
|
+
* Absent, {@link rootDefaultContract} applies — the shipped floor, composed by
|
|
65
|
+
* {@link harness}.
|
|
66
|
+
*/
|
|
67
|
+
readonly contract: readonly Clause[];
|
|
52
68
|
/**
|
|
53
69
|
* The root member's declared enforcement mode — harness-wide, overridable
|
|
54
70
|
* per member. Defaults to `warn`: temper fabricates no enforcement the
|
|
@@ -69,4 +85,28 @@ export declare function harness(init: {
|
|
|
69
85
|
require?: Readonly<Record<string, Requirement>>;
|
|
70
86
|
settings?: Readonly<Record<string, unknown>>;
|
|
71
87
|
mode?: EnforcementMode;
|
|
88
|
+
contract?: readonly Clause[];
|
|
72
89
|
}): Harness;
|
|
90
|
+
/**
|
|
91
|
+
* The **shipped root default contract** — what the root member owes absent an authored
|
|
92
|
+
* one, and what the embedded built-in lock therefore carries for the stranger gate (a
|
|
93
|
+
* harness with no lock of its own gets the same root contract an emitted one does).
|
|
94
|
+
*
|
|
95
|
+
* Homed beside the root member's other fields, the precedent `dialDefaultContract` sets
|
|
96
|
+
* in `dial.ts`: a default contract lives with the surface it governs.
|
|
97
|
+
*
|
|
98
|
+
* Three clauses, all predicates only the root can bind: their selection is the whole
|
|
99
|
+
* forest and their judges read the reference graph, the committed lock and the discovery
|
|
100
|
+
* walk, none of which any one kind's population carries. `reachable` is the one check
|
|
101
|
+
* that catches authored configuration the harness never loads at all; `fresh` is the one
|
|
102
|
+
* that catches a projection or a fingerprinted source dependency that has moved out from
|
|
103
|
+
* under its lock row; `locus-declared` is the one that catches a document sitting at a
|
|
104
|
+
* governed locus that the program never declared.
|
|
105
|
+
*
|
|
106
|
+
* All advisory — today's posture for each, so no adopter turns red on the upgrade. A
|
|
107
|
+
* dead registration is often deliberate work-in-progress, a drifted projection is usually
|
|
108
|
+
* a re-emit away, and an undeclared document is often a surface mid-adoption; whether any
|
|
109
|
+
* of them gates is the adopting author's call, dialed or re-declared rather than
|
|
110
|
+
* tool-decided.
|
|
111
|
+
*/
|
|
112
|
+
export declare const rootDefaultContract: readonly Clause[];
|
package/dist/src/assembly.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* `temper.toml`, no roster/bindings dialect (the Decision rejects both);
|
|
7
7
|
* composing partial harnesses is ordinary code.
|
|
8
8
|
*/
|
|
9
|
+
import { clause, fresh, locusDeclared, reachable } from "./contract.js";
|
|
9
10
|
/**
|
|
10
11
|
* Compose the harness from its six fields — ordinary code, Turing-completeness
|
|
11
12
|
* quarantined at authoring time. Absent
|
|
@@ -20,5 +21,47 @@ export function harness(init) {
|
|
|
20
21
|
require: init.require ?? {},
|
|
21
22
|
settings: init.settings ?? {},
|
|
22
23
|
mode: init.mode ?? "warn",
|
|
24
|
+
// The one place "a shipped root default applies when the author declares none" is
|
|
25
|
+
// spelled. Composing the array is the whole override surface: an authored
|
|
26
|
+
// `contract` replaces the default wholesale, never merges with it — the same
|
|
27
|
+
// rows-or-default rule a kind's `expect` binding takes over its floor.
|
|
28
|
+
contract: init.contract ?? rootDefaultContract,
|
|
23
29
|
};
|
|
24
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* The **shipped root default contract** — what the root member owes absent an authored
|
|
33
|
+
* one, and what the embedded built-in lock therefore carries for the stranger gate (a
|
|
34
|
+
* harness with no lock of its own gets the same root contract an emitted one does).
|
|
35
|
+
*
|
|
36
|
+
* Homed beside the root member's other fields, the precedent `dialDefaultContract` sets
|
|
37
|
+
* in `dial.ts`: a default contract lives with the surface it governs.
|
|
38
|
+
*
|
|
39
|
+
* Three clauses, all predicates only the root can bind: their selection is the whole
|
|
40
|
+
* forest and their judges read the reference graph, the committed lock and the discovery
|
|
41
|
+
* walk, none of which any one kind's population carries. `reachable` is the one check
|
|
42
|
+
* that catches authored configuration the harness never loads at all; `fresh` is the one
|
|
43
|
+
* that catches a projection or a fingerprinted source dependency that has moved out from
|
|
44
|
+
* under its lock row; `locus-declared` is the one that catches a document sitting at a
|
|
45
|
+
* governed locus that the program never declared.
|
|
46
|
+
*
|
|
47
|
+
* All advisory — today's posture for each, so no adopter turns red on the upgrade. A
|
|
48
|
+
* dead registration is often deliberate work-in-progress, a drifted projection is usually
|
|
49
|
+
* a re-emit away, and an undeclared document is often a surface mid-adoption; whether any
|
|
50
|
+
* of them gates is the adopting author's call, dialed or re-declared rather than
|
|
51
|
+
* tool-decided.
|
|
52
|
+
*/
|
|
53
|
+
export const rootDefaultContract = [
|
|
54
|
+
clause(reachable(), {
|
|
55
|
+
severity: "advisory",
|
|
56
|
+
guidance: "This member is authored but unreachable: every registration channel its kind declares is provably dead, and no member that is reachable imports it. Claude never loads it, so it is context you maintain and never pay for — and a reader of the tree cannot tell it from live configuration. Three remedies: open a channel (give the `paths` globs a file they match, give the `description` trigger words), import it from a member that is reachable, or delete it. Advisory because a dead edge is a legitimate work-in-progress state; dial it or re-declare the clause `required` once your tree should hold the line.",
|
|
57
|
+
cite: "https://code.claude.com/docs/en/memory#path-specific-rules (retrieved 2026-07-15); https://code.claude.com/docs/en/skills (retrieved 2026-07-16)",
|
|
58
|
+
}),
|
|
59
|
+
clause(fresh(), {
|
|
60
|
+
severity: "advisory",
|
|
61
|
+
guidance: "A lock row this member owns no longer matches disk: either its committed projection was hand-edited, or a fingerprinted source dependency it imports or includes has moved. Nothing reverse-parses a projection back into the program, so the two sides stay apart until you reconcile them: edit the owning source and re-emit, and for a moved dependency re-verify the member's claims against the new bytes first. Advisory because a drifted checkout is usually one `emit` away and blocking every such run would be temper's escalation, not yours; dial this label to `required` — or re-declare the clause — once a drifted projection should fail CI.",
|
|
62
|
+
}),
|
|
63
|
+
clause(locusDeclared(), {
|
|
64
|
+
severity: "advisory",
|
|
65
|
+
guidance: "A document sits at a governed locus that your program declares no member for. Claude Code loads it, but temper maintains nothing about it: `emit` never writes or reaps it, `guard` never bound it, and every address under it resolves to nothing — so it reads as governed configuration while being governed by no one. Declare the member in the program and re-emit; for a directory whose documents are authored in place rather than projected, declare its kind `local` so `check` derives the rows at read time instead. Advisory because a tree mid-adoption legitimately carries documents the program has not reached yet; dial this label to `required` — or re-declare the clause — once every document at a governed locus should be one you declared.",
|
|
66
|
+
}),
|
|
67
|
+
];
|
package/dist/src/builtins.d.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* `@dtmd/temper/claude-code` subpath
|
|
10
10
|
* — never from the root.
|
|
11
11
|
*/
|
|
12
|
-
import type { KindDefinition } from "./kind.js";
|
|
12
|
+
import type { KindDefinition, Residue } from "./kind.js";
|
|
13
13
|
import type { Prose } from "./prose.js";
|
|
14
14
|
import type { Clause } from "./contract.js";
|
|
15
15
|
/** A Claude Code skill — a directory whose entry file is `SKILL.md` with YAML frontmatter. */
|
|
@@ -766,6 +766,15 @@ export interface SettingsLocal {
|
|
|
766
766
|
readonly autoMemoryEnabled?: boolean;
|
|
767
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
768
|
readonly autoMemoryDirectory?: string;
|
|
769
|
+
/**
|
|
770
|
+
* The opaque residue — every documented-but-untyped top-level key this overlay carries,
|
|
771
|
+
* projected flat and key-sorted after the typed fields above. The settings schema is
|
|
772
|
+
* large and version-evolving, so 0036 settles the unschematized remainder opaque and
|
|
773
|
+
* *named*: this bag is the name. The two halves partition the file's key space, which
|
|
774
|
+
* the bag's type holds: a key typed above belongs above and is unspellable here
|
|
775
|
+
* (code.claude.com/docs/en/settings, retrieved 2026-07-16).
|
|
776
|
+
*/
|
|
777
|
+
readonly residue?: Residue<SettingsLocal>;
|
|
769
778
|
}
|
|
770
779
|
/**
|
|
771
780
|
* `settings-local` — `.claude/settings.local.json`, a whole-file JSON document at the
|
|
@@ -773,9 +782,10 @@ export interface SettingsLocal {
|
|
|
773
782
|
* target, its rows derived at read time and no row of it ever landing in the lock. Its
|
|
774
783
|
* top-level keys are its fields; identity is the fixed singleton stem `settings.local` (the
|
|
775
784
|
* `file` unit shape — every machine's overlay is the one file at this path, so no declared
|
|
776
|
-
* key names it).
|
|
777
|
-
*
|
|
778
|
-
*
|
|
785
|
+
* key names it). The keys it does not type ride {@link SettingsLocal.residue}, the opaque
|
|
786
|
+
* channel 0036's partial governance names. Channel-less: machine configuration read by the
|
|
787
|
+
* harness, never surfaced to the model (code.claude.com/docs/en/settings, retrieved
|
|
788
|
+
* 2026-07-16; decisions 0032/0034/0036).
|
|
779
789
|
*/
|
|
780
790
|
export declare const settingsLocal: KindDefinition<SettingsLocal>;
|
|
781
791
|
/**
|
|
@@ -793,6 +803,102 @@ export declare const settingsLocal: KindDefinition<SettingsLocal>;
|
|
|
793
803
|
* never a gate clause.
|
|
794
804
|
*/
|
|
795
805
|
export declare const settingsLocalDefaultContract: readonly Clause[];
|
|
806
|
+
/**
|
|
807
|
+
* Claude Code's committed project settings — `.claude/settings.json`, the file a team
|
|
808
|
+
* checks in so everyone who clones the repository gets the same permissions, hooks,
|
|
809
|
+
* plugins and environment (code.claude.com/docs/en/settings, "Settings files and who they
|
|
810
|
+
* affect", retrieved 2026-09-22).
|
|
811
|
+
*
|
|
812
|
+
* Only the documented top-level keys a committed team file carries are typed here; the
|
|
813
|
+
* settings schema is large and version-evolving, and every key not named below survives as
|
|
814
|
+
* opaque residue named as such — the partial-governance posture `settings-local` already
|
|
815
|
+
* holds. What is deliberately absent is not residue but *ownership*: `hooks`,
|
|
816
|
+
* `enabledPlugins` and `extraKnownMarketplaces` are the collection addresses of the
|
|
817
|
+
* {@link hook}, {@link installedPlugin} and {@link knownMarketplace} kinds, so a member
|
|
818
|
+
* registers there and this container never types the key. Declaring one here would author
|
|
819
|
+
* the same segment twice.
|
|
820
|
+
*/
|
|
821
|
+
export interface Settings {
|
|
822
|
+
/** JSON Schema URL for editor autocomplete; Claude Code ignores it at load time. */
|
|
823
|
+
readonly $schema?: string;
|
|
824
|
+
/** Tool-permission rules: `{ allow, ask, deny }`, each an array of rule strings, plus `defaultMode`. */
|
|
825
|
+
readonly permissions?: Readonly<Record<string, unknown>>;
|
|
826
|
+
/** Environment variables applied to every session and its subprocesses, a map of string to string. */
|
|
827
|
+
readonly env?: Readonly<Record<string, string>>;
|
|
828
|
+
/** The model this project runs as. */
|
|
829
|
+
readonly model?: string;
|
|
830
|
+
/** Attribution Claude Code adds to commits and pull requests: `{ commit, pr, sessionUrl }`. */
|
|
831
|
+
readonly attribution?: Readonly<Record<string, unknown>>;
|
|
832
|
+
/**
|
|
833
|
+
* Whether to add a Claude co-author trailer to git commits.
|
|
834
|
+
*
|
|
835
|
+
* @deprecated Claude Code documents `attribution` as this key's replacement
|
|
836
|
+
* (code.claude.com/docs/en/settings-reference#attribution, retrieved 2026-09-22).
|
|
837
|
+
*/
|
|
838
|
+
readonly includeCoAuthoredBy?: boolean;
|
|
839
|
+
/** Whether project MCP servers from `.mcp.json` are auto-approved. */
|
|
840
|
+
readonly enableAllProjectMcpServers?: boolean;
|
|
841
|
+
/** MCP servers from `.mcp.json` to approve. */
|
|
842
|
+
readonly enabledMcpjsonServers?: readonly string[];
|
|
843
|
+
/** MCP servers from `.mcp.json` to reject. */
|
|
844
|
+
readonly disabledMcpjsonServers?: readonly string[];
|
|
845
|
+
/** The output rendering style. */
|
|
846
|
+
readonly outputStyle?: string;
|
|
847
|
+
/** Whether to enable auto-memory for this project. */
|
|
848
|
+
readonly autoMemoryEnabled?: boolean;
|
|
849
|
+
/** Absolute or `~/`-prefixed path for auto-memory storage, honored at any settings scope. */
|
|
850
|
+
readonly autoMemoryDirectory?: string;
|
|
851
|
+
/**
|
|
852
|
+
* The opaque residue — every documented-but-untyped top-level key the committed file
|
|
853
|
+
* carries, projected flat and key-sorted after the typed fields above. The settings
|
|
854
|
+
* reference documents several hundred keys and gains more, so the remainder is opaque
|
|
855
|
+
* and *named* rather than indicted; this bag is the name. The two halves partition the
|
|
856
|
+
* file's key space, which the bag's type holds: a key typed above belongs above and is
|
|
857
|
+
* unspellable here. Nor is it an escape hatch for the three collection addresses:
|
|
858
|
+
* `hooks`, `enabledPlugins` and `extraKnownMarketplaces` are their kinds' to author, so
|
|
859
|
+
* a key of those names does not belong here either
|
|
860
|
+
* (code.claude.com/docs/en/settings-reference, retrieved 2026-09-22).
|
|
861
|
+
*/
|
|
862
|
+
readonly residue?: Residue<Settings>;
|
|
863
|
+
}
|
|
864
|
+
/**
|
|
865
|
+
* `settings` — `.claude/settings.json`, a whole-file JSON document at the **committed**
|
|
866
|
+
* commitment class: the program authors every key, `emit` renders the file whole, and the
|
|
867
|
+
* member's byte fingerprint makes a hand edit to any part of it drift under the root
|
|
868
|
+
* `fresh` clause (decision 0050). Its top-level keys are its fields; identity is the fixed singleton stem
|
|
869
|
+
* `settings` (the `file` unit shape — a project's committed settings are the one file at
|
|
870
|
+
* this documented path, so no declared key names it). Channel-less: configuration the
|
|
871
|
+
* harness reads, never surfaced to the model.
|
|
872
|
+
*
|
|
873
|
+
* It is the **container** of three registration collection addresses — `hooks`,
|
|
874
|
+
* `enabledPlugins`, `extraKnownMarketplaces` — which keep their own kinds
|
|
875
|
+
* ({@link hook}, {@link installedPlugin}, {@link knownMarketplace}). Those segments and
|
|
876
|
+
* this member's opaque residue are one file: emit renders the declared segments in
|
|
877
|
+
* address order, then the residue — which the member itself authors through
|
|
878
|
+
* {@link Settings.residue}, flat and key-sorted behind its typed fields
|
|
879
|
+
* (code.claude.com/docs/en/settings, retrieved 2026-09-22).
|
|
880
|
+
*/
|
|
881
|
+
export declare const settings: KindDefinition<Settings>;
|
|
882
|
+
/**
|
|
883
|
+
* The default contract for `settings` — the structural container keys typed, everything
|
|
884
|
+
* else opaque. The settings reference documents several hundred keys, most of them scalar
|
|
885
|
+
* preferences and many of them managed-scope-only, so a `closedKeys()` allow-list would
|
|
886
|
+
* strand a valid committed file the moment upstream adds a key: the residue is opaque, not
|
|
887
|
+
* indicted. What stays decidable is the shape of the documented object-valued keys a
|
|
888
|
+
* committed team file carries — `permissions`, `env` and `attribution` are each a JSON
|
|
889
|
+
* *object*, and a value that is not one cannot be applied — so each is gated as a `map`.
|
|
890
|
+
*
|
|
891
|
+
* `hooks` is **not** gated here, and its absence is ownership rather than an opaque
|
|
892
|
+
* residue key: the committed file's `hooks` object is the {@link hook} kind's collection
|
|
893
|
+
* address, so its shape is that kind's contract to hold, member by member. This is the one
|
|
894
|
+
* place `settingsLocalDefaultContract`'s guidance does not transfer — a hook in the local
|
|
895
|
+
* overlay is opaque residue precisely because no kind reads it there.
|
|
896
|
+
*
|
|
897
|
+
* Deliberately absent as undecidable: whether a permission rule reads correctly, whether
|
|
898
|
+
* an env var is one this project needs, whether the chosen `model` exists — semantic
|
|
899
|
+
* judgment, never a gate clause.
|
|
900
|
+
*/
|
|
901
|
+
export declare const settingsDefaultContract: readonly Clause[];
|
|
796
902
|
/**
|
|
797
903
|
* The default contract for `skill` — Anthropic's documented skill contract: the Agent
|
|
798
904
|
* Skills open standard (agentskills.io), Anthropic's platform upload
|
package/dist/src/builtins.js
CHANGED
|
@@ -620,9 +620,10 @@ export const marketplaceDefaultContract = [
|
|
|
620
620
|
* target, its rows derived at read time and no row of it ever landing in the lock. Its
|
|
621
621
|
* top-level keys are its fields; identity is the fixed singleton stem `settings.local` (the
|
|
622
622
|
* `file` unit shape — every machine's overlay is the one file at this path, so no declared
|
|
623
|
-
* key names it).
|
|
624
|
-
*
|
|
625
|
-
*
|
|
623
|
+
* key names it). The keys it does not type ride {@link SettingsLocal.residue}, the opaque
|
|
624
|
+
* channel 0036's partial governance names. Channel-less: machine configuration read by the
|
|
625
|
+
* harness, never surfaced to the model (code.claude.com/docs/en/settings, retrieved
|
|
626
|
+
* 2026-07-16; decisions 0032/0034/0036).
|
|
626
627
|
*/
|
|
627
628
|
export const settingsLocal = kind({
|
|
628
629
|
name: "settings-local",
|
|
@@ -662,6 +663,66 @@ export const settingsLocalDefaultContract = [
|
|
|
662
663
|
cite: "https://code.claude.com/docs/en/settings#available-settings (retrieved 2026-07-16)",
|
|
663
664
|
}),
|
|
664
665
|
];
|
|
666
|
+
/**
|
|
667
|
+
* `settings` — `.claude/settings.json`, a whole-file JSON document at the **committed**
|
|
668
|
+
* commitment class: the program authors every key, `emit` renders the file whole, and the
|
|
669
|
+
* member's byte fingerprint makes a hand edit to any part of it drift under the root
|
|
670
|
+
* `fresh` clause (decision 0050). Its top-level keys are its fields; identity is the fixed singleton stem
|
|
671
|
+
* `settings` (the `file` unit shape — a project's committed settings are the one file at
|
|
672
|
+
* this documented path, so no declared key names it). Channel-less: configuration the
|
|
673
|
+
* harness reads, never surfaced to the model.
|
|
674
|
+
*
|
|
675
|
+
* It is the **container** of three registration collection addresses — `hooks`,
|
|
676
|
+
* `enabledPlugins`, `extraKnownMarketplaces` — which keep their own kinds
|
|
677
|
+
* ({@link hook}, {@link installedPlugin}, {@link knownMarketplace}). Those segments and
|
|
678
|
+
* this member's opaque residue are one file: emit renders the declared segments in
|
|
679
|
+
* address order, then the residue — which the member itself authors through
|
|
680
|
+
* {@link Settings.residue}, flat and key-sorted behind its typed fields
|
|
681
|
+
* (code.claude.com/docs/en/settings, retrieved 2026-09-22).
|
|
682
|
+
*/
|
|
683
|
+
export const settings = kind({
|
|
684
|
+
name: "settings",
|
|
685
|
+
locus: { kind: "at", root: ".claude", glob: "settings.json" },
|
|
686
|
+
format: "json-document",
|
|
687
|
+
unitShape: "file",
|
|
688
|
+
registration: [],
|
|
689
|
+
});
|
|
690
|
+
/**
|
|
691
|
+
* The default contract for `settings` — the structural container keys typed, everything
|
|
692
|
+
* else opaque. The settings reference documents several hundred keys, most of them scalar
|
|
693
|
+
* preferences and many of them managed-scope-only, so a `closedKeys()` allow-list would
|
|
694
|
+
* strand a valid committed file the moment upstream adds a key: the residue is opaque, not
|
|
695
|
+
* indicted. What stays decidable is the shape of the documented object-valued keys a
|
|
696
|
+
* committed team file carries — `permissions`, `env` and `attribution` are each a JSON
|
|
697
|
+
* *object*, and a value that is not one cannot be applied — so each is gated as a `map`.
|
|
698
|
+
*
|
|
699
|
+
* `hooks` is **not** gated here, and its absence is ownership rather than an opaque
|
|
700
|
+
* residue key: the committed file's `hooks` object is the {@link hook} kind's collection
|
|
701
|
+
* address, so its shape is that kind's contract to hold, member by member. This is the one
|
|
702
|
+
* place `settingsLocalDefaultContract`'s guidance does not transfer — a hook in the local
|
|
703
|
+
* overlay is opaque residue precisely because no kind reads it there.
|
|
704
|
+
*
|
|
705
|
+
* Deliberately absent as undecidable: whether a permission rule reads correctly, whether
|
|
706
|
+
* an env var is one this project needs, whether the chosen `model` exists — semantic
|
|
707
|
+
* judgment, never a gate clause.
|
|
708
|
+
*/
|
|
709
|
+
export const settingsDefaultContract = [
|
|
710
|
+
clause(type("permissions", ["map"]), {
|
|
711
|
+
severity: "required",
|
|
712
|
+
guidance: "`permissions` is the tool-permission object — `allow`, `ask` and `deny` rule arrays plus `defaultMode`. A value that is not an object carries no rules Claude Code can read, so the rules a whole team relies on silently apply nothing.",
|
|
713
|
+
cite: "https://code.claude.com/docs/en/settings-reference#permissions (retrieved 2026-09-22)",
|
|
714
|
+
}),
|
|
715
|
+
clause(type("env", ["map"]), {
|
|
716
|
+
severity: "required",
|
|
717
|
+
guidance: "`env` is a map of environment variables — string keys to string values — applied to every session and its subprocesses. A non-object value is not a shorter spelling of one variable; it is a shape the loader cannot expand, so none of the variables take effect.",
|
|
718
|
+
cite: "https://code.claude.com/docs/en/settings-reference#env (retrieved 2026-09-22)",
|
|
719
|
+
}),
|
|
720
|
+
clause(type("attribution", ["map"]), {
|
|
721
|
+
severity: "required",
|
|
722
|
+
guidance: "`attribution` is the commit/PR attribution object — `commit`, `pr` and `sessionUrl`. A non-object value configures none of them, so Claude Code falls back to its default trailer with no error, which is exactly the outcome a project setting this key was avoiding.",
|
|
723
|
+
cite: "https://code.claude.com/docs/en/settings-reference#attribution (retrieved 2026-09-22)",
|
|
724
|
+
}),
|
|
725
|
+
];
|
|
665
726
|
/**
|
|
666
727
|
* The default contract for `skill` — Anthropic's documented skill contract: the Agent
|
|
667
728
|
* Skills open standard (agentskills.io), Anthropic's platform upload
|
|
@@ -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, Hook, InstalledPlugin, KnownMarketplace, Marketplace, MarketplacePlugin, MarketplaceSource, McpServer, Memory, PluginManifest, Rule, SettingsLocal, Skill, SupportingDoc, } from "./builtins.js";
|
|
11
|
-
export { agent, agentDefaultContract, command, commandDefaultContract, hook, hookDefaultContract, installedPlugin, installedPluginDefaultContract, knownMarketplace, knownMarketplaceDefaultContract, marketplace, marketplaceDefaultContract, mcpServer, mcpServerDefaultContract, memory, memoryAnthropicDefaultContract, pluginManifest, pluginManifestDefaultContract, rule, ruleDefaultContract, settingsLocal, settingsLocalDefaultContract, skill, skillDefaultContract, supportingDoc, supportingDocDefaultContract, } from "./builtins.js";
|
|
10
|
+
export type { Agent, Hook, InstalledPlugin, KnownMarketplace, Marketplace, MarketplacePlugin, MarketplaceSource, McpServer, Memory, PluginManifest, Rule, Settings, SettingsLocal, Skill, SupportingDoc, } from "./builtins.js";
|
|
11
|
+
export { agent, agentDefaultContract, command, commandDefaultContract, hook, hookDefaultContract, installedPlugin, installedPluginDefaultContract, knownMarketplace, knownMarketplaceDefaultContract, marketplace, marketplaceDefaultContract, mcpServer, mcpServerDefaultContract, memory, memoryAnthropicDefaultContract, pluginManifest, pluginManifestDefaultContract, rule, ruleDefaultContract, settings, settingsDefaultContract, settingsLocal, settingsLocalDefaultContract, skill, skillDefaultContract, supportingDoc, supportingDocDefaultContract, } 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, hook, hookDefaultContract, installedPlugin, installedPluginDefaultContract, knownMarketplace, knownMarketplaceDefaultContract, marketplace, marketplaceDefaultContract, mcpServer, mcpServerDefaultContract, memory, memoryAnthropicDefaultContract, pluginManifest, pluginManifestDefaultContract, rule, ruleDefaultContract, settingsLocal, settingsLocalDefaultContract, skill, skillDefaultContract, supportingDoc, supportingDocDefaultContract, } from "./builtins.js";
|
|
10
|
+
export { agent, agentDefaultContract, command, commandDefaultContract, hook, hookDefaultContract, installedPlugin, installedPluginDefaultContract, knownMarketplace, knownMarketplaceDefaultContract, marketplace, marketplaceDefaultContract, mcpServer, mcpServerDefaultContract, memory, memoryAnthropicDefaultContract, pluginManifest, pluginManifestDefaultContract, rule, ruleDefaultContract, settings, settingsDefaultContract, settingsLocal, settingsLocalDefaultContract, skill, skillDefaultContract, supportingDoc, supportingDocDefaultContract, } from "./builtins.js";
|
|
11
11
|
export { blocks, file, text } from "./prose.js";
|
package/dist/src/contract.d.ts
CHANGED
|
@@ -40,8 +40,10 @@ export interface Predicate {
|
|
|
40
40
|
* `incoming_min`/`incoming_max`/`outgoing_min`/`outgoing_max`). */
|
|
41
41
|
readonly args?: Readonly<Record<string, number>>;
|
|
42
42
|
/**
|
|
43
|
-
*
|
|
44
|
-
*
|
|
43
|
+
* The **requirement name** a clause reads its source set from — `membership`'s
|
|
44
|
+
* allowed-value target and `reached-from`'s closure roots, one naming scheme for
|
|
45
|
+
* both. A separate slot from `field` since `membership` names a checked field too,
|
|
46
|
+
* and `reached-from` names no field at all.
|
|
45
47
|
*/
|
|
46
48
|
readonly target?: string;
|
|
47
49
|
/**
|
|
@@ -79,6 +81,13 @@ export interface Predicate {
|
|
|
79
81
|
};
|
|
80
82
|
/** `require_sections`'s required heading list. */
|
|
81
83
|
readonly sections?: readonly string[];
|
|
84
|
+
/**
|
|
85
|
+
* A by-incidence clause's **field set** — the fields whose edges the selection is
|
|
86
|
+
* filtered to. `degree`'s bound filter and `reached-from`'s via set both ride it:
|
|
87
|
+
* one concept, one slot, mirroring the lock's own shared `fields` column rather than
|
|
88
|
+
* a `degree`-private key. Absent ⇒ unfiltered, every edge at the member.
|
|
89
|
+
*/
|
|
90
|
+
readonly fields?: readonly string[];
|
|
82
91
|
/** `extent`'s declared unit — the render-side size proxy the bound is measured in. */
|
|
83
92
|
readonly unit?: ExtentUnit;
|
|
84
93
|
}
|
|
@@ -185,6 +194,56 @@ export declare const globValid: (field: string) => Predicate;
|
|
|
185
194
|
* wrong must not block.
|
|
186
195
|
*/
|
|
187
196
|
export declare const mentionReachable: (scopeField: string, gateField: string) => Predicate;
|
|
197
|
+
/**
|
|
198
|
+
* Every selected member is reachable from the world — its own inbound registration edge
|
|
199
|
+
* is live, or a reachable member imports it. A member whose every declared registration
|
|
200
|
+
* channel is provably dead (a `paths` glob set matching no file, a blank
|
|
201
|
+
* description-trigger field) and that no live importer reaches is authored configuration
|
|
202
|
+
* the harness never loads.
|
|
203
|
+
*
|
|
204
|
+
* Names **no field**: the grain is each-grain over the *root* selection — the whole
|
|
205
|
+
* governed forest — and the verdict is read off the reference graph's reachability
|
|
206
|
+
* closure, never off any member's own fields. So it composes into a root `contract`,
|
|
207
|
+
* where a member-grain clause is refused: there is no one kind whose schema a root
|
|
208
|
+
* clause could name a field in.
|
|
209
|
+
*/
|
|
210
|
+
export declare const reachable: () => Predicate;
|
|
211
|
+
/**
|
|
212
|
+
* Every lock row a selected member owns still matches disk — its projection's byte
|
|
213
|
+
* fingerprint, and each fingerprinted source dependency a layout import or a
|
|
214
|
+
* composed-prose include recorded. A drifted row means the authored source changed and
|
|
215
|
+
* `emit` has not run, or the emitted bytes were hand-edited.
|
|
216
|
+
*
|
|
217
|
+
* Drift is one comparison in one vocabulary, so one predicate carries all of it: a
|
|
218
|
+
* hand-edited projection, a moved import target and a moved include target are the same
|
|
219
|
+
* freshness fact over the same rows, and hardening freshness hardens it once.
|
|
220
|
+
*
|
|
221
|
+
* Names **no field**, like {@link reachable}: the argument is the committed lock read
|
|
222
|
+
* against disk, never any member's own fields. So it composes into a root `contract`,
|
|
223
|
+
* whose selection is the whole governed forest — a per-kind binding beside the root's
|
|
224
|
+
* would double-report one row, and a user-declared kind carries no default contract to
|
|
225
|
+
* bind it in.
|
|
226
|
+
*/
|
|
227
|
+
export declare const fresh: () => Predicate;
|
|
228
|
+
/**
|
|
229
|
+
* Every document discovery finds at a **governed locus** is one the lock declares a
|
|
230
|
+
* member for — a file-content kind's committed document joined against the provenance
|
|
231
|
+
* rows, a layout host's document against the `layout_source` record `emit` writes when
|
|
232
|
+
* it reads one. A stranger at a governed locus is loaded by Claude Code and maintained
|
|
233
|
+
* by nobody: `emit` never reaps it, `guard` never bound it, and every address under it
|
|
234
|
+
* resolves to nothing.
|
|
235
|
+
*
|
|
236
|
+
* Kept apart from {@link fresh} rather than folded into it: a stale pin and an
|
|
237
|
+
* undeclared document are different facts with different remedies — re-emit versus
|
|
238
|
+
* declare the member — and a read-only ground kind draws the second routinely while
|
|
239
|
+
* its pins stay fresh, so hardening one must not harden the other.
|
|
240
|
+
*
|
|
241
|
+
* Names **no field**, like {@link fresh}: the subject is the discovery walk read
|
|
242
|
+
* against the lock's declarations, never any member's own fields. So it composes into a
|
|
243
|
+
* root `contract` — what is governed is a locus, not a population, and a per-kind
|
|
244
|
+
* binding would have no member to hang off.
|
|
245
|
+
*/
|
|
246
|
+
export declare const locusDeclared: () => Predicate;
|
|
188
247
|
/**
|
|
189
248
|
* Every edge the member's kind declares is placed by the format that renders the member
|
|
190
249
|
* — a format that omits one renders a contract the prose does not represent. Names no
|
|
@@ -202,7 +261,16 @@ export declare const count: (bounds: {
|
|
|
202
261
|
export declare const unique: (field: string) => Predicate;
|
|
203
262
|
/** Every selected member's `field` value is drawn from a feature over the selection `target` declares. */
|
|
204
263
|
export declare const membership: (field: string, target: string) => Predicate;
|
|
205
|
-
/**
|
|
264
|
+
/**
|
|
265
|
+
* The in/out edge-count bound every selected member must land in. At least one
|
|
266
|
+
* direction must be given.
|
|
267
|
+
*
|
|
268
|
+
* `fields` filters the by-incidence selection both bounds range over — the clause's
|
|
269
|
+
* own filter, not either direction's. Omit it and the bound ranges over every edge at
|
|
270
|
+
* the member; name a set and it ranges over the union of those fields' edges, so two
|
|
271
|
+
* fields to one target count two. Containment (`contains:<kind>`) counts only where a
|
|
272
|
+
* filter names it.
|
|
273
|
+
*/
|
|
206
274
|
export declare const degree: (bounds: {
|
|
207
275
|
incoming?: {
|
|
208
276
|
min?: number;
|
|
@@ -212,7 +280,32 @@ export declare const degree: (bounds: {
|
|
|
212
280
|
min?: number;
|
|
213
281
|
max?: number;
|
|
214
282
|
};
|
|
283
|
+
fields?: readonly string[];
|
|
215
284
|
}) => Predicate;
|
|
285
|
+
/**
|
|
286
|
+
* Every selected member lies in the forward closure of `roots` over the `via` field
|
|
287
|
+
* set — `degree`'s global counterpart. `degree` asks the local question (how many arcs
|
|
288
|
+
* sit at this member), so the first orphan of a dead chain fires and none behind it;
|
|
289
|
+
* this asks the global one, so every member the roots cannot reach is equally a
|
|
290
|
+
* finding.
|
|
291
|
+
*
|
|
292
|
+
* `roots` names a **requirement**, spelled exactly as `membership` names its target
|
|
293
|
+
* set: rootness is a role, and a role is opt-in, so the roots are declared once and
|
|
294
|
+
* never re-derived from a kind. A root holds trivially, and the walk visits each node
|
|
295
|
+
* once, so a cycle inside the closure terminates and moves no verdict.
|
|
296
|
+
*
|
|
297
|
+
* `via` is the same by-incidence field set `degree`'s filter rides. Omit it and the
|
|
298
|
+
* closure follows every edge at a member but containment; name a set and it follows
|
|
299
|
+
* the union of those fields' arcs, so a mention or an import counts only where the set
|
|
300
|
+
* names its field.
|
|
301
|
+
*
|
|
302
|
+
* Each-grain over the **selection** the clause binds to, and judged by the reference
|
|
303
|
+
* graph rather than any member's own fields. It belongs in **no default contract**: a
|
|
304
|
+
* clause demanding every member be reached is the declaration-density demand
|
|
305
|
+
* `specs/intent.md`'s invariant 1 forbids unless the author declares it
|
|
306
|
+
* (`specs/decisions/0056-reached-from-joins-the-vocabulary.md`).
|
|
307
|
+
*/
|
|
308
|
+
export declare const reachedFrom: (roots: string, via?: readonly string[]) => Predicate;
|
|
216
309
|
/**
|
|
217
310
|
* A guarded clause — a predicate (restricted to `type` or `enum`) that acts as a
|
|
218
311
|
* guard, with a body of ordinary clauses that fire only where the guard holds.
|
package/dist/src/contract.js
CHANGED
|
@@ -130,6 +130,56 @@ export const mentionReachable = (scopeField, gateField) => ({
|
|
|
130
130
|
field: scopeField,
|
|
131
131
|
gate: gateField,
|
|
132
132
|
});
|
|
133
|
+
/**
|
|
134
|
+
* Every selected member is reachable from the world — its own inbound registration edge
|
|
135
|
+
* is live, or a reachable member imports it. A member whose every declared registration
|
|
136
|
+
* channel is provably dead (a `paths` glob set matching no file, a blank
|
|
137
|
+
* description-trigger field) and that no live importer reaches is authored configuration
|
|
138
|
+
* the harness never loads.
|
|
139
|
+
*
|
|
140
|
+
* Names **no field**: the grain is each-grain over the *root* selection — the whole
|
|
141
|
+
* governed forest — and the verdict is read off the reference graph's reachability
|
|
142
|
+
* closure, never off any member's own fields. So it composes into a root `contract`,
|
|
143
|
+
* where a member-grain clause is refused: there is no one kind whose schema a root
|
|
144
|
+
* clause could name a field in.
|
|
145
|
+
*/
|
|
146
|
+
export const reachable = () => ({ key: "reachable" });
|
|
147
|
+
/**
|
|
148
|
+
* Every lock row a selected member owns still matches disk — its projection's byte
|
|
149
|
+
* fingerprint, and each fingerprinted source dependency a layout import or a
|
|
150
|
+
* composed-prose include recorded. A drifted row means the authored source changed and
|
|
151
|
+
* `emit` has not run, or the emitted bytes were hand-edited.
|
|
152
|
+
*
|
|
153
|
+
* Drift is one comparison in one vocabulary, so one predicate carries all of it: a
|
|
154
|
+
* hand-edited projection, a moved import target and a moved include target are the same
|
|
155
|
+
* freshness fact over the same rows, and hardening freshness hardens it once.
|
|
156
|
+
*
|
|
157
|
+
* Names **no field**, like {@link reachable}: the argument is the committed lock read
|
|
158
|
+
* against disk, never any member's own fields. So it composes into a root `contract`,
|
|
159
|
+
* whose selection is the whole governed forest — a per-kind binding beside the root's
|
|
160
|
+
* would double-report one row, and a user-declared kind carries no default contract to
|
|
161
|
+
* bind it in.
|
|
162
|
+
*/
|
|
163
|
+
export const fresh = () => ({ key: "fresh" });
|
|
164
|
+
/**
|
|
165
|
+
* Every document discovery finds at a **governed locus** is one the lock declares a
|
|
166
|
+
* member for — a file-content kind's committed document joined against the provenance
|
|
167
|
+
* rows, a layout host's document against the `layout_source` record `emit` writes when
|
|
168
|
+
* it reads one. A stranger at a governed locus is loaded by Claude Code and maintained
|
|
169
|
+
* by nobody: `emit` never reaps it, `guard` never bound it, and every address under it
|
|
170
|
+
* resolves to nothing.
|
|
171
|
+
*
|
|
172
|
+
* Kept apart from {@link fresh} rather than folded into it: a stale pin and an
|
|
173
|
+
* undeclared document are different facts with different remedies — re-emit versus
|
|
174
|
+
* declare the member — and a read-only ground kind draws the second routinely while
|
|
175
|
+
* its pins stay fresh, so hardening one must not harden the other.
|
|
176
|
+
*
|
|
177
|
+
* Names **no field**, like {@link fresh}: the subject is the discovery walk read
|
|
178
|
+
* against the lock's declarations, never any member's own fields. So it composes into a
|
|
179
|
+
* root `contract` — what is governed is a locus, not a population, and a per-kind
|
|
180
|
+
* binding would have no member to hang off.
|
|
181
|
+
*/
|
|
182
|
+
export const locusDeclared = () => ({ key: "locus-declared" });
|
|
133
183
|
/**
|
|
134
184
|
* Every edge the member's kind declares is placed by the format that renders the member
|
|
135
185
|
* — a format that omits one renders a contract the prose does not represent. Names no
|
|
@@ -161,7 +211,16 @@ export const membership = (field, target) => ({
|
|
|
161
211
|
field,
|
|
162
212
|
target,
|
|
163
213
|
});
|
|
164
|
-
/**
|
|
214
|
+
/**
|
|
215
|
+
* The in/out edge-count bound every selected member must land in. At least one
|
|
216
|
+
* direction must be given.
|
|
217
|
+
*
|
|
218
|
+
* `fields` filters the by-incidence selection both bounds range over — the clause's
|
|
219
|
+
* own filter, not either direction's. Omit it and the bound ranges over every edge at
|
|
220
|
+
* the member; name a set and it ranges over the union of those fields' edges, so two
|
|
221
|
+
* fields to one target count two. Containment (`contains:<kind>`) counts only where a
|
|
222
|
+
* filter names it.
|
|
223
|
+
*/
|
|
165
224
|
export const degree = (bounds) => {
|
|
166
225
|
const args = {};
|
|
167
226
|
if (bounds.incoming?.min !== undefined)
|
|
@@ -172,8 +231,36 @@ export const degree = (bounds) => {
|
|
|
172
231
|
args.outgoing_min = bounds.outgoing.min;
|
|
173
232
|
if (bounds.outgoing?.max !== undefined)
|
|
174
233
|
args.outgoing_max = bounds.outgoing.max;
|
|
175
|
-
return
|
|
234
|
+
return bounds.fields === undefined
|
|
235
|
+
? { key: "degree", args }
|
|
236
|
+
: { key: "degree", args, fields: bounds.fields };
|
|
176
237
|
};
|
|
238
|
+
/**
|
|
239
|
+
* Every selected member lies in the forward closure of `roots` over the `via` field
|
|
240
|
+
* set — `degree`'s global counterpart. `degree` asks the local question (how many arcs
|
|
241
|
+
* sit at this member), so the first orphan of a dead chain fires and none behind it;
|
|
242
|
+
* this asks the global one, so every member the roots cannot reach is equally a
|
|
243
|
+
* finding.
|
|
244
|
+
*
|
|
245
|
+
* `roots` names a **requirement**, spelled exactly as `membership` names its target
|
|
246
|
+
* set: rootness is a role, and a role is opt-in, so the roots are declared once and
|
|
247
|
+
* never re-derived from a kind. A root holds trivially, and the walk visits each node
|
|
248
|
+
* once, so a cycle inside the closure terminates and moves no verdict.
|
|
249
|
+
*
|
|
250
|
+
* `via` is the same by-incidence field set `degree`'s filter rides. Omit it and the
|
|
251
|
+
* closure follows every edge at a member but containment; name a set and it follows
|
|
252
|
+
* the union of those fields' arcs, so a mention or an import counts only where the set
|
|
253
|
+
* names its field.
|
|
254
|
+
*
|
|
255
|
+
* Each-grain over the **selection** the clause binds to, and judged by the reference
|
|
256
|
+
* graph rather than any member's own fields. It belongs in **no default contract**: a
|
|
257
|
+
* clause demanding every member be reached is the declaration-density demand
|
|
258
|
+
* `specs/intent.md`'s invariant 1 forbids unless the author declares it
|
|
259
|
+
* (`specs/decisions/0056-reached-from-joins-the-vocabulary.md`).
|
|
260
|
+
*/
|
|
261
|
+
export const reachedFrom = (roots, via) => via === undefined
|
|
262
|
+
? { key: "reached-from", target: roots }
|
|
263
|
+
: { key: "reached-from", target: roots, fields: via };
|
|
177
264
|
/**
|
|
178
265
|
* A guarded clause — a predicate (restricted to `type` or `enum`) that acts as a
|
|
179
266
|
* guard, with a body of ordinary clauses that fire only where the guard holds.
|
|
@@ -106,9 +106,9 @@ export declare function declaredRequirements(harness: Harness): Set<string>;
|
|
|
106
106
|
export declare function declaredAddresses(harness: Harness): Set<string>;
|
|
107
107
|
/**
|
|
108
108
|
* Every discoverable (`at`-locus) kind the program declares — the deferral signal a
|
|
109
|
-
* dangling mention is measured against (`prose.ts`'s `defersToGate`): a mention
|
|
110
|
-
* one of these
|
|
111
|
-
* naming no declared kind refuses at emit. Member kinds ∪ `expect` kinds; an embedded
|
|
109
|
+
* dangling mention is measured against (`prose.ts`'s `defersToGate`): a mention whose
|
|
110
|
+
* address is a host address of one of these and names no composed value defers to
|
|
111
|
+
* `check`, while a mention naming no declared kind refuses at emit. Member kinds ∪ `expect` kinds; an embedded
|
|
112
112
|
* kind is excluded — its members are composed within a host, never discovered, so a
|
|
113
113
|
* flat `kind:name` mention of one has no discovery locus to defer to.
|
|
114
114
|
*/
|