@dtmd/temper 0.0.2 → 0.0.4
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/README.md +1 -1
- package/dist/src/assembly.d.ts +4 -7
- package/dist/src/assembly.js +2 -3
- package/dist/src/builtins.d.ts +105 -0
- package/dist/src/builtins.js +183 -0
- package/dist/src/claude-code.d.ts +6 -4
- package/dist/src/claude-code.js +5 -4
- package/dist/src/contract.d.ts +66 -22
- package/dist/src/contract.js +51 -7
- package/dist/src/declarations.d.ts +60 -9
- package/dist/src/declarations.js +80 -18
- package/dist/src/emit.d.ts +34 -35
- package/dist/src/emit.js +37 -49
- package/dist/src/genres.d.ts +8 -35
- package/dist/src/genres.js +7 -40
- package/dist/src/index.d.ts +12 -14
- package/dist/src/index.js +8 -8
- package/package.json +1 -1
- package/dist/src/lock.d.ts +0 -43
- package/dist/src/lock.js +0 -127
- package/dist/src/project.d.ts +0 -79
- package/dist/src/project.js +0 -162
- package/dist/src/toml.d.ts +0 -26
- package/dist/src/toml.js +0 -194
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ consumes only declared data, offline, no Node.
|
|
|
11
11
|
## The six-noun face
|
|
12
12
|
|
|
13
13
|
- **`harness()`** — the assembly as one typed value: `members · expect ·
|
|
14
|
-
require · settings
|
|
14
|
+
require · settings` (`specs/architecture/40-composition.md`).
|
|
15
15
|
- **`kind<T>()` / `genre<T>()`** — the engine room: a kind is a typed
|
|
16
16
|
constructor plus five facts of runtime residue (label, locus, layout,
|
|
17
17
|
registration, edge fields — `specs/architecture/15-kinds.md`). The built-in
|
package/dist/src/assembly.d.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The assembly — `harness()` takes the whole as one typed value
|
|
3
3
|
* (`specs/architecture/40-composition.md`): `Harness = members · expect · require ·
|
|
4
|
-
* settings
|
|
4
|
+
* settings`. Like every SDK type it erases at the seam — the
|
|
5
5
|
* engine never sees the constructor, only the declaration rows it compiles to
|
|
6
6
|
* (`20-surface.md`). There is no second authoring surface: no `temper.toml`, no
|
|
7
7
|
* roster/bindings dialect (the Decision rejects both); composing partial
|
|
8
8
|
* harnesses is ordinary code.
|
|
9
9
|
*/
|
|
10
10
|
import type { Member, KindDefinition } from "./kind.js";
|
|
11
|
-
import type { Clause, Requirement
|
|
11
|
+
import type { Clause, Requirement } from "./contract.js";
|
|
12
12
|
/**
|
|
13
13
|
* One `expect` binding — universal: every member of `kind` owes these clauses
|
|
14
14
|
* (`40-composition.md`). Keyed by the kind **value**, an import never a string;
|
|
@@ -18,7 +18,7 @@ export interface ExpectBinding {
|
|
|
18
18
|
readonly kind: KindDefinition<object>;
|
|
19
19
|
readonly clauses: readonly Clause[];
|
|
20
20
|
}
|
|
21
|
-
/** The composed harness — the
|
|
21
|
+
/** The composed harness — the four fields, erased to rows at the seam. */
|
|
22
22
|
export interface Harness {
|
|
23
23
|
/** The member roster — the assembly's imports (`40-composition.md`, "`members`"). */
|
|
24
24
|
readonly members: readonly Member[];
|
|
@@ -28,11 +28,9 @@ export interface Harness {
|
|
|
28
28
|
readonly require: Readonly<Record<string, Requirement>>;
|
|
29
29
|
/** The residual harness-level settings with no member home (a shrinking list). */
|
|
30
30
|
readonly settings: Readonly<Record<string, unknown>>;
|
|
31
|
-
/** The dead-registration dial — the severity of an unreachable member, or absent (no findings). */
|
|
32
|
-
readonly reachability?: Severity;
|
|
33
31
|
}
|
|
34
32
|
/**
|
|
35
|
-
* Compose the harness from its
|
|
33
|
+
* Compose the harness from its four fields — ordinary code, Turing-completeness
|
|
36
34
|
* quarantined at authoring time (`00-intent.md`, the SDK Decision). Absent
|
|
37
35
|
* fields default empty; the member list is the only required part.
|
|
38
36
|
*/
|
|
@@ -41,5 +39,4 @@ export declare function harness(init: {
|
|
|
41
39
|
expect?: readonly ExpectBinding[];
|
|
42
40
|
require?: Readonly<Record<string, Requirement>>;
|
|
43
41
|
settings?: Readonly<Record<string, unknown>>;
|
|
44
|
-
reachability?: Severity;
|
|
45
42
|
}): Harness;
|
package/dist/src/assembly.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The assembly — `harness()` takes the whole as one typed value
|
|
3
3
|
* (`specs/architecture/40-composition.md`): `Harness = members · expect · require ·
|
|
4
|
-
* settings
|
|
4
|
+
* settings`. Like every SDK type it erases at the seam — the
|
|
5
5
|
* engine never sees the constructor, only the declaration rows it compiles to
|
|
6
6
|
* (`20-surface.md`). There is no second authoring surface: no `temper.toml`, no
|
|
7
7
|
* roster/bindings dialect (the Decision rejects both); composing partial
|
|
8
8
|
* harnesses is ordinary code.
|
|
9
9
|
*/
|
|
10
10
|
/**
|
|
11
|
-
* Compose the harness from its
|
|
11
|
+
* Compose the harness from its four fields — ordinary code, Turing-completeness
|
|
12
12
|
* quarantined at authoring time (`00-intent.md`, the SDK Decision). Absent
|
|
13
13
|
* fields default empty; the member list is the only required part.
|
|
14
14
|
*/
|
|
@@ -18,6 +18,5 @@ export function harness(init) {
|
|
|
18
18
|
expect: init.expect ?? [],
|
|
19
19
|
require: init.require ?? {},
|
|
20
20
|
settings: init.settings ?? {},
|
|
21
|
-
reachability: init.reachability,
|
|
22
21
|
};
|
|
23
22
|
}
|
package/dist/src/builtins.d.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import type { KindDefinition } from "./kind.js";
|
|
13
13
|
import type { Prose } from "./prose.js";
|
|
14
|
+
import type { Clause } from "./contract.js";
|
|
14
15
|
/** A Claude Code skill — a directory whose entry file is `SKILL.md` with YAML frontmatter. */
|
|
15
16
|
export interface Skill {
|
|
16
17
|
/**
|
|
@@ -56,3 +57,107 @@ export interface Memory {
|
|
|
56
57
|
* a module-carried memory projects the root file.
|
|
57
58
|
*/
|
|
58
59
|
export declare const memory: KindDefinition<Memory>;
|
|
60
|
+
/**
|
|
61
|
+
* The floor for `skill` — Anthropic's documented skill contract: the Agent
|
|
62
|
+
* Skills open standard (agentskills.io), Anthropic's platform upload
|
|
63
|
+
* validation, and Claude Code's own docs (`packages/skill.anthropic/PACKAGE.md`,
|
|
64
|
+
* the curated authoring reference this migrates verbatim; `10-contracts.md`,
|
|
65
|
+
* "named for its source"). All sources retrieved 2026-07-01.
|
|
66
|
+
*
|
|
67
|
+
* Checks the strictest documented profile: the spec and upload validation are
|
|
68
|
+
* hard, Claude Code's runtime is deliberately forgiving ("All fields are
|
|
69
|
+
* optional") — a skill that passes here loads everywhere the format is
|
|
70
|
+
* honored, not merely on the machine it was written on; where the runtime
|
|
71
|
+
* diverges, the clause's own guidance says so.
|
|
72
|
+
*
|
|
73
|
+
* Deliberately absent — undecidable, so never gate clauses: whether the
|
|
74
|
+
* description actually triggers well or reads third-person (semantic);
|
|
75
|
+
* vagueness/no-op detection (semantic); gerund naming (judgment). Two
|
|
76
|
+
* decidable spec rules are also absent, pending a vocabulary addition (a
|
|
77
|
+
* narrow shape predicate — `10-contracts.md` governs additions): the name
|
|
78
|
+
* must not start/end with a hyphen or contain consecutive hyphens; likewise
|
|
79
|
+
* the platform's "no XML tags in the description."
|
|
80
|
+
*
|
|
81
|
+
* Authoring notes the clauses cannot carry: prefer gerund or noun-phrase
|
|
82
|
+
* names (`processing-pdfs`, `pdf-processing`) over vague ones (`helper`,
|
|
83
|
+
* `utils`); `disable-model-invocation: true` for side-effectful workflows you
|
|
84
|
+
* want to time yourself; `user-invocable: false` for background knowledge
|
|
85
|
+
* that is not a command; `metadata` is the sanctioned home for versioning —
|
|
86
|
+
* there is no top-level `version` field.
|
|
87
|
+
*/
|
|
88
|
+
export declare const skillFloor: readonly Clause[];
|
|
89
|
+
/**
|
|
90
|
+
* The floor for `rule` — Anthropic's documented contract for a Claude Code
|
|
91
|
+
* rules file, sourced from the memory docs (`.claude/rules/` landed in
|
|
92
|
+
* v2.0.64; `packages/rule.anthropic/PACKAGE.md`, the curated authoring
|
|
93
|
+
* reference this migrates verbatim). All sources retrieved 2026-07-01.
|
|
94
|
+
*
|
|
95
|
+
* `paths` is the one documented frontmatter key for rules: glob patterns
|
|
96
|
+
* (brace expansion supported) that scope the rule to matching files. Rules
|
|
97
|
+
* without it load at launch with the same priority as CLAUDE.md; path-scoped
|
|
98
|
+
* rules load when Claude reads a matching file. Note skills now take a
|
|
99
|
+
* `paths` key too — the two schemas are separate. (Guidance only: an
|
|
100
|
+
* optional field asserts nothing decidable, so it carries no clause of its
|
|
101
|
+
* own — `10-contracts.md`, "Judged at the node scope": `required` is the one
|
|
102
|
+
* presence predicate, and its absence is not itself a predicate.)
|
|
103
|
+
* https://code.claude.com/docs/en/memory#path-specific-rules (retrieved 2026-07-01)
|
|
104
|
+
*
|
|
105
|
+
* What the clauses cannot carry, as guidance: keep a rule to facts Claude
|
|
106
|
+
* should hold whenever the rule is in scope — concrete enough to verify ("use
|
|
107
|
+
* 2-space indentation", not "format code properly"). If an entry is a
|
|
108
|
+
* multi-step procedure or only matters occasionally, it belongs in a skill
|
|
109
|
+
* (on-demand) rather than a rule (always-on). Prefer path-scoped rules when
|
|
110
|
+
* one convention governs scattered paths; prefer per-directory CLAUDE.md when
|
|
111
|
+
* directory owners maintain their own. Treat rules like code: prune them when
|
|
112
|
+
* behavior drifts, and test a change by watching whether Claude's behavior
|
|
113
|
+
* actually shifts.
|
|
114
|
+
*/
|
|
115
|
+
export declare const ruleFloor: readonly Clause[];
|
|
116
|
+
/**
|
|
117
|
+
* The floor for the qualified `claude-code.memory` kind — Anthropic's
|
|
118
|
+
* documented contract for a project `CLAUDE.md` (`packages/memory.anthropic/PACKAGE.md`,
|
|
119
|
+
* the curated authoring reference this migrates verbatim). Retrieved 2026-07-02.
|
|
120
|
+
*
|
|
121
|
+
* Deliberately near-empty, because the format is: `CLAUDE.md` is plain
|
|
122
|
+
* markdown with no documented frontmatter and no required fields
|
|
123
|
+
* (code.claude.com/docs/en/memory, retrieved 2026-07-02), so there is no
|
|
124
|
+
* schema to gate — manufacturing a required field or a forbidden-key list
|
|
125
|
+
* would fake a check the format does not carry (`specs/intent/00-intent.md`,
|
|
126
|
+
* law 3: decidable clauses only). The single clause is a context-cost
|
|
127
|
+
* budget; everything else the contract could say is guidance.
|
|
128
|
+
*
|
|
129
|
+
* What the clauses cannot carry, as guidance: a `paths:` frontmatter block
|
|
130
|
+
* belongs on a `.claude/rules/*.md` file, not on `CLAUDE.md` — the memory
|
|
131
|
+
* docs document `paths` only for rules, so a rules-style header on
|
|
132
|
+
* `CLAUDE.md` is dead configuration. Split a large file with `@path` imports
|
|
133
|
+
* (resolved relative to the importing file, absolute allowed, recursion
|
|
134
|
+
* capped at four hops; wrap a path in backticks to mention it without
|
|
135
|
+
* importing). If the repo already ships an `AGENTS.md` for other agents,
|
|
136
|
+
* don't duplicate it — create a `CLAUDE.md` that `@AGENTS.md`-imports it (or
|
|
137
|
+
* symlink, except on Windows where the import is the recommended bridge).
|
|
138
|
+
* Mind the loading asymmetry: every ancestor `CLAUDE.md` loads in full at
|
|
139
|
+
* launch, while files in subdirectories load only when Claude reads a file
|
|
140
|
+
* there — so a rule that must always hold belongs above the working
|
|
141
|
+
* directory, not below it. Personal, un-shared notes go in `CLAUDE.local.md`
|
|
142
|
+
* (gitignored), appended after `CLAUDE.md` at its level.
|
|
143
|
+
*/
|
|
144
|
+
export declare const memoryAnthropicFloor: readonly Clause[];
|
|
145
|
+
/**
|
|
146
|
+
* The floor for the qualified `agents-md.memory` kind — the AGENTS.md
|
|
147
|
+
* standard's contract for a memory file, which is that there is almost none
|
|
148
|
+
* (`packages/memory.agents-md/PACKAGE.md`, the curated authoring reference
|
|
149
|
+
* this migrates). Guidance-only, and that is the honest encoding: `AGENTS.md`
|
|
150
|
+
* "is just standard Markdown" with no required fields, no sections, and no
|
|
151
|
+
* frontmatter (agents.md, retrieved 2026-07-02); the format deliberately
|
|
152
|
+
* constrains nothing. A floor that manufactured a required field, a size
|
|
153
|
+
* gate, or a forbidden-key list would assert a contract the standard
|
|
154
|
+
* disclaims (`specs/intent/00-intent.md`, law 3). Even the tempting size
|
|
155
|
+
* number is a *tool's* rule, not the format's: agents read the closest
|
|
156
|
+
* `AGENTS.md` in the tree (nested, nearest-wins); Codex concatenates the
|
|
157
|
+
* chain root-to-cwd and stops once combined size hits a byte budget, not a
|
|
158
|
+
* per-file line count; Gemini CLI reads `GEMINI.md` by default and only
|
|
159
|
+
* treats `AGENTS.md` as an alias when configured; Claude Code does not read
|
|
160
|
+
* `AGENTS.md` natively — bridge it with a `CLAUDE.md` that
|
|
161
|
+
* `@AGENTS.md`-imports it. All retrieved 2026-07-02.
|
|
162
|
+
*/
|
|
163
|
+
export declare const memoryAgentsMdFloor: readonly Clause[];
|
package/dist/src/builtins.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* package — the provider face is a subpath export") — never from the root.
|
|
11
11
|
*/
|
|
12
12
|
import { kind } from "./kind.js";
|
|
13
|
+
import { allowedChars, clause, deny, forbiddenKeys, maxLen, maxLines, minLen, nameMatchesDir, required, } from "./contract.js";
|
|
13
14
|
/**
|
|
14
15
|
* `skill` — `.claude/skills/<name>/SKILL.md`, a directory unit, YAML frontmatter
|
|
15
16
|
* carrying `name` then `description`; registers a description trigger
|
|
@@ -49,3 +50,185 @@ export const memory = kind({
|
|
|
49
50
|
unitShape: "file",
|
|
50
51
|
registration: { via: "always" },
|
|
51
52
|
});
|
|
53
|
+
/**
|
|
54
|
+
* The floor for `skill` — Anthropic's documented skill contract: the Agent
|
|
55
|
+
* Skills open standard (agentskills.io), Anthropic's platform upload
|
|
56
|
+
* validation, and Claude Code's own docs (`packages/skill.anthropic/PACKAGE.md`,
|
|
57
|
+
* the curated authoring reference this migrates verbatim; `10-contracts.md`,
|
|
58
|
+
* "named for its source"). All sources retrieved 2026-07-01.
|
|
59
|
+
*
|
|
60
|
+
* Checks the strictest documented profile: the spec and upload validation are
|
|
61
|
+
* hard, Claude Code's runtime is deliberately forgiving ("All fields are
|
|
62
|
+
* optional") — a skill that passes here loads everywhere the format is
|
|
63
|
+
* honored, not merely on the machine it was written on; where the runtime
|
|
64
|
+
* diverges, the clause's own guidance says so.
|
|
65
|
+
*
|
|
66
|
+
* Deliberately absent — undecidable, so never gate clauses: whether the
|
|
67
|
+
* description actually triggers well or reads third-person (semantic);
|
|
68
|
+
* vagueness/no-op detection (semantic); gerund naming (judgment). Two
|
|
69
|
+
* decidable spec rules are also absent, pending a vocabulary addition (a
|
|
70
|
+
* narrow shape predicate — `10-contracts.md` governs additions): the name
|
|
71
|
+
* must not start/end with a hyphen or contain consecutive hyphens; likewise
|
|
72
|
+
* the platform's "no XML tags in the description."
|
|
73
|
+
*
|
|
74
|
+
* Authoring notes the clauses cannot carry: prefer gerund or noun-phrase
|
|
75
|
+
* names (`processing-pdfs`, `pdf-processing`) over vague ones (`helper`,
|
|
76
|
+
* `utils`); `disable-model-invocation: true` for side-effectful workflows you
|
|
77
|
+
* want to time yourself; `user-invocable: false` for background knowledge
|
|
78
|
+
* that is not a command; `metadata` is the sanctioned home for versioning —
|
|
79
|
+
* there is no top-level `version` field.
|
|
80
|
+
*/
|
|
81
|
+
export const skillFloor = [
|
|
82
|
+
clause(required("name"), {
|
|
83
|
+
severity: "required",
|
|
84
|
+
guidance: "Every skill declares a `name` — the slug the harness binds to. Claude Code alone would default it from the directory name, but a nameless skill is not portable: the spec and Anthropic's upload validation both require it.",
|
|
85
|
+
cite: "https://agentskills.io/specification#frontmatter (retrieved 2026-07-01)",
|
|
86
|
+
}),
|
|
87
|
+
clause(minLen("name", 1), {
|
|
88
|
+
severity: "required",
|
|
89
|
+
guidance: "A present-but-empty name fails the spec's 1-64 character bound.",
|
|
90
|
+
cite: "https://agentskills.io/specification#name-field (retrieved 2026-07-01)",
|
|
91
|
+
}),
|
|
92
|
+
clause(allowedChars("name", { ranges: ["a-z", "0-9"], chars: "-" }), {
|
|
93
|
+
severity: "required",
|
|
94
|
+
guidance: "Lowercase letters, digits, and hyphens only — `PDF-Processing` is the spec's own counter-example. The charset also keeps XML out of the name, which Anthropic's upload validation separately forbids.",
|
|
95
|
+
cite: "https://agentskills.io/specification#name-field (retrieved 2026-07-01)",
|
|
96
|
+
}),
|
|
97
|
+
clause(maxLen("name", 64), {
|
|
98
|
+
severity: "required",
|
|
99
|
+
guidance: "Keep the name short and slug-like; it becomes a directory and an id.",
|
|
100
|
+
cite: "https://agentskills.io/specification#name-field (retrieved 2026-07-01)",
|
|
101
|
+
}),
|
|
102
|
+
clause(deny("name", ["anthropic", "claude"]), {
|
|
103
|
+
severity: "required",
|
|
104
|
+
guidance: "Reserved words, enforced by Anthropic's platform upload validation (not by the open spec, and not by Claude Code's runtime — which itself ships a `claude-api` skill). Keep them out if the skill will ever travel through the API or claude.ai.",
|
|
105
|
+
cite: "https://platform.claude.com/docs/en/agents-and-tools/agent-skills/overview#skill-structure (retrieved 2026-07-01)",
|
|
106
|
+
}),
|
|
107
|
+
clause(nameMatchesDir(), {
|
|
108
|
+
severity: "required",
|
|
109
|
+
guidance: "The spec requires the name to match its parent directory. Claude Code decouples the two (the frontmatter name is a display label; the directory names the slash command, except for a plugin-root SKILL.md) — but a mismatch is a portability trap and a reader trap even where it loads.",
|
|
110
|
+
cite: "https://agentskills.io/specification#name-field (retrieved 2026-07-01)",
|
|
111
|
+
}),
|
|
112
|
+
clause(required("description"), {
|
|
113
|
+
severity: "required",
|
|
114
|
+
guidance: "The description is how the model chooses this skill from potentially 100+ available — it is the skill's API. Claude Code would fall back to the body's first paragraph; the spec and upload validation require it declared.",
|
|
115
|
+
cite: "https://agentskills.io/specification#frontmatter (retrieved 2026-07-01)",
|
|
116
|
+
}),
|
|
117
|
+
clause(minLen("description", 1), {
|
|
118
|
+
severity: "required",
|
|
119
|
+
guidance: "Say both what the skill does and when to use it, with the keywords a user would naturally say. Write in third person — the text is injected into the system prompt, and inconsistent point-of-view causes discovery problems.",
|
|
120
|
+
cite: "https://agentskills.io/specification#description-field (retrieved 2026-07-01)",
|
|
121
|
+
}),
|
|
122
|
+
clause(maxLen("description", 1024), {
|
|
123
|
+
severity: "required",
|
|
124
|
+
guidance: "The spec's cap. Claude Code additionally truncates the skill listing at 1,536 combined characters (description + when_to_use) — truncation, not rejection, but text past the fold cannot help the model choose.",
|
|
125
|
+
cite: "https://agentskills.io/specification#description-field (retrieved 2026-07-01)",
|
|
126
|
+
}),
|
|
127
|
+
clause(maxLen("compatibility", 500), {
|
|
128
|
+
severity: "required",
|
|
129
|
+
guidance: "Optional field; when present the spec caps it at 500 characters. Most skills do not need it.",
|
|
130
|
+
cite: "https://agentskills.io/specification#frontmatter (retrieved 2026-07-01)",
|
|
131
|
+
}),
|
|
132
|
+
clause(maxLines(500), {
|
|
133
|
+
severity: "advisory",
|
|
134
|
+
guidance: "Progressive disclosure: keep SKILL.md under 500 lines and move detailed reference material to separate files, one level deep. Once a skill loads, its body stays in context across turns — every line is a recurring token cost. The context window is a public good.",
|
|
135
|
+
cite: "https://agentskills.io/specification#progressive-disclosure (retrieved 2026-07-01)",
|
|
136
|
+
}),
|
|
137
|
+
clause(forbiddenKeys(["globs", "alwaysApply"]), {
|
|
138
|
+
severity: "required",
|
|
139
|
+
guidance: "Cursor `.mdc` keys. Nothing in the Agent Skills spec or Claude Code's documented frontmatter accepts them — a skill authored with them is carrying dead configuration that another tool's semantics silently fail to apply.",
|
|
140
|
+
cite: "https://agentskills.io/specification#frontmatter (retrieved 2026-07-01)",
|
|
141
|
+
}),
|
|
142
|
+
];
|
|
143
|
+
/**
|
|
144
|
+
* The floor for `rule` — Anthropic's documented contract for a Claude Code
|
|
145
|
+
* rules file, sourced from the memory docs (`.claude/rules/` landed in
|
|
146
|
+
* v2.0.64; `packages/rule.anthropic/PACKAGE.md`, the curated authoring
|
|
147
|
+
* reference this migrates verbatim). All sources retrieved 2026-07-01.
|
|
148
|
+
*
|
|
149
|
+
* `paths` is the one documented frontmatter key for rules: glob patterns
|
|
150
|
+
* (brace expansion supported) that scope the rule to matching files. Rules
|
|
151
|
+
* without it load at launch with the same priority as CLAUDE.md; path-scoped
|
|
152
|
+
* rules load when Claude reads a matching file. Note skills now take a
|
|
153
|
+
* `paths` key too — the two schemas are separate. (Guidance only: an
|
|
154
|
+
* optional field asserts nothing decidable, so it carries no clause of its
|
|
155
|
+
* own — `10-contracts.md`, "Judged at the node scope": `required` is the one
|
|
156
|
+
* presence predicate, and its absence is not itself a predicate.)
|
|
157
|
+
* https://code.claude.com/docs/en/memory#path-specific-rules (retrieved 2026-07-01)
|
|
158
|
+
*
|
|
159
|
+
* What the clauses cannot carry, as guidance: keep a rule to facts Claude
|
|
160
|
+
* should hold whenever the rule is in scope — concrete enough to verify ("use
|
|
161
|
+
* 2-space indentation", not "format code properly"). If an entry is a
|
|
162
|
+
* multi-step procedure or only matters occasionally, it belongs in a skill
|
|
163
|
+
* (on-demand) rather than a rule (always-on). Prefer path-scoped rules when
|
|
164
|
+
* one convention governs scattered paths; prefer per-directory CLAUDE.md when
|
|
165
|
+
* directory owners maintain their own. Treat rules like code: prune them when
|
|
166
|
+
* behavior drifts, and test a change by watching whether Claude's behavior
|
|
167
|
+
* actually shifts.
|
|
168
|
+
*/
|
|
169
|
+
export const ruleFloor = [
|
|
170
|
+
clause(forbiddenKeys(["description", "globs", "alwaysApply"]), {
|
|
171
|
+
severity: "required",
|
|
172
|
+
guidance: "Cursor `.mdc` keys. Claude Code's documented rules schema is `paths`-only; a rule authored with Cursor frontmatter is configuration another tool's semantics silently fail to honor — the rule loads, the scoping you meant does not. (That Claude Code ignores unknown keys is observed behavior, not documented contract — the documented schema is the citation.)",
|
|
173
|
+
cite: "https://code.claude.com/docs/en/memory#path-specific-rules (retrieved 2026-07-01)",
|
|
174
|
+
}),
|
|
175
|
+
clause(maxLines(200), {
|
|
176
|
+
severity: "advisory",
|
|
177
|
+
guidance: "Unconditional rules are always-on context, paid every session: the docs' size target is under 200 lines per memory file — 'longer files consume more context and reduce adherence.' (Distinct from the hard 200-line/25KB cutoff, which applies only to auto-memory MEMORY.md; rules load in full regardless of length.) For each line ask: would removing it cause Claude to make mistakes? If not, cut it.",
|
|
178
|
+
cite: "https://code.claude.com/docs/en/memory#write-effective-instructions (retrieved 2026-07-01)",
|
|
179
|
+
}),
|
|
180
|
+
];
|
|
181
|
+
/**
|
|
182
|
+
* The floor for the qualified `claude-code.memory` kind — Anthropic's
|
|
183
|
+
* documented contract for a project `CLAUDE.md` (`packages/memory.anthropic/PACKAGE.md`,
|
|
184
|
+
* the curated authoring reference this migrates verbatim). Retrieved 2026-07-02.
|
|
185
|
+
*
|
|
186
|
+
* Deliberately near-empty, because the format is: `CLAUDE.md` is plain
|
|
187
|
+
* markdown with no documented frontmatter and no required fields
|
|
188
|
+
* (code.claude.com/docs/en/memory, retrieved 2026-07-02), so there is no
|
|
189
|
+
* schema to gate — manufacturing a required field or a forbidden-key list
|
|
190
|
+
* would fake a check the format does not carry (`specs/intent/00-intent.md`,
|
|
191
|
+
* law 3: decidable clauses only). The single clause is a context-cost
|
|
192
|
+
* budget; everything else the contract could say is guidance.
|
|
193
|
+
*
|
|
194
|
+
* What the clauses cannot carry, as guidance: a `paths:` frontmatter block
|
|
195
|
+
* belongs on a `.claude/rules/*.md` file, not on `CLAUDE.md` — the memory
|
|
196
|
+
* docs document `paths` only for rules, so a rules-style header on
|
|
197
|
+
* `CLAUDE.md` is dead configuration. Split a large file with `@path` imports
|
|
198
|
+
* (resolved relative to the importing file, absolute allowed, recursion
|
|
199
|
+
* capped at four hops; wrap a path in backticks to mention it without
|
|
200
|
+
* importing). If the repo already ships an `AGENTS.md` for other agents,
|
|
201
|
+
* don't duplicate it — create a `CLAUDE.md` that `@AGENTS.md`-imports it (or
|
|
202
|
+
* symlink, except on Windows where the import is the recommended bridge).
|
|
203
|
+
* Mind the loading asymmetry: every ancestor `CLAUDE.md` loads in full at
|
|
204
|
+
* launch, while files in subdirectories load only when Claude reads a file
|
|
205
|
+
* there — so a rule that must always hold belongs above the working
|
|
206
|
+
* directory, not below it. Personal, un-shared notes go in `CLAUDE.local.md`
|
|
207
|
+
* (gitignored), appended after `CLAUDE.md` at its level.
|
|
208
|
+
*/
|
|
209
|
+
export const memoryAnthropicFloor = [
|
|
210
|
+
clause(maxLines(200), {
|
|
211
|
+
severity: "advisory",
|
|
212
|
+
guidance: "CLAUDE.md is always-on context, paid every session. The memory docs' size target is under 200 lines per memory file — 'longer files consume more context and reduce adherence.' For each line ask: would removing it cause Claude to make mistakes? If not, cut it. (Advisory: Claude Code loads the file in full regardless of length; this is a context-cost budget, not a hard cutoff.)",
|
|
213
|
+
cite: "https://code.claude.com/docs/en/memory#write-effective-instructions (retrieved 2026-07-02)",
|
|
214
|
+
}),
|
|
215
|
+
];
|
|
216
|
+
/**
|
|
217
|
+
* The floor for the qualified `agents-md.memory` kind — the AGENTS.md
|
|
218
|
+
* standard's contract for a memory file, which is that there is almost none
|
|
219
|
+
* (`packages/memory.agents-md/PACKAGE.md`, the curated authoring reference
|
|
220
|
+
* this migrates). Guidance-only, and that is the honest encoding: `AGENTS.md`
|
|
221
|
+
* "is just standard Markdown" with no required fields, no sections, and no
|
|
222
|
+
* frontmatter (agents.md, retrieved 2026-07-02); the format deliberately
|
|
223
|
+
* constrains nothing. A floor that manufactured a required field, a size
|
|
224
|
+
* gate, or a forbidden-key list would assert a contract the standard
|
|
225
|
+
* disclaims (`specs/intent/00-intent.md`, law 3). Even the tempting size
|
|
226
|
+
* number is a *tool's* rule, not the format's: agents read the closest
|
|
227
|
+
* `AGENTS.md` in the tree (nested, nearest-wins); Codex concatenates the
|
|
228
|
+
* chain root-to-cwd and stops once combined size hits a byte budget, not a
|
|
229
|
+
* per-file line count; Gemini CLI reads `GEMINI.md` by default and only
|
|
230
|
+
* treats `AGENTS.md` as an alias when configured; Claude Code does not read
|
|
231
|
+
* `AGENTS.md` natively — bridge it with a `CLAUDE.md` that
|
|
232
|
+
* `@AGENTS.md`-imports it. All retrieved 2026-07-02.
|
|
233
|
+
*/
|
|
234
|
+
export const memoryAgentsMdFloor = [];
|
|
@@ -5,9 +5,11 @@
|
|
|
5
5
|
* targets Claude Code imports the built-in kinds from here, never the root:
|
|
6
6
|
* the root carries only the six-noun core, and identity travels by import
|
|
7
7
|
* (`specs/architecture/15-kinds.md`), so a subpath specifier is a full module
|
|
8
|
-
* specifier like any other. The built-in floors (exported clause arrays
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* specifier like any other. The built-in floors (exported clause arrays,
|
|
9
|
+
* `10-contracts.md`, "A shared clause set — a floor") join the kinds here
|
|
10
|
+
* too: adoption is `import { skill, skillFloor } from "@dtmd/temper/claude-code"`.
|
|
11
11
|
*/
|
|
12
12
|
export type { Memory, Rule, Skill } from "./builtins.js";
|
|
13
|
-
export { memory, rule, skill } from "./builtins.js";
|
|
13
|
+
export { memory, memoryAgentsMdFloor, memoryAnthropicFloor, rule, ruleFloor, skill, skillFloor } from "./builtins.js";
|
|
14
|
+
export type { Blocks, File, Prose, Text } from "./prose.js";
|
|
15
|
+
export { blocks, file, text } from "./prose.js";
|
package/dist/src/claude-code.js
CHANGED
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
* targets Claude Code imports the built-in kinds from here, never the root:
|
|
6
6
|
* the root carries only the six-noun core, and identity travels by import
|
|
7
7
|
* (`specs/architecture/15-kinds.md`), so a subpath specifier is a full module
|
|
8
|
-
* specifier like any other. The built-in floors (exported clause arrays
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* specifier like any other. The built-in floors (exported clause arrays,
|
|
9
|
+
* `10-contracts.md`, "A shared clause set — a floor") join the kinds here
|
|
10
|
+
* too: adoption is `import { skill, skillFloor } from "@dtmd/temper/claude-code"`.
|
|
11
11
|
*/
|
|
12
|
-
export { memory, rule, skill } from "./builtins.js";
|
|
12
|
+
export { memory, memoryAgentsMdFloor, memoryAnthropicFloor, rule, ruleFloor, skill, skillFloor } from "./builtins.js";
|
|
13
|
+
export { blocks, file, text } from "./prose.js";
|
package/dist/src/contract.d.ts
CHANGED
|
@@ -1,29 +1,53 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Contracts — clauses and requirements as typed values (`specs/architecture/10-contracts.md`).
|
|
3
3
|
* A clause is `predicate · severity · guidance · cite`; a requirement is
|
|
4
|
-
* `means · kind · required ·
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* `means · kind · required · clauses? · verifiedBy?`. Both erase to compiled data
|
|
5
|
+
* at the seam (`20-surface.md`): the author composes typed objects, the engine
|
|
6
|
+
* consumes their rows. The predicate vocabulary is the closed algebra — a clause
|
|
7
|
+
* outside it is a squiggle, not a runtime rejection (`10-contracts.md`, the two
|
|
8
|
+
* walls).
|
|
9
9
|
*/
|
|
10
10
|
import type { KindDefinition } from "./kind.js";
|
|
11
11
|
/** A clause's delivery posture: `required` gate-blocks, `advisory` reports. */
|
|
12
12
|
export type Severity = "required" | "advisory";
|
|
13
13
|
/**
|
|
14
|
-
* A member of the closed predicate algebra (`10-contracts.md`).
|
|
15
|
-
*
|
|
16
|
-
* severity
|
|
17
|
-
*
|
|
18
|
-
*
|
|
14
|
+
* A member of the closed predicate algebra (`10-contracts.md`). Both a kind's
|
|
15
|
+
* own `expect` clauses and a requirement's `clauses` compile to the row's full
|
|
16
|
+
* `key`/`field`/`severity`/argument shape (`declarations.ts` `clauseRow`): a
|
|
17
|
+
* floor clause's own `bound`/`charset`/`keys`/`values` ride the row alongside
|
|
18
|
+
* a requirement's `count`/`target`/`degree`, so the lock encodes the floor
|
|
19
|
+
* losslessly rather than identity+severity alone.
|
|
19
20
|
*/
|
|
20
21
|
export interface Predicate {
|
|
21
22
|
/** The predicate's clause key (`required`, `max_len`, `max_lines`, …). */
|
|
22
23
|
readonly key: string;
|
|
23
24
|
/** The field (or marker) the predicate constrains, when it names one. */
|
|
24
25
|
readonly field?: string;
|
|
25
|
-
/** The predicate's scalar bounds
|
|
26
|
+
/** The predicate's scalar bounds, keyed per predicate (`min`/`max`,
|
|
27
|
+
* `incoming_min`/`incoming_max`/`outgoing_min`/`outgoing_max`). */
|
|
26
28
|
readonly args?: Readonly<Record<string, number>>;
|
|
29
|
+
/**
|
|
30
|
+
* `membership`'s target requirement name — a separate slot from `field` (the
|
|
31
|
+
* checked field) since `membership` names both (`10-contracts.md`, "Judged
|
|
32
|
+
* at the node-set scope").
|
|
33
|
+
*/
|
|
34
|
+
readonly target?: string;
|
|
35
|
+
/** `allowed_chars`'s declared character class (`10-contracts.md`, "allowed_chars"). */
|
|
36
|
+
readonly charset?: Charset;
|
|
37
|
+
/** `forbidden_keys`'s forbidden key list. */
|
|
38
|
+
readonly keys?: readonly string[];
|
|
39
|
+
/** `enum`/`deny`'s permitted or forbidden value list. */
|
|
40
|
+
readonly values?: readonly string[];
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The character class `allowed_chars` admits — inclusive ranges plus individual
|
|
44
|
+
* characters, e.g. `[a-z0-9-]` (`10-contracts.md`, "Decision: `allowed_chars`,
|
|
45
|
+
* not a general `pattern` clause"). Each range is a `"<lo>-<hi>"` two-character
|
|
46
|
+
* span (`src/contract.rs` `parse_range`'s wire spelling).
|
|
47
|
+
*/
|
|
48
|
+
export interface Charset {
|
|
49
|
+
readonly ranges?: readonly string[];
|
|
50
|
+
readonly chars?: string;
|
|
27
51
|
}
|
|
28
52
|
/** A field or marker is present. */
|
|
29
53
|
export declare const required: (field: string) => Predicate;
|
|
@@ -34,15 +58,37 @@ export declare const minLen: (field: string, n: number) => Predicate;
|
|
|
34
58
|
/** The field's value is at most `n` characters. */
|
|
35
59
|
export declare const maxLen: (field: string, n: number) => Predicate;
|
|
36
60
|
/** The field's characters are drawn from a declared class (`allowed_chars`). */
|
|
37
|
-
export declare const allowedChars: (field: string) => Predicate;
|
|
61
|
+
export declare const allowedChars: (field: string, charset: Charset) => Predicate;
|
|
38
62
|
/** The member's body is at most `n` lines. */
|
|
39
63
|
export declare const maxLines: (n: number) => Predicate;
|
|
40
64
|
/** The forbidden keys (e.g. the Cursor `globs`/`alwaysApply` keys) are absent. */
|
|
41
|
-
export declare const forbiddenKeys: () => Predicate;
|
|
65
|
+
export declare const forbiddenKeys: (keys: readonly string[]) => Predicate;
|
|
66
|
+
/** The field's value is none of `values` (forbidden values). */
|
|
67
|
+
export declare const deny: (field: string, values: readonly string[]) => Predicate;
|
|
42
68
|
/** The named headings are present. */
|
|
43
69
|
export declare const requireSections: () => Predicate;
|
|
44
70
|
/** The member's name matches its directory. */
|
|
45
71
|
export declare const nameMatchesDir: () => Predicate;
|
|
72
|
+
/** The satisfier set's size lies in the inclusive `[min, max]` bound. */
|
|
73
|
+
export declare const count: (bounds: {
|
|
74
|
+
min?: number;
|
|
75
|
+
max?: number;
|
|
76
|
+
}) => Predicate;
|
|
77
|
+
/** The field's extracted value does not repeat across the satisfier set. */
|
|
78
|
+
export declare const unique: (field: string) => Predicate;
|
|
79
|
+
/** Every satisfier's `field` value is drawn from a feature over `target`'s own satisfier set. */
|
|
80
|
+
export declare const membership: (field: string, target: string) => Predicate;
|
|
81
|
+
/** The in/out edge-count bound every satisfier must land in. At least one direction must be given. */
|
|
82
|
+
export declare const degree: (bounds: {
|
|
83
|
+
incoming?: {
|
|
84
|
+
min?: number;
|
|
85
|
+
max?: number;
|
|
86
|
+
};
|
|
87
|
+
outgoing?: {
|
|
88
|
+
min?: number;
|
|
89
|
+
max?: number;
|
|
90
|
+
};
|
|
91
|
+
}) => Predicate;
|
|
46
92
|
/**
|
|
47
93
|
* A clause — a predicate the author marks with a severity, the just-in-time
|
|
48
94
|
* guidance the predicate cannot encode, and the external-fact `cite` that makes
|
|
@@ -64,20 +110,18 @@ export declare function clause(predicate: Predicate, opts: {
|
|
|
64
110
|
* A requirement — a named obligation on the harness (`10-contracts.md`,
|
|
65
111
|
* "Requirements"). `means` is the authored intent, carried never interpreted;
|
|
66
112
|
* `kind` constrains what may fill it **by import** (a value, never a string);
|
|
67
|
-
* `required` is the posture declaration;
|
|
68
|
-
*
|
|
113
|
+
* `required` is the posture declaration; `clauses` are the requirement's own
|
|
114
|
+
* set-/edge-scope demands — ordinary [`Clause`] values whose predicates range
|
|
115
|
+
* over the satisfier set (`count`/`unique`/`membership`) or its graph
|
|
116
|
+
* neighborhood (`degree`), the same four-channel clause as everywhere
|
|
117
|
+
* (`10-contracts.md`, "Decision: set-scope demands are clauses"); `verifiedBy`
|
|
118
|
+
* wires the behavioral remainder.
|
|
69
119
|
*/
|
|
70
120
|
export interface Requirement {
|
|
71
121
|
readonly means: string;
|
|
72
122
|
readonly kind?: KindDefinition<object>;
|
|
73
123
|
readonly required?: boolean;
|
|
74
|
-
readonly
|
|
75
|
-
readonly min?: number;
|
|
76
|
-
readonly max?: number;
|
|
77
|
-
};
|
|
78
|
-
readonly unique?: string;
|
|
79
|
-
readonly membership?: string;
|
|
80
|
-
readonly degree?: string;
|
|
124
|
+
readonly clauses?: readonly Clause[];
|
|
81
125
|
readonly verifiedBy?: string;
|
|
82
126
|
}
|
|
83
127
|
/** An identity helper — types a requirement literal at the keystroke (`40-composition.md`). */
|
package/dist/src/contract.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Contracts — clauses and requirements as typed values (`specs/architecture/10-contracts.md`).
|
|
3
3
|
* A clause is `predicate · severity · guidance · cite`; a requirement is
|
|
4
|
-
* `means · kind · required ·
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* `means · kind · required · clauses? · verifiedBy?`. Both erase to compiled data
|
|
5
|
+
* at the seam (`20-surface.md`): the author composes typed objects, the engine
|
|
6
|
+
* consumes their rows. The predicate vocabulary is the closed algebra — a clause
|
|
7
|
+
* outside it is a squiggle, not a runtime rejection (`10-contracts.md`, the two
|
|
8
|
+
* walls).
|
|
9
9
|
*/
|
|
10
10
|
// Node-scope predicates (`10-contracts.md`, "The predicate algebra").
|
|
11
11
|
/** A field or marker is present. */
|
|
@@ -17,15 +17,59 @@ export const minLen = (field, n) => ({ key: "min_len", field, args: { min: n } }
|
|
|
17
17
|
/** The field's value is at most `n` characters. */
|
|
18
18
|
export const maxLen = (field, n) => ({ key: "max_len", field, args: { max: n } });
|
|
19
19
|
/** The field's characters are drawn from a declared class (`allowed_chars`). */
|
|
20
|
-
export const allowedChars = (field) => ({
|
|
20
|
+
export const allowedChars = (field, charset) => ({
|
|
21
|
+
key: "allowed_chars",
|
|
22
|
+
field,
|
|
23
|
+
charset,
|
|
24
|
+
});
|
|
21
25
|
/** The member's body is at most `n` lines. */
|
|
22
26
|
export const maxLines = (n) => ({ key: "max_lines", args: { max: n } });
|
|
23
27
|
/** The forbidden keys (e.g. the Cursor `globs`/`alwaysApply` keys) are absent. */
|
|
24
|
-
export const forbiddenKeys = () => ({ key: "forbidden_keys" });
|
|
28
|
+
export const forbiddenKeys = (keys) => ({ key: "forbidden_keys", keys });
|
|
29
|
+
/** The field's value is none of `values` (forbidden values). */
|
|
30
|
+
export const deny = (field, values) => ({
|
|
31
|
+
key: "deny",
|
|
32
|
+
field,
|
|
33
|
+
values,
|
|
34
|
+
});
|
|
25
35
|
/** The named headings are present. */
|
|
26
36
|
export const requireSections = () => ({ key: "require_sections" });
|
|
27
37
|
/** The member's name matches its directory. */
|
|
28
38
|
export const nameMatchesDir = () => ({ key: "name-matches-dir" });
|
|
39
|
+
// Node-set/edge-scope predicates (`10-contracts.md`, "Judged at the node-set
|
|
40
|
+
// scope" / "Judged at the edge scope") — a requirement's set-scope demands ride
|
|
41
|
+
// these as ordinary clause values, the same four-channel `clause()` shape as
|
|
42
|
+
// the node-scope predicates above.
|
|
43
|
+
/** The satisfier set's size lies in the inclusive `[min, max]` bound. */
|
|
44
|
+
export const count = (bounds) => {
|
|
45
|
+
const args = {};
|
|
46
|
+
if (bounds.min !== undefined)
|
|
47
|
+
args.min = bounds.min;
|
|
48
|
+
if (bounds.max !== undefined)
|
|
49
|
+
args.max = bounds.max;
|
|
50
|
+
return { key: "count", args };
|
|
51
|
+
};
|
|
52
|
+
/** The field's extracted value does not repeat across the satisfier set. */
|
|
53
|
+
export const unique = (field) => ({ key: "unique", field });
|
|
54
|
+
/** Every satisfier's `field` value is drawn from a feature over `target`'s own satisfier set. */
|
|
55
|
+
export const membership = (field, target) => ({
|
|
56
|
+
key: "membership",
|
|
57
|
+
field,
|
|
58
|
+
target,
|
|
59
|
+
});
|
|
60
|
+
/** The in/out edge-count bound every satisfier must land in. At least one direction must be given. */
|
|
61
|
+
export const degree = (bounds) => {
|
|
62
|
+
const args = {};
|
|
63
|
+
if (bounds.incoming?.min !== undefined)
|
|
64
|
+
args.incoming_min = bounds.incoming.min;
|
|
65
|
+
if (bounds.incoming?.max !== undefined)
|
|
66
|
+
args.incoming_max = bounds.incoming.max;
|
|
67
|
+
if (bounds.outgoing?.min !== undefined)
|
|
68
|
+
args.outgoing_min = bounds.outgoing.min;
|
|
69
|
+
if (bounds.outgoing?.max !== undefined)
|
|
70
|
+
args.outgoing_max = bounds.outgoing.max;
|
|
71
|
+
return { key: "degree", args };
|
|
72
|
+
};
|
|
29
73
|
/** Compose a clause value — a predicate under a declared severity, with optional guidance/cite. */
|
|
30
74
|
export function clause(predicate, opts) {
|
|
31
75
|
return { predicate, severity: opts.severity, guidance: opts.guidance, cite: opts.cite };
|