@cleocode/contracts 2026.5.112 → 2026.5.113

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.
Files changed (98) hide show
  1. package/dist/__tests__/cli-meta-suggested-next.test.d.ts +21 -0
  2. package/dist/__tests__/cli-meta-suggested-next.test.d.ts.map +1 -0
  3. package/dist/__tests__/cli-meta-suggested-next.test.js +70 -0
  4. package/dist/__tests__/cli-meta-suggested-next.test.js.map +1 -0
  5. package/dist/config/__tests__/manifest.test.d.ts +13 -0
  6. package/dist/config/__tests__/manifest.test.d.ts.map +1 -0
  7. package/dist/config/__tests__/manifest.test.js +143 -0
  8. package/dist/config/__tests__/manifest.test.js.map +1 -0
  9. package/dist/config/manifest.d.ts +158 -0
  10. package/dist/config/manifest.d.ts.map +1 -0
  11. package/dist/config/manifest.js +123 -0
  12. package/dist/config/manifest.js.map +1 -0
  13. package/dist/docs/__tests__/provenance.test.d.ts +14 -0
  14. package/dist/docs/__tests__/provenance.test.d.ts.map +1 -0
  15. package/dist/docs/__tests__/provenance.test.js +579 -0
  16. package/dist/docs/__tests__/provenance.test.js.map +1 -0
  17. package/dist/docs/provenance.d.ts +731 -0
  18. package/dist/docs/provenance.d.ts.map +1 -0
  19. package/dist/docs/provenance.js +258 -0
  20. package/dist/docs/provenance.js.map +1 -0
  21. package/dist/docs-taxonomy.d.ts +24 -0
  22. package/dist/docs-taxonomy.d.ts.map +1 -1
  23. package/dist/docs-taxonomy.js +27 -0
  24. package/dist/docs-taxonomy.js.map +1 -1
  25. package/dist/doctor.d.ts +101 -0
  26. package/dist/doctor.d.ts.map +1 -1
  27. package/dist/errors.d.ts +4 -0
  28. package/dist/errors.d.ts.map +1 -1
  29. package/dist/errors.js +4 -0
  30. package/dist/errors.js.map +1 -1
  31. package/dist/exit-codes.d.ts +37 -0
  32. package/dist/exit-codes.d.ts.map +1 -1
  33. package/dist/exit-codes.js +37 -0
  34. package/dist/exit-codes.js.map +1 -1
  35. package/dist/index.d.ts +10 -3
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +7 -2
  38. package/dist/index.js.map +1 -1
  39. package/dist/invariants/__tests__/adr-056-release.test.d.ts +16 -0
  40. package/dist/invariants/__tests__/adr-056-release.test.d.ts.map +1 -0
  41. package/dist/invariants/__tests__/adr-056-release.test.js +88 -0
  42. package/dist/invariants/__tests__/adr-056-release.test.js.map +1 -0
  43. package/dist/invariants/__tests__/adr-070-orchestration.test.d.ts +22 -0
  44. package/dist/invariants/__tests__/adr-070-orchestration.test.d.ts.map +1 -0
  45. package/dist/invariants/__tests__/adr-070-orchestration.test.js +151 -0
  46. package/dist/invariants/__tests__/adr-070-orchestration.test.js.map +1 -0
  47. package/dist/invariants/adr-056-release.d.ts +42 -0
  48. package/dist/invariants/adr-056-release.d.ts.map +1 -0
  49. package/dist/invariants/adr-056-release.js +143 -0
  50. package/dist/invariants/adr-056-release.js.map +1 -0
  51. package/dist/invariants/adr-070-orchestration.d.ts +73 -0
  52. package/dist/invariants/adr-070-orchestration.d.ts.map +1 -0
  53. package/dist/invariants/adr-070-orchestration.js +290 -0
  54. package/dist/invariants/adr-070-orchestration.js.map +1 -0
  55. package/dist/invariants/index.d.ts +4 -1
  56. package/dist/invariants/index.d.ts.map +1 -1
  57. package/dist/invariants/index.js +11 -2
  58. package/dist/invariants/index.js.map +1 -1
  59. package/dist/operations/__tests__/input-contract.test.d.ts +20 -0
  60. package/dist/operations/__tests__/input-contract.test.d.ts.map +1 -0
  61. package/dist/operations/__tests__/input-contract.test.js +158 -0
  62. package/dist/operations/__tests__/input-contract.test.js.map +1 -0
  63. package/dist/operations/docs.d.ts +16 -0
  64. package/dist/operations/docs.d.ts.map +1 -1
  65. package/dist/operations/input-contract.d.ts +267 -0
  66. package/dist/operations/input-contract.d.ts.map +1 -0
  67. package/dist/operations/input-contract.js +29 -0
  68. package/dist/operations/input-contract.js.map +1 -0
  69. package/dist/templates/__tests__/manifest.test.d.ts +18 -0
  70. package/dist/templates/__tests__/manifest.test.d.ts.map +1 -0
  71. package/dist/templates/__tests__/manifest.test.js +123 -0
  72. package/dist/templates/__tests__/manifest.test.js.map +1 -0
  73. package/dist/templates/manifest.d.ts +186 -0
  74. package/dist/templates/manifest.d.ts.map +1 -0
  75. package/dist/templates/manifest.js +165 -0
  76. package/dist/templates/manifest.js.map +1 -0
  77. package/package.json +2 -2
  78. package/src/__tests__/cli-meta-suggested-next.test.ts +76 -0
  79. package/src/config/__tests__/manifest.test.ts +176 -0
  80. package/src/config/manifest.ts +218 -0
  81. package/src/docs/__tests__/__snapshots__/provenance.test.ts.snap +252 -0
  82. package/src/docs/__tests__/provenance.test.ts +648 -0
  83. package/src/docs/provenance.ts +601 -0
  84. package/src/docs-taxonomy.ts +55 -0
  85. package/src/doctor.ts +111 -0
  86. package/src/errors.ts +4 -0
  87. package/src/exit-codes.ts +39 -0
  88. package/src/index.ts +90 -0
  89. package/src/invariants/__tests__/adr-056-release.test.ts +102 -0
  90. package/src/invariants/__tests__/adr-070-orchestration.test.ts +186 -0
  91. package/src/invariants/adr-056-release.ts +154 -0
  92. package/src/invariants/adr-070-orchestration.ts +317 -0
  93. package/src/invariants/index.ts +11 -2
  94. package/src/operations/__tests__/input-contract.test.ts +188 -0
  95. package/src/operations/docs.ts +16 -0
  96. package/src/operations/input-contract.ts +301 -0
  97. package/src/templates/__tests__/manifest.test.ts +140 -0
  98. package/src/templates/manifest.ts +208 -0
