@cleocode/contracts 2026.5.92 → 2026.5.93

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.
@@ -0,0 +1,286 @@
1
+ /**
2
+ * Canonical Doc-Kind Taxonomy Registry (T9788).
3
+ *
4
+ * Single source of truth for the user-facing document-kind taxonomy consumed
5
+ * by `cleo docs` (add / list / publish-pr / list-types / schema). Prior to
6
+ * this module the taxonomy was duplicated across three files:
7
+ *
8
+ * - `packages/contracts/src/operations/docs.ts` — `DOCS_TYPE_VALUES` (6 kinds)
9
+ * - `packages/core/src/docs/publish-pr.ts` — `KNOWN_DOC_TYPES` (6 kinds)
10
+ * - `packages/cleo/src/dispatch/domains/docs.ts` — local mirror (6 kinds)
11
+ *
12
+ * The three copies drifted independently. T9788 consolidates them behind
13
+ * {@link DocKindRegistry} so a new kind requires editing exactly one place
14
+ * (or one config file for project-level extensions).
15
+ *
16
+ * IMPORTANT — relationship to {@link import('./docs-accessor.js').DocKind}:
17
+ * `DocKind` in `docs-accessor.ts` is the STORAGE-LAYER discriminator (which
18
+ * backing store holds the bytes — llmtxt.db vs manifest.db). The taxonomy
19
+ * here is the USER-FACING DOCUMENT CLASSIFICATION (what kind of document
20
+ * the human / agent authored). The two are intentionally distinct:
21
+ *
22
+ * - `docs-accessor.DocKind` answers "where is it stored?"
23
+ * - `docs-taxonomy.DocKindMetadata.kind` answers "what is it about?"
24
+ *
25
+ * Most user-facing docs are stored under `docs-accessor.DocKind = 'adr'` or
26
+ * `'agent-output'`; their taxonomy `kind` (this file) carries the semantic
27
+ * classification.
28
+ *
29
+ * Backward compatibility (T9788 AC8):
30
+ * - Every prior `DOCS_TYPE_VALUES` value remains in {@link BUILTIN_DOC_KINDS}.
31
+ * - Tests using literal strings `'spec'`, `'adr'`, etc. continue to pass.
32
+ * - The stored `type` column shape is unchanged (still a string).
33
+ *
34
+ * @epic T9787 (E-DOCS-TAXONOMY-V2)
35
+ * @task T9788
36
+ * @see ADR-073 §1 — Task Hierarchy Charter (sibling registry pattern)
37
+ */
38
+ /**
39
+ * Metadata for a single document kind in the canonical registry.
40
+ *
41
+ * Built-in kinds are declared in {@link BUILTIN_DOC_KINDS}; project-level
42
+ * extensions are loaded from `.cleo/docs-config.json` via
43
+ * {@link DocKindRegistry.load}.
44
+ *
45
+ * @see {@link DocKindRegistry} — runtime accessor and validator
46
+ */
47
+ export interface DocKindMetadata {
48
+ /** Canonical kind id, lowercase kebab-case. */
49
+ readonly kind: string;
50
+ /** Human label for display in CLI output and UIs. */
51
+ readonly label: string;
52
+ /** One-line description shown by `cleo docs list-types`. */
53
+ readonly description: string;
54
+ /**
55
+ * Default owner kind prefix when no explicit owner is provided.
56
+ *
57
+ * - `task` — `T###`
58
+ * - `session` — `ses_*`
59
+ * - `observation` — `O-*`
60
+ * - `project` — repo-root project doc (no entity owner)
61
+ */
62
+ readonly defaultOwnerKind: 'task' | 'session' | 'observation' | 'project';
63
+ /**
64
+ * Publish-dir under the repo root for `cleo docs publish-pr`.
65
+ *
66
+ * Examples: `docs/adr`, `docs/spec`, `.changeset`, `docs/release`.
67
+ */
68
+ readonly publishDir: string;
69
+ /**
70
+ * When `true`, every doc of this kind MUST carry a slug matching
71
+ * {@link entityIdPattern}.
72
+ *
73
+ * Used by `cleo docs add --type X --slug Y` to enforce naming
74
+ * conventions for kinds whose downstream consumers parse the slug
75
+ * (e.g. ADRs expect `adr-NNN-<rest>`).
76
+ */
77
+ readonly requiresEntityId: boolean;
78
+ /**
79
+ * Regex slug pattern. Validated when {@link requiresEntityId} is `true`.
80
+ *
81
+ * Stored as a {@link RegExp} so the validator can run without a
82
+ * compile step. Extensions loaded from `.cleo/docs-config.json` parse
83
+ * their string pattern into a `RegExp` at load time.
84
+ */
85
+ readonly entityIdPattern?: RegExp;
86
+ /**
87
+ * Marks an entry that was loaded from `.cleo/docs-config.json` rather
88
+ * than the built-in {@link BUILTIN_DOC_KINDS} array.
89
+ *
90
+ * Built-in kinds leave this unset; extensions set it to `true`.
91
+ */
92
+ readonly isExtension?: boolean;
93
+ }
94
+ /**
95
+ * Canonical list of built-in document kinds.
96
+ *
97
+ * Adding a kind requires:
98
+ * 1. Append an entry here (preserving existing kinds for back-compat).
99
+ * 2. Run the contracts build — every consumer picks the new kind up.
100
+ *
101
+ * Order is preserved by {@link DocKindRegistry.list}. Built-in kinds
102
+ * always sort before extensions.
103
+ *
104
+ * @see {@link DocKindMetadata} — entry shape
105
+ */
106
+ export declare const BUILTIN_DOC_KINDS: ReadonlyArray<DocKindMetadata>;
107
+ /**
108
+ * Tuple of every built-in kind id (lowercase kebab-case).
109
+ *
110
+ * Useful for `satisfies` checks and union derivation in downstream
111
+ * contracts (e.g. `operations/docs.ts`). Kept frozen.
112
+ */
113
+ export declare const BUILTIN_DOC_KIND_VALUES: ReadonlyArray<string>;
114
+ /**
115
+ * Union of every built-in kind id.
116
+ *
117
+ * Extensions loaded at runtime widen the runtime registry but NOT this
118
+ * compile-time type — that is intentional: extensions are opt-in and
119
+ * code that only handles the built-in surface stays type-safe.
120
+ */
121
+ export type BuiltinDocKind = (typeof BUILTIN_DOC_KINDS)[number]['kind'];
122
+ /**
123
+ * Wire shape of an extension entry in `.cleo/docs-config.json`.
124
+ *
125
+ * `entityIdPattern` is a string here (not a {@link RegExp}) because JSON
126
+ * cannot carry compiled regexes. {@link DocKindRegistry.load} compiles it
127
+ * to a `RegExp` while validating the config.
128
+ */
129
+ export interface DocKindExtensionConfig {
130
+ /** Canonical kind id, lowercase kebab-case. */
131
+ readonly kind: string;
132
+ /** Human label for display. */
133
+ readonly label: string;
134
+ /** One-line description for `cleo docs list-types`. */
135
+ readonly description: string;
136
+ /** Default owner kind prefix. */
137
+ readonly defaultOwnerKind: 'task' | 'session' | 'observation' | 'project';
138
+ /** Publish-dir under repo root. */
139
+ readonly publishDir: string;
140
+ /** When `true`, slug MUST match {@link entityIdPattern}. */
141
+ readonly requiresEntityId: boolean;
142
+ /**
143
+ * Regex source string (no flags). Compiled to a `RegExp` at load time.
144
+ *
145
+ * Validated against {@link DocKindRegistry.SAFE_REGEX_LENGTH_LIMIT} so
146
+ * a malformed config can't trigger pathological backtracking.
147
+ */
148
+ readonly entityIdPattern?: string;
149
+ }
150
+ /**
151
+ * Top-level shape of `.cleo/docs-config.json`.
152
+ *
153
+ * Future fields stay backward compatible by being optional.
154
+ */
155
+ export interface DocKindConfigFile {
156
+ /** Project-level extension registry. */
157
+ readonly extensions?: ReadonlyArray<DocKindExtensionConfig>;
158
+ }
159
+ /**
160
+ * Result of {@link DocKindRegistry.validateSlug}.
161
+ *
162
+ * - `ok: true` — slug is valid for the given kind.
163
+ * - `ok: false` — slug fails validation; `error` carries a human-readable
164
+ * reason and `example` shows a passing slug.
165
+ */
166
+ export type SlugValidationResult = {
167
+ readonly ok: true;
168
+ } | {
169
+ readonly ok: false;
170
+ readonly error: string;
171
+ readonly example?: string;
172
+ };
173
+ /**
174
+ * Runtime accessor for the canonical doc-kind registry.
175
+ *
176
+ * Combines {@link BUILTIN_DOC_KINDS} with project-level extensions loaded
177
+ * from `.cleo/docs-config.json`. Built-in entries always win on collision
178
+ * — extensions cannot override a built-in kind.
179
+ *
180
+ * Usage:
181
+ * ```ts
182
+ * const registry = DocKindRegistry.load(projectRoot);
183
+ * const adr = registry.get('adr');
184
+ * const check = registry.validateSlug('adr', 'adr-001-intro');
185
+ * ```
186
+ *
187
+ * @task T9788
188
+ */
189
+ export declare class DocKindRegistry {
190
+ /**
191
+ * Maximum allowed length of an `entityIdPattern` source string.
192
+ *
193
+ * Caps the input surface so a malformed extension config cannot trigger
194
+ * pathological regex backtracking. 256 chars is far more than any
195
+ * realistic slug pattern (typical: 30–60 chars).
196
+ */
197
+ static readonly SAFE_REGEX_LENGTH_LIMIT = 256;
198
+ private readonly byKind;
199
+ private readonly orderedEntries;
200
+ /**
201
+ * Construct a registry from an explicit array of entries.
202
+ *
203
+ * Most callers should use {@link DocKindRegistry.load} instead; this
204
+ * constructor is exposed for tests that want to bypass filesystem I/O.
205
+ *
206
+ * @param entries - Pre-validated doc-kind metadata (built-in + extensions).
207
+ */
208
+ constructor(entries: ReadonlyArray<DocKindMetadata>);
209
+ /**
210
+ * Load the canonical registry, merging built-ins with extensions from
211
+ * `<projectRoot>/.cleo/docs-config.json`.
212
+ *
213
+ * Missing or unreadable config file → returns the built-in-only registry.
214
+ * Malformed config (bad JSON, invalid entry, regex too long, etc.) →
215
+ * throws {@link DocKindConfigError} so the caller can surface a clear
216
+ * envelope rather than silently dropping extensions.
217
+ *
218
+ * @param projectRoot - Absolute path to the repo root.
219
+ * @throws DocKindConfigError when the config exists but is invalid.
220
+ */
221
+ static load(projectRoot: string): DocKindRegistry;
222
+ /**
223
+ * Build a registry from already-parsed config — bypasses filesystem I/O.
224
+ *
225
+ * Used by tests and HTTP-dispatch callers that hand-construct a config
226
+ * object instead of reading from disk.
227
+ *
228
+ * @param config - Parsed config object (or `undefined` for built-ins only).
229
+ * @param sourceLabel - Optional label used in error messages.
230
+ * @throws DocKindConfigError when the config object is invalid.
231
+ */
232
+ static fromConfig(config: DocKindConfigFile | undefined, sourceLabel?: string): DocKindRegistry;
233
+ /**
234
+ * Default registry — built-in kinds only, no extensions.
235
+ *
236
+ * Suitable for code paths that never need project-level extensions
237
+ * (e.g. unit tests, library-mode consumers).
238
+ */
239
+ static builtinOnly(): DocKindRegistry;
240
+ /** True when `kind` is registered (built-in OR extension). */
241
+ has(kind: string): boolean;
242
+ /** Look up metadata for a registered kind. Returns `undefined` on miss. */
243
+ get(kind: string): DocKindMetadata | undefined;
244
+ /**
245
+ * List every registered kind, built-ins first then extensions in
246
+ * declaration order.
247
+ *
248
+ * Used by `cleo docs schema` and `cleo docs list-types`.
249
+ */
250
+ list(): ReadonlyArray<DocKindMetadata>;
251
+ /**
252
+ * Validate a slug against the registered pattern for `kind`.
253
+ *
254
+ * Behaviour:
255
+ * - Unknown kind → `{ ok: false, error: "unknown kind '<kind>'" }`.
256
+ * - Known kind with `requiresEntityId === false` → always `{ ok: true }`.
257
+ * - Known kind with `requiresEntityId === true` and no pattern → defensive
258
+ * `{ ok: false }` since the registry entry is internally inconsistent.
259
+ * - Known kind with pattern → tests `slug` against the pattern.
260
+ *
261
+ * @param kind - Registered kind id.
262
+ * @param slug - Slug to validate.
263
+ * @returns Pass/fail result with a human-readable error on failure.
264
+ */
265
+ validateSlug(kind: string, slug: string): SlugValidationResult;
266
+ /**
267
+ * Map a kind to its `publishDir` (e.g. `'adr'` → `'docs/adr'`).
268
+ *
269
+ * Returns `undefined` for unknown kinds — callers decide whether to
270
+ * fall back to a default (e.g. `'docs/note'`) or surface an error.
271
+ */
272
+ publishDirFor(kind: string): string | undefined;
273
+ }
274
+ /**
275
+ * Error thrown by {@link DocKindRegistry.load} and
276
+ * {@link DocKindRegistry.fromConfig} when the supplied config is invalid.
277
+ *
278
+ * Carries the offending source path / label so the CLI surface can render
279
+ * a `details` payload pointing the user at the right file.
280
+ */
281
+ export declare class DocKindConfigError extends Error {
282
+ /** Source identifier — file path on disk, or `<inline-config>` for tests. */
283
+ readonly source: string;
284
+ constructor(message: string, source: string);
285
+ }
286
+ //# sourceMappingURL=docs-taxonomy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"docs-taxonomy.d.ts","sourceRoot":"","sources":["../src/docs-taxonomy.ts"],"names":[],"mappings":"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"}