@dtmd/temper 0.0.1
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 +57 -0
- package/dist/src/assembly.d.ts +45 -0
- package/dist/src/assembly.js +23 -0
- package/dist/src/builtins.d.ts +57 -0
- package/dist/src/builtins.js +50 -0
- package/dist/src/contract.d.ts +84 -0
- package/dist/src/contract.js +36 -0
- package/dist/src/declarations.d.ts +62 -0
- package/dist/src/declarations.js +133 -0
- package/dist/src/emit.d.ts +70 -0
- package/dist/src/emit.js +187 -0
- package/dist/src/genres.d.ts +65 -0
- package/dist/src/genres.js +57 -0
- package/dist/src/index.d.ts +32 -0
- package/dist/src/index.js +21 -0
- package/dist/src/kind.d.ts +138 -0
- package/dist/src/kind.js +63 -0
- package/dist/src/lock.d.ts +43 -0
- package/dist/src/lock.js +127 -0
- package/dist/src/needs.d.ts +31 -0
- package/dist/src/needs.js +28 -0
- package/dist/src/project.d.ts +79 -0
- package/dist/src/project.js +162 -0
- package/dist/src/prose.d.ts +78 -0
- package/dist/src/prose.js +0 -0
- package/dist/src/toml.d.ts +26 -0
- package/dist/src/toml.js +194 -0
- package/package.json +40 -0
package/dist/src/emit.js
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Emit — the compile from the six-noun face to the committed seam
|
|
3
|
+
* (`specs/architecture/20-surface.md`, "Emit — total, byte-reproducible, refusing";
|
|
4
|
+
* "The seam — one implementation"). The SDK implements **no semantics**: emit
|
|
5
|
+
* produces plain data — the declaration rows the engine reads (the internal
|
|
6
|
+
* versioned JSON pipe and the lock's `[declaration]` families), a byte-faithful
|
|
7
|
+
* `.claude/**` projection, and the lock. Emit is total (members are the only
|
|
8
|
+
* source), refuses before it writes a byte on a broken source, and is
|
|
9
|
+
* byte-reproducible — double-emit verified at every run (law 5).
|
|
10
|
+
*/
|
|
11
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
12
|
+
import { dirname, join, resolve as resolvePath } from "node:path";
|
|
13
|
+
import { renderText } from "./prose.js";
|
|
14
|
+
import { permissionUnion } from "./needs.js";
|
|
15
|
+
import { projectMember } from "./project.js";
|
|
16
|
+
import { lockRow, stampLock } from "./lock.js";
|
|
17
|
+
import { compileDeclarations, declarationsToJson } from "./declarations.js";
|
|
18
|
+
/**
|
|
19
|
+
* Resolve a member's prose to its final body bytes: a `file()` asset is read in
|
|
20
|
+
* byte-for-byte; a `text` body's mentions are resolution-checked (loud on a
|
|
21
|
+
* dangling address) and rendered by the one display rule; a `blocks()` body is
|
|
22
|
+
* refused until the fence format lands. The words are never reworded (law 5).
|
|
23
|
+
*
|
|
24
|
+
* # Throws
|
|
25
|
+
* If a `file()` asset does not resolve, a mention names no declared value, or a
|
|
26
|
+
* `blocks()` body is projected before `(genre-fence-format)` lands.
|
|
27
|
+
*/
|
|
28
|
+
function resolveBody(member, options) {
|
|
29
|
+
const prose = member.prose;
|
|
30
|
+
if (prose === undefined)
|
|
31
|
+
return "";
|
|
32
|
+
if (prose.kind === "file") {
|
|
33
|
+
const assetPath = resolvePath(options.baseDir ?? process.cwd(), prose.path);
|
|
34
|
+
try {
|
|
35
|
+
return readFileSync(assetPath, "utf8");
|
|
36
|
+
}
|
|
37
|
+
catch (cause) {
|
|
38
|
+
throw new Error(`member \`${member.name}\`: file() asset \`${prose.path}\` did not resolve ` +
|
|
39
|
+
`(looked at \`${assetPath}\`).`, { cause });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (prose.kind === "blocks") {
|
|
43
|
+
throw new Error(`member \`${member.name}\`: a blocks() body renders through the genre fence format, ` +
|
|
44
|
+
`deferred until its first consumer lands ((genre-fence-format), specs/architecture/15-kinds.md).`);
|
|
45
|
+
}
|
|
46
|
+
const mentionable = options.mentionable ?? new Set();
|
|
47
|
+
for (const mention of prose.mentions) {
|
|
48
|
+
if (!mentionable.has(mention.target.address)) {
|
|
49
|
+
throw new Error(`member \`${member.name}\`: mention of \`${mention.target.address}\` resolves to no ` +
|
|
50
|
+
`declared value — a mention cannot dangle (specs/architecture/45-governance.md).`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return renderText(prose);
|
|
54
|
+
}
|
|
55
|
+
/** Every requirement name a `satisfies` claim may fill — assembly `require` ∪ member `requires`. */
|
|
56
|
+
function declaredRequirements(harness) {
|
|
57
|
+
const set = new Set();
|
|
58
|
+
for (const name of Object.keys(harness.require))
|
|
59
|
+
set.add(name);
|
|
60
|
+
for (const member of harness.members) {
|
|
61
|
+
for (const name of Object.keys(member.requires))
|
|
62
|
+
set.add(name);
|
|
63
|
+
}
|
|
64
|
+
return set;
|
|
65
|
+
}
|
|
66
|
+
/** Every address a mention may name — declared requirement names ∪ each member's `kind:name`. */
|
|
67
|
+
function declaredAddresses(harness) {
|
|
68
|
+
const set = declaredRequirements(harness);
|
|
69
|
+
for (const member of harness.members)
|
|
70
|
+
set.add(`${member.kind}:${member.name}`);
|
|
71
|
+
return set;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The two declare-side refusals emit runs before it compiles a byte
|
|
75
|
+
* (`20-surface.md`, "Emit refuses before it writes"): a `satisfies` claim naming
|
|
76
|
+
* no declared requirement (a dangling join), and a `required` requirement no
|
|
77
|
+
* member fills (an unfilled required requirement).
|
|
78
|
+
*
|
|
79
|
+
* # Throws
|
|
80
|
+
* On a dangling `satisfies` join or an unfilled `required` requirement.
|
|
81
|
+
*/
|
|
82
|
+
function refuseBrokenSource(harness) {
|
|
83
|
+
const requirements = declaredRequirements(harness);
|
|
84
|
+
const filled = new Set();
|
|
85
|
+
for (const member of harness.members) {
|
|
86
|
+
for (const name of member.satisfies) {
|
|
87
|
+
if (!requirements.has(name)) {
|
|
88
|
+
throw new Error(`member \`${member.name}\`: \`satisfies\` claims requirement \`${name}\`, which no ` +
|
|
89
|
+
`harness-level or member-published requirement declares — a dangling join ` +
|
|
90
|
+
`(specs/architecture/20-surface.md, "Emit refuses before it writes").`);
|
|
91
|
+
}
|
|
92
|
+
filled.add(name);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const requiredSources = [];
|
|
96
|
+
for (const [name, requirement] of Object.entries(harness.require)) {
|
|
97
|
+
if (requirement.required)
|
|
98
|
+
requiredSources.push([name, "the assembly"]);
|
|
99
|
+
}
|
|
100
|
+
for (const member of harness.members) {
|
|
101
|
+
for (const [name, requirement] of Object.entries(member.requires)) {
|
|
102
|
+
if (requirement.required)
|
|
103
|
+
requiredSources.push([name, `member \`${member.name}\``]);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
for (const [name, source] of requiredSources) {
|
|
107
|
+
if (!filled.has(name)) {
|
|
108
|
+
throw new Error(`required requirement \`${name}\` (declared by ${source}) is filled by no member's ` +
|
|
109
|
+
`\`satisfies\` — an unfilled required requirement ` +
|
|
110
|
+
`(specs/architecture/20-surface.md, "Emit refuses before it writes").`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/** A member is projected iff its kind lives at a path locus (a genre member is not). */
|
|
115
|
+
function isProjected(member) {
|
|
116
|
+
return member.facts.locus.kind === "at";
|
|
117
|
+
}
|
|
118
|
+
/** The harness's projected members as projection inputs, deterministically kind-then-name ordered. */
|
|
119
|
+
function orderedProjectionInputs(harness, options) {
|
|
120
|
+
return [...harness.members]
|
|
121
|
+
.filter(isProjected)
|
|
122
|
+
.sort((a, b) => (a.kind < b.kind ? -1 : a.kind > b.kind ? 1 : 0) ||
|
|
123
|
+
(a.name < b.name ? -1 : a.name > b.name ? 1 : 0))
|
|
124
|
+
.map((member) => ({
|
|
125
|
+
facts: member.facts,
|
|
126
|
+
name: member.name,
|
|
127
|
+
fields: member.fields,
|
|
128
|
+
body: resolveBody(member, options),
|
|
129
|
+
}));
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Compile the whole face in one deterministic pass: the projection, the lock (its
|
|
133
|
+
* rollup and its declaration rows), the JSON pipe, and the derived permission
|
|
134
|
+
* union. Prose resolves once (`file()` assets read in, mentions resolution-checked
|
|
135
|
+
* against the harness's declared values). Double-emit verified — nondeterministic
|
|
136
|
+
* authoring is a loud failure, never a silent churn (law 5).
|
|
137
|
+
*/
|
|
138
|
+
export function emit(harness, options = {}) {
|
|
139
|
+
refuseBrokenSource(harness);
|
|
140
|
+
const resolve = {
|
|
141
|
+
mentionable: declaredAddresses(harness),
|
|
142
|
+
baseDir: options.baseDir,
|
|
143
|
+
};
|
|
144
|
+
const compile = () => {
|
|
145
|
+
const inputs = orderedProjectionInputs(harness, resolve);
|
|
146
|
+
const projections = inputs.map((input) => projectMember(input, { projectionDir: options.projectionDir }));
|
|
147
|
+
const rows = inputs.map((input, i) => lockRow(input.facts.name, projections[i]));
|
|
148
|
+
const declarations = compileDeclarations(harness);
|
|
149
|
+
return {
|
|
150
|
+
projections,
|
|
151
|
+
lock: stampLock(rows, declarations),
|
|
152
|
+
declarations,
|
|
153
|
+
seam: declarationsToJson(declarations),
|
|
154
|
+
permissions: permissionUnion(harness.members.flatMap((member) => [...member.needs])),
|
|
155
|
+
};
|
|
156
|
+
};
|
|
157
|
+
const first = compile();
|
|
158
|
+
const second = compile();
|
|
159
|
+
if (first.lock !== second.lock ||
|
|
160
|
+
first.seam !== second.seam ||
|
|
161
|
+
!sameProjections(first.projections, second.projections)) {
|
|
162
|
+
throw new Error("double-emit divergence: two passes over the same harness produced different bytes — " +
|
|
163
|
+
"authoring code is nondeterministic (a timestamp? an unordered map?).");
|
|
164
|
+
}
|
|
165
|
+
return first;
|
|
166
|
+
}
|
|
167
|
+
/** Whether two projection lists are byte-identical, path and bytes both. */
|
|
168
|
+
function sameProjections(a, b) {
|
|
169
|
+
return a.length === b.length && a.every((p, i) => p.path === b[i].path && p.bytes === b[i].bytes);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Run a full [`emit`] and write its committed artifacts under `targetDir`: the
|
|
173
|
+
* lock to `lock.toml` and each projection to its `.claude/**` path (parent
|
|
174
|
+
* directories created). Whole-file writes — a projection is regenerated, never
|
|
175
|
+
* patched. The JSON pipe is in-flight, not a committed artifact, so it is not
|
|
176
|
+
* written (`20-surface.md`, "the committed seam" is artifacts plus lock).
|
|
177
|
+
*/
|
|
178
|
+
export function writeEmit(harness, targetDir, options = {}) {
|
|
179
|
+
const result = emit(harness, { ...options, projectionDir: options.projectionDir ?? targetDir });
|
|
180
|
+
writeFileSync(join(targetDir, "lock.toml"), result.lock);
|
|
181
|
+
for (const projection of result.projections) {
|
|
182
|
+
const path = join(targetDir, projection.path);
|
|
183
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
184
|
+
writeFileSync(path, projection.bytes);
|
|
185
|
+
}
|
|
186
|
+
return result;
|
|
187
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Genre values — prose that declares its own anatomy (`specs/architecture/15-kinds.md`,
|
|
3
|
+
* "A genre is a kind at the block locus"; ratified `specs/intent/00-intent.md`, the
|
|
4
|
+
* genre Decision). A genre value's meaning-carrying fields are prose leaves —
|
|
5
|
+
* authored strings, law-5 protected one by one — plus keyed sibling collections.
|
|
6
|
+
* These constructors carry the **shape only**: any predicate over a genre value
|
|
7
|
+
* (a decision names at least one rejected alternative) is a clause some module
|
|
8
|
+
* ships, never here (`15-kinds.md`, the genre Decision).
|
|
9
|
+
*
|
|
10
|
+
* They are the posture-3 spelling — fully composed values passed to `blocks()`
|
|
11
|
+
* (`20-surface.md`). The byte-identical posture-2 fence render awaits
|
|
12
|
+
* `(genre-fence-format)`, deferred until its first consumer lands.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* A genre value serialized whole: leaves are authored strings keyed by field
|
|
16
|
+
* name; sibling collections are keyed at every level (`rejected."baked-projection"`),
|
|
17
|
+
* never positional — leaf addresses are structural and keyed (`20-surface.md`,
|
|
18
|
+
* the leaf-address Decision).
|
|
19
|
+
*/
|
|
20
|
+
export interface GenreValue {
|
|
21
|
+
/** The genre name — `decision`, `law`, `bound`, or a project's own. */
|
|
22
|
+
readonly genre: string;
|
|
23
|
+
/** The value's key — the identity a leaf address carries (`surface-authority`). */
|
|
24
|
+
readonly key: string;
|
|
25
|
+
/** Prose leaves: authored strings, law-5 protected one by one. */
|
|
26
|
+
readonly leaves: Readonly<Record<string, string>>;
|
|
27
|
+
/** Keyed sibling collections: collection → entry key → field → authored string. */
|
|
28
|
+
readonly collections: Readonly<Record<string, Readonly<Record<string, Readonly<Record<string, string>>>>>>;
|
|
29
|
+
}
|
|
30
|
+
/** A rejected alternative: keyed by option slug, its rationale a prose leaf. */
|
|
31
|
+
export interface Alternative {
|
|
32
|
+
readonly because: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The `decision` genre — the Chosen/Rejected convention, typed. Sibling
|
|
36
|
+
* collections are keyed by option slug, never positional: positional addresses
|
|
37
|
+
* die on insertion and reorder, which is exactly when impact must survive.
|
|
38
|
+
*/
|
|
39
|
+
export declare function decision(init: {
|
|
40
|
+
key: string;
|
|
41
|
+
chosen: string;
|
|
42
|
+
rejected?: Readonly<Record<string, Alternative>>;
|
|
43
|
+
}): GenreValue;
|
|
44
|
+
/** The `law` genre — a numbered law's statement with its named bounds. */
|
|
45
|
+
export declare function law(init: {
|
|
46
|
+
key: string;
|
|
47
|
+
statement: string;
|
|
48
|
+
bounds?: Readonly<Record<string, {
|
|
49
|
+
claim: string;
|
|
50
|
+
}>>;
|
|
51
|
+
}): GenreValue;
|
|
52
|
+
/** The `bound` genre — the honest bound: claim, deferral, unlock condition. */
|
|
53
|
+
export declare function bound(init: {
|
|
54
|
+
key: string;
|
|
55
|
+
claim: string;
|
|
56
|
+
deferred: string;
|
|
57
|
+
unlock: string;
|
|
58
|
+
}): GenreValue;
|
|
59
|
+
/** A project's own genre — the same machinery, an author-declared shape. */
|
|
60
|
+
export declare function genreValue(init: {
|
|
61
|
+
genre: string;
|
|
62
|
+
key: string;
|
|
63
|
+
leaves: Readonly<Record<string, string>>;
|
|
64
|
+
collections?: GenreValue["collections"];
|
|
65
|
+
}): GenreValue;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Genre values — prose that declares its own anatomy (`specs/architecture/15-kinds.md`,
|
|
3
|
+
* "A genre is a kind at the block locus"; ratified `specs/intent/00-intent.md`, the
|
|
4
|
+
* genre Decision). A genre value's meaning-carrying fields are prose leaves —
|
|
5
|
+
* authored strings, law-5 protected one by one — plus keyed sibling collections.
|
|
6
|
+
* These constructors carry the **shape only**: any predicate over a genre value
|
|
7
|
+
* (a decision names at least one rejected alternative) is a clause some module
|
|
8
|
+
* ships, never here (`15-kinds.md`, the genre Decision).
|
|
9
|
+
*
|
|
10
|
+
* They are the posture-3 spelling — fully composed values passed to `blocks()`
|
|
11
|
+
* (`20-surface.md`). The byte-identical posture-2 fence render awaits
|
|
12
|
+
* `(genre-fence-format)`, deferred until its first consumer lands.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* The `decision` genre — the Chosen/Rejected convention, typed. Sibling
|
|
16
|
+
* collections are keyed by option slug, never positional: positional addresses
|
|
17
|
+
* die on insertion and reorder, which is exactly when impact must survive.
|
|
18
|
+
*/
|
|
19
|
+
export function decision(init) {
|
|
20
|
+
return {
|
|
21
|
+
genre: "decision",
|
|
22
|
+
key: init.key,
|
|
23
|
+
leaves: { chosen: init.chosen },
|
|
24
|
+
collections: {
|
|
25
|
+
rejected: Object.fromEntries(Object.entries(init.rejected ?? {}).map(([slug, alt]) => [slug, { because: alt.because }])),
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/** The `law` genre — a numbered law's statement with its named bounds. */
|
|
30
|
+
export function law(init) {
|
|
31
|
+
return {
|
|
32
|
+
genre: "law",
|
|
33
|
+
key: init.key,
|
|
34
|
+
leaves: { statement: init.statement },
|
|
35
|
+
collections: {
|
|
36
|
+
bounds: Object.fromEntries(Object.entries(init.bounds ?? {}).map(([slug, bound]) => [slug, { claim: bound.claim }])),
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/** The `bound` genre — the honest bound: claim, deferral, unlock condition. */
|
|
41
|
+
export function bound(init) {
|
|
42
|
+
return {
|
|
43
|
+
genre: "bound",
|
|
44
|
+
key: init.key,
|
|
45
|
+
leaves: { claim: init.claim, deferred: init.deferred, unlock: init.unlock },
|
|
46
|
+
collections: {},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/** A project's own genre — the same machinery, an author-declared shape. */
|
|
50
|
+
export function genreValue(init) {
|
|
51
|
+
return {
|
|
52
|
+
genre: init.genre,
|
|
53
|
+
key: init.key,
|
|
54
|
+
leaves: init.leaves,
|
|
55
|
+
collections: init.collections ?? {},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* temper's authoring face — the six-noun model as a typed module library
|
|
3
|
+
* (`specs/intent/00-intent.md`, the SDK Decision; `specs/architecture/20-surface.md`).
|
|
4
|
+
* A harness author imports plain nouns — the built-in kinds, `harness()`, the
|
|
5
|
+
* clause and requirement constructors, `needs`, and the three prose constructors —
|
|
6
|
+
* and composes members as typed values. `emit` compiles the whole into the
|
|
7
|
+
* declaration rows the engine reads, a byte-faithful projection, and the lock;
|
|
8
|
+
* every type erases at the seam, and Turing-completeness stays quarantined at
|
|
9
|
+
* authoring time.
|
|
10
|
+
*/
|
|
11
|
+
export type { Blocks, File, Mention, Mentionable, Prose, Text } from "./prose.js";
|
|
12
|
+
export { blocks, file, renderText, text } from "./prose.js";
|
|
13
|
+
export type { Alternative, GenreValue } from "./genres.js";
|
|
14
|
+
export { bound, decision, genreValue, law } from "./genres.js";
|
|
15
|
+
export type { Capability } from "./needs.js";
|
|
16
|
+
export { bash, capability, permissionUnion } from "./needs.js";
|
|
17
|
+
export type { Clause, Predicate, Requirement, Severity } from "./contract.js";
|
|
18
|
+
export { allowedChars, clause, forbiddenKeys, maxLen, maxLines, minLen, nameMatchesDir, required, requireSections, requirement, type, } from "./contract.js";
|
|
19
|
+
export type { EdgeField, Format, KindDefinition, KindFacts, Locus, Member, MemberInit, Registration, UnitShape, } from "./kind.js";
|
|
20
|
+
export { genre, kind } from "./kind.js";
|
|
21
|
+
export type { Memory, Rule, Skill } from "./builtins.js";
|
|
22
|
+
export { memory, rule, skill } from "./builtins.js";
|
|
23
|
+
export type { ExpectBinding, Harness } from "./assembly.js";
|
|
24
|
+
export { harness } from "./assembly.js";
|
|
25
|
+
export type { AssemblyFactRow, ClauseRow, Declarations, KindFactRow, RequirementRow, } from "./declarations.js";
|
|
26
|
+
export { SEAM_VERSION, compileDeclarations, declarationsToJson } from "./declarations.js";
|
|
27
|
+
export type { Projection, ProjectionInput, ProjectOptions } from "./project.js";
|
|
28
|
+
export { placementLines, projectBytes, projectMember, projectionPath, renderField } from "./project.js";
|
|
29
|
+
export type { LockRow } from "./lock.js";
|
|
30
|
+
export { lockRow, sha256Hex, stampLock } from "./lock.js";
|
|
31
|
+
export type { EmitOptions, EmitResult, ResolveOptions } from "./emit.js";
|
|
32
|
+
export { emit, writeEmit } from "./emit.js";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* temper's authoring face — the six-noun model as a typed module library
|
|
3
|
+
* (`specs/intent/00-intent.md`, the SDK Decision; `specs/architecture/20-surface.md`).
|
|
4
|
+
* A harness author imports plain nouns — the built-in kinds, `harness()`, the
|
|
5
|
+
* clause and requirement constructors, `needs`, and the three prose constructors —
|
|
6
|
+
* and composes members as typed values. `emit` compiles the whole into the
|
|
7
|
+
* declaration rows the engine reads, a byte-faithful projection, and the lock;
|
|
8
|
+
* every type erases at the seam, and Turing-completeness stays quarantined at
|
|
9
|
+
* authoring time.
|
|
10
|
+
*/
|
|
11
|
+
export { blocks, file, renderText, text } from "./prose.js";
|
|
12
|
+
export { bound, decision, genreValue, law } from "./genres.js";
|
|
13
|
+
export { bash, capability, permissionUnion } from "./needs.js";
|
|
14
|
+
export { allowedChars, clause, forbiddenKeys, maxLen, maxLines, minLen, nameMatchesDir, required, requireSections, requirement, type, } from "./contract.js";
|
|
15
|
+
export { genre, kind } from "./kind.js";
|
|
16
|
+
export { memory, rule, skill } from "./builtins.js";
|
|
17
|
+
export { harness } from "./assembly.js";
|
|
18
|
+
export { SEAM_VERSION, compileDeclarations, declarationsToJson } from "./declarations.js";
|
|
19
|
+
export { placementLines, projectBytes, projectMember, projectionPath, renderField } from "./project.js";
|
|
20
|
+
export { lockRow, sha256Hex, stampLock } from "./lock.js";
|
|
21
|
+
export { emit, writeEmit } from "./emit.js";
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kinds — the engine room (`specs/architecture/15-kinds.md`, "A kind is a
|
|
3
|
+
* constructor plus five facts"). A kind is a plain typed surface — an interface
|
|
4
|
+
* `T` and a constructor `kind<T>()` — plus five facts of runtime residue: label,
|
|
5
|
+
* locus, layout, registration, and edge fields. `tsc` is the keystroke wall; every
|
|
6
|
+
* type erases at the seam, and what a kind leaves behind is those five facts,
|
|
7
|
+
* riding the lock as rows. Identity travels by import, never by string — a `kind`
|
|
8
|
+
* reference is the imported value (`15-kinds.md`, the built-ins-are-a-module
|
|
9
|
+
* Decision).
|
|
10
|
+
*/
|
|
11
|
+
import type { Prose } from "./prose.js";
|
|
12
|
+
import type { Capability } from "./needs.js";
|
|
13
|
+
import type { Requirement } from "./contract.js";
|
|
14
|
+
/** The shape of the on-disk artifact a member projects to (fact 3, layout). */
|
|
15
|
+
export type Format = "yaml-frontmatter";
|
|
16
|
+
/** Whether a member is a lone file (identity from the stem) or a directory with an entry file. */
|
|
17
|
+
export type UnitShape = "file" | "directory";
|
|
18
|
+
/**
|
|
19
|
+
* A kind's **registration** — the declared edge between a member and the world
|
|
20
|
+
* (fact 4, `15-kinds.md`, "Registration"). Reachability is graph reachability
|
|
21
|
+
* from the world node over these edges.
|
|
22
|
+
*/
|
|
23
|
+
export type Registration = {
|
|
24
|
+
readonly via: "always";
|
|
25
|
+
} | {
|
|
26
|
+
readonly via: "description-trigger";
|
|
27
|
+
readonly field: string;
|
|
28
|
+
} | {
|
|
29
|
+
readonly via: "paths-match";
|
|
30
|
+
readonly field: string;
|
|
31
|
+
} | {
|
|
32
|
+
readonly via: "event";
|
|
33
|
+
readonly field: string;
|
|
34
|
+
} | {
|
|
35
|
+
readonly via: "connection";
|
|
36
|
+
};
|
|
37
|
+
/** One of a kind's fields that is a reference to another member — a graph edge (fact 5). */
|
|
38
|
+
export interface EdgeField {
|
|
39
|
+
readonly field: string;
|
|
40
|
+
/** The target kind's name — the far end of the edge. */
|
|
41
|
+
readonly to: string;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* A kind's **locus** (fact 2): members live at path globs (`at`) or as typed
|
|
45
|
+
* fenced blocks inside host documents (`genre`). An `at` locus is split root +
|
|
46
|
+
* glob so the kind fact row carries `governs_root`/`governs_glob` directly.
|
|
47
|
+
*/
|
|
48
|
+
export type Locus = {
|
|
49
|
+
readonly kind: "at";
|
|
50
|
+
readonly root: string;
|
|
51
|
+
readonly glob: string;
|
|
52
|
+
} | {
|
|
53
|
+
readonly kind: "genre";
|
|
54
|
+
readonly withinHosts: readonly string[];
|
|
55
|
+
};
|
|
56
|
+
/** The five facts of a kind's runtime residue (`15-kinds.md`). */
|
|
57
|
+
export interface KindFacts {
|
|
58
|
+
/** Fact 1, label — the compiled debug label findings speak; the kind's name. */
|
|
59
|
+
readonly name: string;
|
|
60
|
+
/** The declared provider authority, when the kind qualifies by one. */
|
|
61
|
+
readonly provider?: string;
|
|
62
|
+
/** Fact 2, locus — where members live. */
|
|
63
|
+
readonly locus: Locus;
|
|
64
|
+
/** Fact 3a, layout — the projection format; omitted for a frontmatterless kind. */
|
|
65
|
+
readonly format?: Format;
|
|
66
|
+
/** Fact 3b, layout — the on-disk unit shape. */
|
|
67
|
+
readonly unitShape: UnitShape;
|
|
68
|
+
/** Fact 4, registration — the world edge. */
|
|
69
|
+
readonly registration: Registration;
|
|
70
|
+
/**
|
|
71
|
+
* The frontmatter key the member's name writes under (a skill's `name`), or
|
|
72
|
+
* absent when identity is the file stem (a rule). A layout detail: it shapes
|
|
73
|
+
* the projected frontmatter, never the model.
|
|
74
|
+
*/
|
|
75
|
+
readonly identityField?: string;
|
|
76
|
+
/** Fact 5, edge fields — the kind's fields that are references to other members. */
|
|
77
|
+
readonly edgeFields?: readonly EdgeField[];
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* One authored member — a typed value in the library (`20-surface.md`, "The
|
|
81
|
+
* member"). Kind identity travels by import (`facts`), never by string; the
|
|
82
|
+
* typed fields are flat at the top level, carried as an ordered pair list so the
|
|
83
|
+
* projected frontmatter key order is the author's.
|
|
84
|
+
*/
|
|
85
|
+
export interface Member {
|
|
86
|
+
/** The kind's name — its declaration-row and lock identity. */
|
|
87
|
+
readonly kind: string;
|
|
88
|
+
/** The kind's five facts — carried for projection and the declaration rows. */
|
|
89
|
+
readonly facts: KindFacts;
|
|
90
|
+
/** Identity within the kind. */
|
|
91
|
+
readonly name: string;
|
|
92
|
+
/** The member's words (`20-surface.md`, "Prose"). */
|
|
93
|
+
readonly prose?: Prose;
|
|
94
|
+
/** The kind's typed fields, flat and ordered — the projected frontmatter. */
|
|
95
|
+
readonly fields: ReadonlyArray<readonly [string, unknown]>;
|
|
96
|
+
/** String keys naming the requirements this member fills. */
|
|
97
|
+
readonly satisfies: readonly string[];
|
|
98
|
+
/** Requirements the member itself publishes, by name. */
|
|
99
|
+
readonly requires: Readonly<Record<string, Requirement>>;
|
|
100
|
+
/** The capabilities the member's behavior uses — the permission union's source. */
|
|
101
|
+
readonly needs: readonly Capability[];
|
|
102
|
+
}
|
|
103
|
+
/** The init a kind constructor takes — the framework keys plus the kind's typed fields `T`. */
|
|
104
|
+
export type MemberInit<T> = {
|
|
105
|
+
readonly name: string;
|
|
106
|
+
readonly prose?: Prose;
|
|
107
|
+
readonly satisfies?: readonly string[];
|
|
108
|
+
readonly requires?: Readonly<Record<string, Requirement>>;
|
|
109
|
+
readonly needs?: readonly Capability[];
|
|
110
|
+
} & T;
|
|
111
|
+
/**
|
|
112
|
+
* A kind — a callable constructor carrying its five facts. Calling it builds a
|
|
113
|
+
* member; `key` (its name) keys `expect` and a `kind` reference in a requirement.
|
|
114
|
+
* The value *is* the identity (`15-kinds.md`, "identity travels by import").
|
|
115
|
+
*/
|
|
116
|
+
export interface KindDefinition<T> {
|
|
117
|
+
(init: MemberInit<T>): Member;
|
|
118
|
+
readonly facts: KindFacts;
|
|
119
|
+
readonly key: string;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Define a kind (`15-kinds.md`). Returns a constructor over the kind's typed
|
|
123
|
+
* fields `T`; every type erases at the seam, so what the returned member carries
|
|
124
|
+
* into emit is the five facts plus flat field data.
|
|
125
|
+
*/
|
|
126
|
+
export declare function kind<T extends object>(facts: KindFacts): KindDefinition<T>;
|
|
127
|
+
/**
|
|
128
|
+
* Define a **genre** — a kind whose locus is `genre(within hosts)`: its members
|
|
129
|
+
* live as typed fenced blocks inside host documents instead of at their own
|
|
130
|
+
* paths (`15-kinds.md`, "A genre is a kind at the block locus"). Registration
|
|
131
|
+
* inherits through the host, so a genre carries no world edge of its own.
|
|
132
|
+
*/
|
|
133
|
+
export declare function genre<T extends object>(facts: {
|
|
134
|
+
name: string;
|
|
135
|
+
provider?: string;
|
|
136
|
+
withinHosts: readonly string[];
|
|
137
|
+
edgeFields?: readonly EdgeField[];
|
|
138
|
+
}): KindDefinition<T>;
|
package/dist/src/kind.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kinds — the engine room (`specs/architecture/15-kinds.md`, "A kind is a
|
|
3
|
+
* constructor plus five facts"). A kind is a plain typed surface — an interface
|
|
4
|
+
* `T` and a constructor `kind<T>()` — plus five facts of runtime residue: label,
|
|
5
|
+
* locus, layout, registration, and edge fields. `tsc` is the keystroke wall; every
|
|
6
|
+
* type erases at the seam, and what a kind leaves behind is those five facts,
|
|
7
|
+
* riding the lock as rows. Identity travels by import, never by string — a `kind`
|
|
8
|
+
* reference is the imported value (`15-kinds.md`, the built-ins-are-a-module
|
|
9
|
+
* Decision).
|
|
10
|
+
*/
|
|
11
|
+
/** The framework keys of a member init — everything else is a typed field (flat). */
|
|
12
|
+
const FRAMEWORK_KEYS = new Set(["name", "prose", "satisfies", "requires", "needs"]);
|
|
13
|
+
/**
|
|
14
|
+
* Build the ordered projected-frontmatter fields for a member: nothing for a
|
|
15
|
+
* frontmatterless kind (memory declares no `format`), else the identity field
|
|
16
|
+
* (when the kind writes its name into frontmatter) followed by the typed fields
|
|
17
|
+
* in the author's declared order.
|
|
18
|
+
*/
|
|
19
|
+
function orderedFields(facts, init) {
|
|
20
|
+
if (facts.format === undefined)
|
|
21
|
+
return [];
|
|
22
|
+
const typed = [];
|
|
23
|
+
for (const [key, value] of Object.entries(init)) {
|
|
24
|
+
if (!FRAMEWORK_KEYS.has(key))
|
|
25
|
+
typed.push([key, value]);
|
|
26
|
+
}
|
|
27
|
+
const head = facts.identityField !== undefined ? [[facts.identityField, init.name]] : [];
|
|
28
|
+
return [...head, ...typed];
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Define a kind (`15-kinds.md`). Returns a constructor over the kind's typed
|
|
32
|
+
* fields `T`; every type erases at the seam, so what the returned member carries
|
|
33
|
+
* into emit is the five facts plus flat field data.
|
|
34
|
+
*/
|
|
35
|
+
export function kind(facts) {
|
|
36
|
+
const construct = (init) => ({
|
|
37
|
+
kind: facts.name,
|
|
38
|
+
facts,
|
|
39
|
+
name: init.name,
|
|
40
|
+
prose: init.prose,
|
|
41
|
+
fields: orderedFields(facts, init),
|
|
42
|
+
satisfies: init.satisfies ?? [],
|
|
43
|
+
requires: init.requires ?? {},
|
|
44
|
+
needs: init.needs ?? [],
|
|
45
|
+
});
|
|
46
|
+
return Object.assign(construct, { facts, key: facts.name });
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Define a **genre** — a kind whose locus is `genre(within hosts)`: its members
|
|
50
|
+
* live as typed fenced blocks inside host documents instead of at their own
|
|
51
|
+
* paths (`15-kinds.md`, "A genre is a kind at the block locus"). Registration
|
|
52
|
+
* inherits through the host, so a genre carries no world edge of its own.
|
|
53
|
+
*/
|
|
54
|
+
export function genre(facts) {
|
|
55
|
+
return kind({
|
|
56
|
+
name: facts.name,
|
|
57
|
+
provider: facts.provider,
|
|
58
|
+
locus: { kind: "genre", withinHosts: facts.withinHosts },
|
|
59
|
+
unitShape: "file",
|
|
60
|
+
registration: { via: "always" },
|
|
61
|
+
edgeFields: facts.edgeFields,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The lock — tool-written provenance, emit fingerprints, and the program's
|
|
3
|
+
* declaration rows (`specs/architecture/20-surface.md`, "The lock and drift").
|
|
4
|
+
* Two row families: a per-member `[[<kind>]]` rollup (`name`, `source_path`,
|
|
5
|
+
* `source_hash`, `emit_hash`) and the `[declaration]` table's four sub-families
|
|
6
|
+
* (`[[declaration.kind]]`, `[[declaration.clause]]`, `[[declaration.requirement]]`,
|
|
7
|
+
* `[[declaration.assembly]]`). Both are byte-identical to the Rust lock
|
|
8
|
+
* (`src/import.rs` `write_rollup`, `src/drift.rs` `Declarations::write_into`) — the
|
|
9
|
+
* byte-parity lockstep two writers keep until single-writer lands.
|
|
10
|
+
*
|
|
11
|
+
* Fingerprints are SHA-256 hex over raw UTF-8 (`hash::sha256_hex`), so an
|
|
12
|
+
* SDK-emitted lock and a Rust-emitted lock agree for the same harness.
|
|
13
|
+
*/
|
|
14
|
+
import type { Projection } from "./project.js";
|
|
15
|
+
import type { Declarations } from "./declarations.js";
|
|
16
|
+
/** Lowercase hex SHA-256 of `text`'s UTF-8 bytes — the Rust `sha256_hex` port. */
|
|
17
|
+
export declare function sha256Hex(text: string): string;
|
|
18
|
+
/** One rollup row: a member's identity and the two freshness fingerprints. */
|
|
19
|
+
export interface LockRow {
|
|
20
|
+
/** The bare kind name — the `[[<kind>]]` array key (`rule`, `skill`, `memory`). */
|
|
21
|
+
readonly kind: string;
|
|
22
|
+
/** The member id — its `[[<kind>]]` `name` column. */
|
|
23
|
+
readonly name: string;
|
|
24
|
+
/** The projection's harness path — the source-of-record the fingerprints anchor. */
|
|
25
|
+
readonly sourcePath: string;
|
|
26
|
+
/** SHA-256 of the authored source bytes (the projection, for a module-carried member). */
|
|
27
|
+
readonly sourceHash: string;
|
|
28
|
+
/** SHA-256 of the last emitted projection — the `config.stale` baseline. */
|
|
29
|
+
readonly emitHash: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The rollup row a projection stamps: both fingerprints are `sha256(projection
|
|
33
|
+
* bytes)`, the fresh-emit baseline (`source_hash == emit_hash`) a Rust import then
|
|
34
|
+
* emit lands on for a byte-identical projection.
|
|
35
|
+
*/
|
|
36
|
+
export declare function lockRow(kind: string, projection: Projection): LockRow;
|
|
37
|
+
/**
|
|
38
|
+
* Serialize the lock — the rollup rows then the declaration families, joined the
|
|
39
|
+
* `toml_edit` way (exactly one blank line before every table header but the
|
|
40
|
+
* document's first). An all-empty declaration set contributes no sections, so a
|
|
41
|
+
* memberless lock is empty and a rollup-only lock carries no `[declaration]` rows.
|
|
42
|
+
*/
|
|
43
|
+
export declare function stampLock(rows: readonly LockRow[], declarations?: Declarations): string;
|