@@ -0,0 +1,267 @@
1
+ /**
2
+ * OperationInputContract — schema-first input contract for CLEO operations.
3
+ *
4
+ * This module is the SINGLE SOURCE OF TRUTH for the schema-first input
5
+ * validation surface that powers the `mutate(operation, input)` DX
6
+ * (Saga T9855 / E7). Every CLEO operation that accepts structured input
7
+ * MUST be describable as an {@link OperationInputContract}: a JSON Schema
8
+ * draft-07 document, a stable operation identifier, and at least one
9
+ * worked example payload.
10
+ *
11
+ * Validators consume an {@link OperationInputContract} and produce a
12
+ * {@link ValidationResult} discriminated union — either the typed value
13
+ * (`ok: true`) or an array of {@link ValidationError} entries with
14
+ * actionable, JSON-Pointer-anchored remediation hints (`ok: false`).
15
+ *
16
+ * ZERO runtime dependencies: `contracts` is a leaf package. JSON Schema
17
+ * documents are modeled as the loose alias {@link JsonSchema} so this
18
+ * module does not pull in a JSON Schema typings package.
19
+ *
20
+ * @packageDocumentation
21
+ * @module @cleocode/contracts/operations/input-contract
22
+ *
23
+ * @see {@link https://json-schema.org/draft-07/schema}
24
+ *
25
+ * @epic T9855
26
+ * @task T9914
27
+ */
28
+ /**
29
+ * Loose alias for a JSON Schema draft-07 document.
30
+ *
31
+ * Intentionally typed as `Record<string, unknown>` to keep the contracts
32
+ * package free of any JSON Schema typings runtime/type dependency. The
33
+ * actual draft-07 shape is enforced by the validator implementation
34
+ * (T9915) at the point of use, not by the type system here.
35
+ *
36
+ * @example
37
+ * ```ts
38
+ * const titleSchema: JsonSchema = {
39
+ * type: 'object',
40
+ * required: ['title'],
41
+ * properties: {
42
+ * title: { type: 'string', minLength: 1 },
43
+ * },
44
+ * };
45
+ * ```
46
+ */
47
+ export type JsonSchema = Record<string, unknown>;
48
+ /**
49
+ * A single worked example payload for an {@link OperationInputContract}.
50
+ *
51
+ * Examples are MANDATORY (the contract requires at least one) — they
52
+ * double as documentation for human readers AND as conformance fixtures
53
+ * for the validator self-test that ships with every CLEO release.
54
+ *
55
+ * @typeParam T - The validated, typed input shape produced by the contract.
56
+ *
57
+ * @example
58
+ * ```ts
59
+ * const example: OperationInputExample<{ title: string }> = {
60
+ * name: 'minimal',
61
+ * value: { title: 'Ship saga T9855' },
62
+ * description: 'Smallest valid input — title is the only required field.',
63
+ * };
64
+ * ```
65
+ */
66
+ export interface OperationInputExample<T> {
67
+ /**
68
+ * Short, human-readable identifier for the example. Conventionally
69
+ * kebab-case (`'minimal'`, `'with-all-fields'`, `'edge-empty-array'`).
70
+ */
71
+ name: string;
72
+ /**
73
+ * A concrete value that validates successfully against the contract's
74
+ * {@link OperationInputContract.schema | schema}. Strongly typed via
75
+ * the `T` generic so example tables stay in sync with the operation
76
+ * payload shape at compile time.
77
+ */
78
+ value: T;
79
+ /**
80
+ * Optional prose explaining why this example matters — what edge case
81
+ * it covers, what default it exercises, etc. Surfaced in generated docs.
82
+ */
83
+ description?: string;
84
+ }
85
+ /**
86
+ * Schema-first input contract for a single CLEO operation.
87
+ *
88
+ * Pairs a stable operation identifier with the JSON Schema draft-07
89
+ * document that describes the operation's accepted input shape, plus a
90
+ * non-empty (by convention) catalogue of worked examples. The validator
91
+ * implementation (T9915) ingests an {@link OperationInputContract} and a
92
+ * raw payload, returning a {@link ValidationResult}.
93
+ *
94
+ * Together with the {@link OperationInputContractRegistry}, this type is
95
+ * the foundation of the schema-first `mutate(operation, input)` DX
96
+ * surface (Saga T9855 / E7).
97
+ *
98
+ * @typeParam T - The validated, typed input shape produced when the
99
+ * raw payload passes validation. The generic propagates through
100
+ * {@link ValidationResult} so callers receive a strongly-typed `value`
101
+ * in the success branch.
102
+ *
103
+ * @example
104
+ * ```ts
105
+ * interface CreateTaskInput {
106
+ * title: string;
107
+ * acceptance: string;
108
+ * }
109
+ *
110
+ * const createTaskContract: OperationInputContract<CreateTaskInput> = {
111
+ * operation: 'tasks.create',
112
+ * schema: {
113
+ * type: 'object',
114
+ * required: ['title', 'acceptance'],
115
+ * properties: {
116
+ * title: { type: 'string', minLength: 1, maxLength: 200 },
117
+ * acceptance: { type: 'string', minLength: 1 },
118
+ * },
119
+ * additionalProperties: false,
120
+ * },
121
+ * examples: [
122
+ * {
123
+ * name: 'minimal',
124
+ * value: { title: 'Ship E7', acceptance: 'PR merged' },
125
+ * },
126
+ * ],
127
+ * };
128
+ * ```
129
+ */
130
+ export interface OperationInputContract<T> {
131
+ /**
132
+ * Stable, fully-qualified operation identifier in
133
+ * `<domain>.<verb>` form (e.g. `'tasks.create'`, `'docs.publish'`).
134
+ * Used as the registry key in {@link OperationInputContractRegistry}.
135
+ */
136
+ operation: string;
137
+ /**
138
+ * JSON Schema draft-07 document describing the accepted input shape.
139
+ * The validator MUST treat absent properties as not-present (it does
140
+ * NOT inject `default` values) so callers can distinguish "user
141
+ * omitted" from "user supplied default".
142
+ */
143
+ schema: JsonSchema;
144
+ /**
145
+ * Catalogue of worked example payloads. Conventionally non-empty so
146
+ * generated docs always have at least one runnable sample. Each
147
+ * example's {@link OperationInputExample.value | value} is statically
148
+ * typed to the contract's `T` parameter.
149
+ */
150
+ examples: ReadonlyArray<OperationInputExample<T>>;
151
+ }
152
+ /**
153
+ * A single validation failure produced by the input validator.
154
+ *
155
+ * Every field is REQUIRED — there are no optional remediation hints.
156
+ * This shape is the wire format the `mutate` DX surface returns to
157
+ * callers, so it MUST be stable, machine-parseable, AND human-readable.
158
+ *
159
+ * @example
160
+ * ```ts
161
+ * const err: ValidationError = {
162
+ * path: '/title',
163
+ * expected: 'string with minLength 1',
164
+ * received: 'empty string',
165
+ * fix: 'Provide a non-empty title (1-200 chars).',
166
+ * errorCode: 'E_INPUT_MIN_LENGTH',
167
+ * schemaPath: '#/properties/title/minLength',
168
+ * };
169
+ * ```
170
+ */
171
+ export interface ValidationError {
172
+ /**
173
+ * RFC 6901 JSON Pointer to the failing input location, anchored at
174
+ * the root of the payload (e.g. `'/title'`, `'/owners/0/email'`).
175
+ * An empty string `''` denotes the root document itself.
176
+ */
177
+ path: string;
178
+ /**
179
+ * Human-readable description of what the validator expected at
180
+ * {@link ValidationError.path | path} (e.g. `'string'`,
181
+ * `'integer >= 0'`, `'one of: pending | active | done'`).
182
+ */
183
+ expected: string;
184
+ /**
185
+ * Human-readable description of what was actually received at
186
+ * {@link ValidationError.path | path}. NEVER includes the raw value
187
+ * itself — describes type/cardinality only — so logs of errors do
188
+ * not accidentally leak user payloads.
189
+ */
190
+ received: string;
191
+ /**
192
+ * Actionable, imperative remediation hint targeted at the caller
193
+ * (e.g. `'Provide a non-empty title (1-200 chars).'`). Surfaced
194
+ * directly in CLI output and validator return envelopes.
195
+ */
196
+ fix: string;
197
+ /**
198
+ * Stable, screaming-snake-case error code (e.g.
199
+ * `'E_INPUT_MIN_LENGTH'`, `'E_INPUT_REQUIRED'`). Stable across
200
+ * releases so callers can branch on it programmatically.
201
+ */
202
+ errorCode: string;
203
+ /**
204
+ * RFC 6901 JSON Pointer into the contract's
205
+ * {@link OperationInputContract.schema | schema} that produced the
206
+ * failure (e.g. `'#/properties/title/minLength'`). Lets tooling jump
207
+ * straight to the offending schema clause.
208
+ */
209
+ schemaPath: string;
210
+ }
211
+ /**
212
+ * Discriminated union returned by the input validator.
213
+ *
214
+ * Narrows on the `ok` boolean: when `ok === true`, the strongly-typed
215
+ * `value` is present; when `ok === false`, a non-empty `errors` array
216
+ * lists every failure (validators are encouraged to surface ALL errors
217
+ * in one pass, not bail on the first).
218
+ *
219
+ * @typeParam T - The validated, typed input shape produced on success.
220
+ *
221
+ * @example
222
+ * ```ts
223
+ * function handle(result: ValidationResult<{ title: string }>) {
224
+ * if (result.ok) {
225
+ * // result.value is { title: string }
226
+ * return createTask(result.value);
227
+ * }
228
+ * // result.errors is ValidationError[]
229
+ * for (const e of result.errors) {
230
+ * console.error(`${e.path}: ${e.fix}`);
231
+ * }
232
+ * }
233
+ * ```
234
+ */
235
+ export type ValidationResult<T> = {
236
+ readonly ok: true;
237
+ readonly value: T;
238
+ } | {
239
+ readonly ok: false;
240
+ readonly errors: ValidationError[];
241
+ };
242
+ /**
243
+ * Registry of every {@link OperationInputContract} known to the CLEO
244
+ * runtime, keyed by the contract's `operation` identifier (e.g.
245
+ * `'tasks.create'`).
246
+ *
247
+ * The value type uses `unknown` for the contract's generic so registry
248
+ * lookups stay assignable across operations with different input
249
+ * shapes. Callers that need the concrete `T` MUST narrow via an
250
+ * operation-specific accessor — direct `registry[op]` reads are
251
+ * intentionally not type-narrowed by operation name.
252
+ *
253
+ * @example
254
+ * ```ts
255
+ * const registry: OperationInputContractRegistry = {
256
+ * 'tasks.create': createTaskContract,
257
+ * 'docs.publish': publishDocsContract,
258
+ * };
259
+ *
260
+ * const contract = registry['tasks.create'];
261
+ * if (contract) {
262
+ * // validate(contract, payload) → ValidationResult<unknown>
263
+ * }
264
+ * ```
265
+ */
266
+ export type OperationInputContractRegistry = Record<string, OperationInputContract<unknown>>;
267
+ //# sourceMappingURL=input-contract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input-contract.d.ts","sourceRoot":"","sources":["../../src/operations/input-contract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAMH;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAMjD;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,qBAAqB,CAAC,CAAC;IACtC;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;;;;OAKG;IACH,KAAK,EAAE,CAAC,CAAC;IAET;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,MAAM,WAAW,sBAAsB,CAAC,CAAC;IACvC;;;;OAIG;IACH,SAAS,EAAE,MAAM,CAAC;IAElB;;;;;OAKG;IACH,MAAM,EAAE,UAAU,CAAC;IAEnB;;;;;OAKG;IACH,QAAQ,EAAE,aAAa,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC,CAAC;CACnD;AAMD;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;;;OAIG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB;;;;;OAKG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB;;;;OAIG;IACH,GAAG,EAAE,MAAM,CAAC;IAEZ;;;;OAIG;IACH,SAAS,EAAE,MAAM,CAAC;IAElB;;;;;OAKG;IACH,UAAU,EAAE,MAAM,CAAC;CACpB;AAMD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,MAAM,gBAAgB,CAAC,CAAC,IAC1B;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAA;CAAE,GACxC;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,eAAe,EAAE,CAAA;CAAE,CAAC;AAM/D;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,MAAM,8BAA8B,GAAG,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,OAAO,CAAC,CAAC,CAAC"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * OperationInputContract — schema-first input contract for CLEO operations.
3
+ *
4
+ * This module is the SINGLE SOURCE OF TRUTH for the schema-first input
5
+ * validation surface that powers the `mutate(operation, input)` DX
6
+ * (Saga T9855 / E7). Every CLEO operation that accepts structured input
7
+ * MUST be describable as an {@link OperationInputContract}: a JSON Schema
8
+ * draft-07 document, a stable operation identifier, and at least one
9
+ * worked example payload.
10
+ *
11
+ * Validators consume an {@link OperationInputContract} and produce a
12
+ * {@link ValidationResult} discriminated union — either the typed value
13
+ * (`ok: true`) or an array of {@link ValidationError} entries with
14
+ * actionable, JSON-Pointer-anchored remediation hints (`ok: false`).
15
+ *
16
+ * ZERO runtime dependencies: `contracts` is a leaf package. JSON Schema
17
+ * documents are modeled as the loose alias {@link JsonSchema} so this
18
+ * module does not pull in a JSON Schema typings package.
19
+ *
20
+ * @packageDocumentation
21
+ * @module @cleocode/contracts/operations/input-contract
22
+ *
23
+ * @see {@link https://json-schema.org/draft-07/schema}
24
+ *
25
+ * @epic T9855
26
+ * @task T9914
27
+ */
28
+ export {};
29
+ //# sourceMappingURL=input-contract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input-contract.js","sourceRoot":"","sources":["../../src/operations/input-contract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Contract tests for the TemplateManifest schema.
3
+ *
4
+ * Asserts:
5
+ *
6
+ * 1. A valid entry for every {@link TemplateKind} parses successfully.
7
+ * 2. An entry with an unknown `kind` is rejected.
8
+ * 3. An entry missing `installPath` is rejected.
9
+ * 4. The {@link PlaceholderSpec} `source` field is exhaustively typed —
10
+ * every member of {@link PLACEHOLDER_SOURCES} parses, and a bogus
11
+ * source value is rejected.
12
+ *
13
+ * @task T9875
14
+ * @epic T9874
15
+ * @saga T9855
16
+ */
17
+ export {};
18
+ //# sourceMappingURL=manifest.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.test.d.ts","sourceRoot":"","sources":["../../../src/templates/__tests__/manifest.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG"}
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Contract tests for the TemplateManifest schema.
3
+ *
4
+ * Asserts:
5
+ *
6
+ * 1. A valid entry for every {@link TemplateKind} parses successfully.
7
+ * 2. An entry with an unknown `kind` is rejected.
8
+ * 3. An entry missing `installPath` is rejected.
9
+ * 4. The {@link PlaceholderSpec} `source` field is exhaustively typed —
10
+ * every member of {@link PLACEHOLDER_SOURCES} parses, and a bogus
11
+ * source value is rejected.
12
+ *
13
+ * @task T9875
14
+ * @epic T9874
15
+ * @saga T9855
16
+ */
17
+ import { describe, expect, it } from 'vitest';
18
+ import { PLACEHOLDER_SOURCES, PlaceholderSpecSchema, TEMPLATE_KINDS, TemplateManifestEntrySchema, } from '../manifest.js';
19
+ /**
20
+ * Build a valid baseline entry parameterized by `kind` so each
21
+ * assertion below stays minimal.
22
+ */
23
+ function baseEntry(kind) {
24
+ return {
25
+ id: `sample-${kind}`,
26
+ kind,
27
+ sourcePath: `packages/core/templates/${kind}s/sample.tmpl`,
28
+ installPath: `.cleo/${kind}s/sample`,
29
+ substitution: 'regex-tmpl',
30
+ placeholders: [
31
+ {
32
+ name: 'NODE_VERSION',
33
+ source: 'project-context',
34
+ sourcePath: 'engines.node',
35
+ defaultValue: 24,
36
+ },
37
+ ],
38
+ updateStrategy: 'overwrite-on-bump',
39
+ };
40
+ }
41
+ describe('TemplateManifestEntrySchema (T9875)', () => {
42
+ it('accepts a valid entry for every TemplateKind', () => {
43
+ for (const kind of TEMPLATE_KINDS) {
44
+ const entry = baseEntry(kind);
45
+ const parsed = TemplateManifestEntrySchema.parse(entry);
46
+ expect(parsed.kind, `kind '${kind}' must round-trip`).toBe(kind);
47
+ expect(parsed.installPath).toBe(entry.installPath);
48
+ }
49
+ });
50
+ it('rejects an entry with an unknown kind', () => {
51
+ const bogus = {
52
+ ...baseEntry('workflow'),
53
+ kind: 'not-a-real-kind',
54
+ };
55
+ const result = TemplateManifestEntrySchema.safeParse(bogus);
56
+ expect(result.success).toBe(false);
57
+ });
58
+ it('rejects an entry missing installPath', () => {
59
+ const { installPath: _drop, ...rest } = baseEntry('config');
60
+ const result = TemplateManifestEntrySchema.safeParse(rest);
61
+ expect(result.success).toBe(false);
62
+ });
63
+ it('accepts an entry whose placeholders array is empty', () => {
64
+ const entry = { ...baseEntry('doc'), placeholders: [] };
65
+ const result = TemplateManifestEntrySchema.safeParse(entry);
66
+ expect(result.success).toBe(true);
67
+ });
68
+ it('accepts a defaultValue of every supported literal type and null', () => {
69
+ const types = [
70
+ { defaultValue: 'string-default' },
71
+ { defaultValue: 42 },
72
+ { defaultValue: true },
73
+ { defaultValue: null },
74
+ {},
75
+ ];
76
+ for (const extra of types) {
77
+ const result = PlaceholderSpecSchema.safeParse({
78
+ name: 'X',
79
+ source: 'literal',
80
+ sourcePath: 'X',
81
+ ...extra,
82
+ });
83
+ expect(result.success, `defaultValue case ${JSON.stringify(extra)} must parse`).toBe(true);
84
+ }
85
+ });
86
+ });
87
+ describe('PlaceholderSpecSchema source enum (T9875)', () => {
88
+ it('exhaustively accepts every member of PLACEHOLDER_SOURCES', () => {
89
+ for (const source of PLACEHOLDER_SOURCES) {
90
+ const result = PlaceholderSpecSchema.safeParse({
91
+ name: 'PLACEHOLDER',
92
+ source,
93
+ sourcePath: 'some.path',
94
+ });
95
+ expect(result.success, `source '${source}' must parse`).toBe(true);
96
+ }
97
+ });
98
+ it('rejects an unknown source value', () => {
99
+ const result = PlaceholderSpecSchema.safeParse({
100
+ name: 'PLACEHOLDER',
101
+ source: 'not-a-source',
102
+ sourcePath: 'some.path',
103
+ });
104
+ expect(result.success).toBe(false);
105
+ });
106
+ it('rejects an empty name', () => {
107
+ const result = PlaceholderSpecSchema.safeParse({
108
+ name: '',
109
+ source: 'project-context',
110
+ sourcePath: 'some.path',
111
+ });
112
+ expect(result.success).toBe(false);
113
+ });
114
+ it('rejects an empty sourcePath', () => {
115
+ const result = PlaceholderSpecSchema.safeParse({
116
+ name: 'X',
117
+ source: 'project-context',
118
+ sourcePath: '',
119
+ });
120
+ expect(result.success).toBe(false);
121
+ });
122
+ });
123
+ //# sourceMappingURL=manifest.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.test.js","sourceRoot":"","sources":["../../../src/templates/__tests__/manifest.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,186 @@
1
+ /**
2
+ * TemplateManifest contract — SSoT for every template CLEO ships, installs,
3
+ * or rewrites during `cleo init` / `cleo upgrade` / `cleo scaffold-*`.
4
+ *
5
+ * A {@link TemplateManifestEntry} declares one template file: where its
6
+ * source lives in the monorepo, where it installs into a consumer project,
7
+ * how its placeholders are substituted, which placeholders it accepts, and
8
+ * how it MUST be reconciled when the shipped version moves ahead of the
9
+ * deployed copy.
10
+ *
11
+ * This contract is the leaf type. The CORE-side template registry
12
+ * (T9877) consumes it; the CLI verbs (`cleo init --workflows`,
13
+ * `cleo upgrade`, the scaffold sweeper) read the manifest at runtime to
14
+ * decide what to write, what to skip, and what to diff-prompt the user on.
15
+ *
16
+ * @example minimal workflow template entry
17
+ * ```ts
18
+ * import { TemplateManifestEntrySchema, type TemplateManifestEntry } from '@cleocode/contracts';
19
+ *
20
+ * const entry: TemplateManifestEntry = TemplateManifestEntrySchema.parse({
21
+ * id: 'ci.yml',
22
+ * kind: 'workflow',
23
+ * sourcePath: 'packages/core/templates/workflows/ci.yml.tmpl',
24
+ * installPath: '.github/workflows/ci.yml',
25
+ * substitution: 'regex-tmpl',
26
+ * placeholders: [
27
+ * {
28
+ * name: 'NODE_VERSION',
29
+ * source: 'project-context',
30
+ * sourcePath: 'engines.node',
31
+ * defaultValue: 24,
32
+ * },
33
+ * ],
34
+ * updateStrategy: 'overwrite-on-bump',
35
+ * });
36
+ * ```
37
+ *
38
+ * @task T9875
39
+ * @epic T9874
40
+ * @saga T9855
41
+ * @see ADR-076 (canonical doc routing — same boundary discipline applies here)
42
+ */
43
+ import { z } from 'zod';
44
+ /**
45
+ * Allowed categories of template the manifest can describe.
46
+ *
47
+ * - `workflow` — GitHub Actions / CI workflow files.
48
+ * - `config` — Configuration files (e.g. `.editorconfig`, `tsconfig.json`).
49
+ * - `agent` — Agent definition files shipped to `.claude/agents/` etc.
50
+ * - `skill` — Skill markdown shipped to `.claude/skills/` etc.
51
+ * - `provider` — Provider-specific bootstrap files (Anthropic, OpenAI, …).
52
+ * - `doc` — Documentation scaffolds (e.g. README.md, AGENTS.md).
53
+ */
54
+ export declare const TEMPLATE_KINDS: readonly ["workflow", "config", "agent", "skill", "provider", "doc"];
55
+ /** Discriminator for the category of file a template represents. */
56
+ export type TemplateKind = (typeof TEMPLATE_KINDS)[number];
57
+ /**
58
+ * Allowed substitution strategies a template can request.
59
+ *
60
+ * - `regex-tmpl` — `{{KEY}}` placeholder substitution via regex.
61
+ * - `static` — No substitution; the file is copied byte-for-byte.
62
+ * - `json-merge` — The template is parsed as JSON and merged into any
63
+ * existing JSON file at `installPath`; placeholders inside
64
+ * string values still go through `regex-tmpl`.
65
+ */
66
+ export declare const TEMPLATE_SUBSTITUTIONS: readonly ["regex-tmpl", "static", "json-merge"];
67
+ /** Strategy the installer uses to materialize a template's `sourcePath`. */
68
+ export type TemplateSubstitution = (typeof TEMPLATE_SUBSTITUTIONS)[number];
69
+ /**
70
+ * How an installed template is reconciled with the shipped copy on
71
+ * `cleo upgrade` / `cleo init --force`.
72
+ *
73
+ * - `overwrite-on-bump` — Always overwrite when the manifest version is
74
+ * newer than the deployed copy.
75
+ * - `diff-prompt` — Show a diff and ask the user before overwriting.
76
+ * - `immutable` — Never overwrite once installed.
77
+ * - `manifest-merge` — Apply structural merge (e.g. JSON deep-merge,
78
+ * respecting existing user keys).
79
+ */
80
+ export declare const TEMPLATE_UPDATE_STRATEGIES: readonly ["overwrite-on-bump", "diff-prompt", "immutable", "manifest-merge"];
81
+ /** Reconciliation policy for an installed template on upgrade. */
82
+ export type UpdateStrategy = (typeof TEMPLATE_UPDATE_STRATEGIES)[number];
83
+ /**
84
+ * Allowed resolver sources a {@link PlaceholderSpec} can reference.
85
+ *
86
+ * - `project-context` — `.cleo/project-context.json` (detected ecosystem hints).
87
+ * - `project-info` — `.cleo/project-info.json` (per-project metadata).
88
+ * - `.cleo/config` — Per-project config (`<projectRoot>/.cleo/config.json`).
89
+ * - `~/.cleo/config` — User-global config (`$XDG_CONFIG_HOME/cleo/config.json`).
90
+ * - `tool-resolver` — Resolved via the per-language tool resolver
91
+ * (e.g. `pnpm` vs `npm` chosen by `primaryType`).
92
+ * - `literal` — Hard-coded value baked into the manifest entry.
93
+ */
94
+ export declare const PLACEHOLDER_SOURCES: readonly ["project-context", "project-info", ".cleo/config", "~/.cleo/config", "tool-resolver", "literal"];
95
+ /** Resolver source enum for {@link PlaceholderSpec.source}. */
96
+ export type PlaceholderSource = (typeof PLACEHOLDER_SOURCES)[number];
97
+ /**
98
+ * Zod schema for a single placeholder declaration on a template entry.
99
+ *
100
+ * Each placeholder is resolved at install time by reading `sourcePath` from
101
+ * the named `source`. If resolution returns `undefined`, the installer
102
+ * falls back to `defaultValue`; if neither yields a value, installation
103
+ * fails with `E_PLACEHOLDER_UNRESOLVED`.
104
+ */
105
+ export declare const PlaceholderSpecSchema: z.ZodObject<{
106
+ name: z.ZodString;
107
+ source: z.ZodEnum<{
108
+ literal: "literal";
109
+ "project-info": "project-info";
110
+ "project-context": "project-context";
111
+ ".cleo/config": ".cleo/config";
112
+ "~/.cleo/config": "~/.cleo/config";
113
+ "tool-resolver": "tool-resolver";
114
+ }>;
115
+ sourcePath: z.ZodString;
116
+ defaultValue: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull]>>;
117
+ }, z.core.$strip>;
118
+ /**
119
+ * Zod-inferred input shape for a placeholder declaration.
120
+ *
121
+ * Identical to {@link PlaceholderSpec} — there are no schema transforms.
122
+ */
123
+ export type PlaceholderSpecInput = z.infer<typeof PlaceholderSpecSchema>;
124
+ /** Declared placeholder on a {@link TemplateManifestEntry}. */
125
+ export type PlaceholderSpec = PlaceholderSpecInput;
126
+ /**
127
+ * Zod schema for a single template manifest entry.
128
+ *
129
+ * Validates the full descriptor a template registry consumer needs to
130
+ * install a template file:
131
+ *
132
+ * - `id` — Stable identifier (kebab-case, matches install basename
133
+ * or a domain-unique slug).
134
+ * - `kind` — One of {@link TEMPLATE_KINDS}.
135
+ * - `sourcePath` — Repo-relative path of the template source.
136
+ * - `installPath` — Project-relative path where the rendered file lands.
137
+ * - `substitution` — One of {@link TEMPLATE_SUBSTITUTIONS}.
138
+ * - `placeholders` — Declared placeholders the installer must resolve.
139
+ * - `updateStrategy` — Reconciliation policy on upgrade.
140
+ */
141
+ export declare const TemplateManifestEntrySchema: z.ZodObject<{
142
+ id: z.ZodString;
143
+ kind: z.ZodEnum<{
144
+ agent: "agent";
145
+ config: "config";
146
+ provider: "provider";
147
+ workflow: "workflow";
148
+ doc: "doc";
149
+ skill: "skill";
150
+ }>;
151
+ sourcePath: z.ZodString;
152
+ installPath: z.ZodString;
153
+ substitution: z.ZodEnum<{
154
+ static: "static";
155
+ "regex-tmpl": "regex-tmpl";
156
+ "json-merge": "json-merge";
157
+ }>;
158
+ placeholders: z.ZodArray<z.ZodObject<{
159
+ name: z.ZodString;
160
+ source: z.ZodEnum<{
161
+ literal: "literal";
162
+ "project-info": "project-info";
163
+ "project-context": "project-context";
164
+ ".cleo/config": ".cleo/config";
165
+ "~/.cleo/config": "~/.cleo/config";
166
+ "tool-resolver": "tool-resolver";
167
+ }>;
168
+ sourcePath: z.ZodString;
169
+ defaultValue: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull]>>;
170
+ }, z.core.$strip>>;
171
+ updateStrategy: z.ZodEnum<{
172
+ "overwrite-on-bump": "overwrite-on-bump";
173
+ "diff-prompt": "diff-prompt";
174
+ immutable: "immutable";
175
+ "manifest-merge": "manifest-merge";
176
+ }>;
177
+ }, z.core.$strip>;
178
+ /**
179
+ * Zod-inferred input shape for a template manifest entry.
180
+ *
181
+ * Identical to {@link TemplateManifestEntry} — there are no schema transforms.
182
+ */
183
+ export type TemplateManifestEntryInput = z.infer<typeof TemplateManifestEntrySchema>;
184
+ /** Public type for a fully-validated template manifest entry. */
185
+ export type TemplateManifestEntry = TemplateManifestEntryInput;
186
+ //# sourceMappingURL=manifest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/templates/manifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc,sEAAuE,CAAC;AAEnG,oEAAoE;AACpE,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC;AAI3D;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB,iDAAkD,CAAC;AAEtF,4EAA4E;AAC5E,MAAM,MAAM,oBAAoB,GAAG,CAAC,OAAO,sBAAsB,CAAC,CAAC,MAAM,CAAC,CAAC;AAI3E;;;;;;;;;;GAUG;AACH,eAAO,MAAM,0BAA0B,8EAK7B,CAAC;AAEX,kEAAkE;AAClE,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,0BAA0B,CAAC,CAAC,MAAM,CAAC,CAAC;AAIzE;;;;;;;;;;GAUG;AACH,eAAO,MAAM,mBAAmB,4GAOtB,CAAC;AAEX,+DAA+D;AAC/D,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC;AAErE;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;iBAmBhC,CAAC;AAEH;;;;GAIG;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEzE,+DAA+D;AAC/D,MAAM,MAAM,eAAe,GAAG,oBAAoB,CAAC;AAInD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAetC,CAAC;AAEH;;;;GAIG;AACH,MAAM,MAAM,0BAA0B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC;AAErF,iEAAiE;AACjE,MAAM,MAAM,qBAAqB,GAAG,0BAA0B,CAAC"}