mhproto 0.8.0-preview.0

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 (124) hide show
  1. package/CONTRIBUTING.md +62 -0
  2. package/LICENSE +21 -0
  3. package/README.md +53 -0
  4. package/THIRD_PARTY_NOTICES.md +40 -0
  5. package/bin/mhproto.mjs +248 -0
  6. package/doc/context-design.md +43 -0
  7. package/doc/context-measurements.json +28 -0
  8. package/doc/guide.md +138 -0
  9. package/doc/publishing.md +93 -0
  10. package/doc/release-notes.md +23 -0
  11. package/doc/release-review-2026-10-02.md +127 -0
  12. package/doc/release-review.md +68 -0
  13. package/doc/viewer-design.md +80 -0
  14. package/package.json +69 -0
  15. package/skills/mhproto-discover/SKILL.md +14 -0
  16. package/skills/mhproto-implement/SKILL.md +14 -0
  17. package/skills/mhproto-reconcile/SKILL.md +14 -0
  18. package/skills/mhproto-specify/SKILL.md +16 -0
  19. package/skills/mhproto-specify/references/format.md +20 -0
  20. package/skills/mhproto-verify/SKILL.md +14 -0
  21. package/src/config.mjs +202 -0
  22. package/src/context.mjs +228 -0
  23. package/src/core.mjs +471 -0
  24. package/src/node-reporter.mjs +15 -0
  25. package/src/paths.mjs +51 -0
  26. package/src/server.mjs +290 -0
  27. package/src/verify.mjs +149 -0
  28. package/src/visuals.mjs +189 -0
  29. package/viewer/app.js +1463 -0
  30. package/viewer/diff.js +245 -0
  31. package/viewer/index.html +51 -0
  32. package/viewer/style.css +1003 -0
  33. package/viewer/vendor/LICENSE +21 -0
  34. package/viewer/vendor/NOTICE.txt +3372 -0
  35. package/viewer/vendor/README.md +75 -0
  36. package/viewer/vendor/build-evidence.json +2775 -0
  37. package/viewer/vendor/bundled-audit.json +88 -0
  38. package/viewer/vendor/bundled-packages.json +84 -0
  39. package/viewer/vendor/embedded-notices.txt +33 -0
  40. package/viewer/vendor/license-inventory.json +1456 -0
  41. package/viewer/vendor/licenses/braintree__sanitize-url-7.1.2.txt +21 -0
  42. package/viewer/vendor/licenses/chevrotain-13.2.0.txt +202 -0
  43. package/viewer/vendor/licenses/chevrotain-allstar-0.5.0.txt +16 -0
  44. package/viewer/vendor/licenses/chevrotain__cst-dts-gen-13.2.0.txt +202 -0
  45. package/viewer/vendor/licenses/chevrotain__gast-13.2.0.txt +202 -0
  46. package/viewer/vendor/licenses/chevrotain__regexp-to-ast-13.2.0.txt +202 -0
  47. package/viewer/vendor/licenses/chevrotain__utils-13.2.0.txt +202 -0
  48. package/viewer/vendor/licenses/cose-base-1.0.3.txt +21 -0
  49. package/viewer/vendor/licenses/cose-base-2.2.0.txt +21 -0
  50. package/viewer/vendor/licenses/cytoscape-3.34.3.txt +19 -0
  51. package/viewer/vendor/licenses/cytoscape-cose-bilkent-4.1.0.txt +21 -0
  52. package/viewer/vendor/licenses/cytoscape-fcose-2.2.0.txt +19 -0
  53. package/viewer/vendor/licenses/d3-7.9.0.txt +13 -0
  54. package/viewer/vendor/licenses/d3-array-2.12.1.txt +27 -0
  55. package/viewer/vendor/licenses/d3-array-3.2.4.txt +13 -0
  56. package/viewer/vendor/licenses/d3-axis-3.0.0.txt +13 -0
  57. package/viewer/vendor/licenses/d3-brush-3.0.0.txt +13 -0
  58. package/viewer/vendor/licenses/d3-chord-3.0.1.txt +13 -0
  59. package/viewer/vendor/licenses/d3-color-3.1.0.txt +13 -0
  60. package/viewer/vendor/licenses/d3-contour-4.0.2.txt +13 -0
  61. package/viewer/vendor/licenses/d3-delaunay-6.0.4.txt +14 -0
  62. package/viewer/vendor/licenses/d3-dispatch-3.0.1.txt +13 -0
  63. package/viewer/vendor/licenses/d3-drag-3.0.0.txt +13 -0
  64. package/viewer/vendor/licenses/d3-dsv-3.0.1.txt +13 -0
  65. package/viewer/vendor/licenses/d3-ease-3.0.1.txt +28 -0
  66. package/viewer/vendor/licenses/d3-fetch-3.0.1.txt +13 -0
  67. package/viewer/vendor/licenses/d3-force-3.0.0.txt +13 -0
  68. package/viewer/vendor/licenses/d3-format-3.1.2.txt +13 -0
  69. package/viewer/vendor/licenses/d3-geo-3.1.1.txt +34 -0
  70. package/viewer/vendor/licenses/d3-hierarchy-3.1.2.txt +13 -0
  71. package/viewer/vendor/licenses/d3-interpolate-3.0.1.txt +13 -0
  72. package/viewer/vendor/licenses/d3-path-1.0.9.txt +27 -0
  73. package/viewer/vendor/licenses/d3-path-3.1.0.txt +13 -0
  74. package/viewer/vendor/licenses/d3-polygon-3.0.1.txt +13 -0
  75. package/viewer/vendor/licenses/d3-quadtree-3.0.1.txt +13 -0
  76. package/viewer/vendor/licenses/d3-random-3.0.1.txt +13 -0
  77. package/viewer/vendor/licenses/d3-sankey-0.12.3.txt +27 -0
  78. package/viewer/vendor/licenses/d3-scale-4.0.2.txt +13 -0
  79. package/viewer/vendor/licenses/d3-scale-chromatic-3.1.0.txt +28 -0
  80. package/viewer/vendor/licenses/d3-selection-3.0.0.txt +13 -0
  81. package/viewer/vendor/licenses/d3-shape-1.3.7.txt +27 -0
  82. package/viewer/vendor/licenses/d3-shape-3.2.0.txt +13 -0
  83. package/viewer/vendor/licenses/d3-time-3.1.0.txt +13 -0
  84. package/viewer/vendor/licenses/d3-time-format-4.1.0.txt +13 -0
  85. package/viewer/vendor/licenses/d3-timer-3.0.1.txt +13 -0
  86. package/viewer/vendor/licenses/d3-transition-3.0.1.txt +13 -0
  87. package/viewer/vendor/licenses/d3-zoom-3.0.0.txt +13 -0
  88. package/viewer/vendor/licenses/dagre-d3-es-7.0.14.txt +23 -0
  89. package/viewer/vendor/licenses/dayjs-1.11.23.txt +21 -0
  90. package/viewer/vendor/licenses/delaunator-5.1.0.txt +15 -0
  91. package/viewer/vendor/licenses/dompurify-3.4.16.txt +202 -0
  92. package/viewer/vendor/licenses/elk-source-notice.txt +11 -0
  93. package/viewer/vendor/licenses/elkjs-0.9.3.txt +264 -0
  94. package/viewer/vendor/licenses/embedded-and-node-notices.txt +45 -0
  95. package/viewer/vendor/licenses/es-toolkit-1.52.0-1.txt +39 -0
  96. package/viewer/vendor/licenses/es-toolkit-1.52.0.txt +21 -0
  97. package/viewer/vendor/licenses/fastdom-1.0.12.txt +221 -0
  98. package/viewer/vendor/licenses/iconify__utils-3.1.7.txt +21 -0
  99. package/viewer/vendor/licenses/internmap-1.0.1.txt +13 -0
  100. package/viewer/vendor/licenses/internmap-2.0.3.txt +13 -0
  101. package/viewer/vendor/licenses/js-yaml-4.3.2.txt +21 -0
  102. package/viewer/vendor/licenses/katex-0.16.47.txt +21 -0
  103. package/viewer/vendor/licenses/khroma-2.1.0.txt +21 -0
  104. package/viewer/vendor/licenses/langium-4.4.0.txt +16 -0
  105. package/viewer/vendor/licenses/layout-base-1.0.2.txt +21 -0
  106. package/viewer/vendor/licenses/layout-base-2.0.1.txt +21 -0
  107. package/viewer/vendor/licenses/lodash-es-4.18.1.txt +47 -0
  108. package/viewer/vendor/licenses/marked-16.4.2.txt +44 -0
  109. package/viewer/vendor/licenses/mermaid-12.1.0.txt +21 -0
  110. package/viewer/vendor/licenses/mermaid-js__parser-2.0.1.txt +21 -0
  111. package/viewer/vendor/licenses/path-browserify-1.0.1.txt +20 -0
  112. package/viewer/vendor/licenses/robust-predicates-3.0.3.txt +24 -0
  113. package/viewer/vendor/licenses/roughjs-4.6.6.txt +21 -0
  114. package/viewer/vendor/licenses/stylis-4.4.0.txt +21 -0
  115. package/viewer/vendor/licenses/ts-dedent-2.3.0.txt +21 -0
  116. package/viewer/vendor/licenses/upsetjs__venn.js-2.0.0.txt +22 -0
  117. package/viewer/vendor/licenses/uuid-14.0.2.txt +9 -0
  118. package/viewer/vendor/licenses/vscode-jsonrpc-9.0.3.txt +11 -0
  119. package/viewer/vendor/licenses/vscode-languageserver-protocol-3.18.4.txt +11 -0
  120. package/viewer/vendor/licenses/vscode-languageserver-textdocument-1.0.15.txt +11 -0
  121. package/viewer/vendor/licenses/vscode-languageserver-types-3.18.4.txt +11 -0
  122. package/viewer/vendor/licenses/vscode-uri-3.1.0.txt +9 -0
  123. package/viewer/vendor/manifest.json +21 -0
  124. package/viewer/vendor/mermaid.min.js +7729 -0
package/src/config.mjs ADDED
@@ -0,0 +1,202 @@
1
+ import Ajv from 'ajv';
2
+
3
+ const text = { type: 'string', minLength: 1 };
4
+ const texts = { type: 'array', items: text, uniqueItems: true };
5
+ const object = (properties, required = []) => ({ type: 'object', properties, required });
6
+ const strictObject = (properties, required = []) => ({
7
+ ...object(properties, required),
8
+ additionalProperties: false,
9
+ patternProperties: { '^x-': {} },
10
+ });
11
+ const ajv = new Ajv({ allErrors: true });
12
+ const schema = { anyOf: [{ type: 'object' }, { type: 'boolean' }] };
13
+ const reference = object({ $ref: text }, ['$ref']);
14
+ const media = object({
15
+ schema,
16
+ examples: { type: 'object', additionalProperties: { type: 'object' } },
17
+ });
18
+ const content = { type: 'object', additionalProperties: media };
19
+ const parameter = {
20
+ anyOf: [
21
+ reference,
22
+ object(
23
+ {
24
+ name: text,
25
+ in: { enum: ['path', 'query', 'header', 'cookie'] },
26
+ schema,
27
+ required: { type: 'boolean' },
28
+ },
29
+ ['name', 'in'],
30
+ ),
31
+ ],
32
+ };
33
+ const parameters = { type: 'array', items: parameter };
34
+ const body = object({ content, required: { type: 'boolean' } });
35
+ const response = object({ content, 'x-error-codes': { type: 'array', items: { type: 'object' } } });
36
+ const operation = object({
37
+ operationId: text,
38
+ parameters,
39
+ requestBody: body,
40
+ responses: { type: 'object', additionalProperties: response },
41
+ 'x-mhproto-rules': texts,
42
+ 'x-clauses': texts,
43
+ 'x-preconditions': { type: 'array', items: { type: 'object' } },
44
+ });
45
+ const pathItem = object({
46
+ parameters,
47
+ ...Object.fromEntries(
48
+ ['get', 'post', 'put', 'patch', 'delete', 'options', 'head', 'trace'].map((method) => [
49
+ method,
50
+ operation,
51
+ ]),
52
+ ),
53
+ });
54
+ const shapes = {
55
+ evidence: object(
56
+ {
57
+ version: { const: 1 },
58
+ capability: text,
59
+ digest: text,
60
+ finishedAt: text,
61
+ results: {
62
+ type: 'array',
63
+ items: object(
64
+ { id: text, status: { enum: ['passing', 'failing'] }, tests: { type: 'array' } },
65
+ ['id', 'status'],
66
+ ),
67
+ },
68
+ },
69
+ ['version', 'capability', 'digest', 'finishedAt', 'results'],
70
+ ),
71
+ openapi: object({
72
+ openapi: text,
73
+ info: { type: 'object' },
74
+ paths: { type: 'object', additionalProperties: pathItem },
75
+ components: object({ schemas: { type: 'object', additionalProperties: schema } }),
76
+ }),
77
+ config: strictObject(
78
+ {
79
+ version: { const: 1 },
80
+ name: text,
81
+ system: text,
82
+ capabilities: {
83
+ type: 'array',
84
+ minItems: 1,
85
+ items: strictObject(
86
+ {
87
+ id: { ...text, pattern: '^[a-z][a-z0-9-]*$' },
88
+ title: text,
89
+ description: { type: 'string' },
90
+ owner: text,
91
+ spec: text,
92
+ interface: text,
93
+ examples: text,
94
+ checks: text,
95
+ sources: texts,
96
+ gaps: texts,
97
+ url: { ...text, pattern: '^/(?!/)' },
98
+ presentation: strictObject({
99
+ operationOrder: texts,
100
+ ruleTitles: { type: 'object', additionalProperties: text },
101
+ operations: {
102
+ type: 'object',
103
+ additionalProperties: strictObject({
104
+ description: { type: 'string' },
105
+ behaviour: { type: 'string' },
106
+ rules: texts,
107
+ ruleGroups: {
108
+ type: 'array',
109
+ items: strictObject(
110
+ { title: text, description: { type: 'string' }, rules: texts },
111
+ ['title', 'rules'],
112
+ ),
113
+ },
114
+ diagrams: {
115
+ type: 'array',
116
+ items: strictObject({ title: text, source: text }, ['title', 'source']),
117
+ },
118
+ }),
119
+ },
120
+ }),
121
+ },
122
+ ['id', 'spec', 'interface', 'examples', 'checks'],
123
+ ),
124
+ },
125
+ },
126
+ ['version', 'capabilities'],
127
+ ),
128
+ examples: strictObject(
129
+ {
130
+ examples: {
131
+ type: 'array',
132
+ items: strictObject(
133
+ {
134
+ id: text,
135
+ title: text,
136
+ rules: texts,
137
+ operations: texts,
138
+ given: text,
139
+ when: text,
140
+ then: text,
141
+ request: strictObject({ operation: text, body: {} }, ['operation']),
142
+ response: strictObject(
143
+ {
144
+ status: { anyOf: [{ type: 'integer', minimum: 100, maximum: 599 }, text] },
145
+ body: {},
146
+ },
147
+ ['status'],
148
+ ),
149
+ },
150
+ ['id', 'given', 'when', 'then'],
151
+ ),
152
+ },
153
+ },
154
+ ['examples'],
155
+ ),
156
+ checks: strictObject(
157
+ {
158
+ checks: {
159
+ type: 'array',
160
+ items: strictObject(
161
+ {
162
+ id: text,
163
+ title: text,
164
+ description: { type: 'string' },
165
+ rules: texts,
166
+ examples: texts,
167
+ files: texts,
168
+ command: {
169
+ type: 'array',
170
+ minItems: 1,
171
+ items: { type: 'string', pattern: '^[^\\u0000]*$' },
172
+ },
173
+ runner: { enum: ['command', 'node-test'] },
174
+ testNames: { ...texts, minItems: 1 },
175
+ timeoutMs: { type: 'integer', minimum: 1, maximum: 2147483647 },
176
+ env: { type: 'object', additionalProperties: { type: 'string' } },
177
+ },
178
+ ['id', 'command'],
179
+ ),
180
+ },
181
+ },
182
+ ['checks'],
183
+ ),
184
+ };
185
+ const validators = Object.fromEntries(
186
+ Object.entries(shapes).map(([name, schema]) => [name, ajv.compile(schema)]),
187
+ );
188
+
189
+ export function assertShape(name, value, file) {
190
+ const validates = validators[name];
191
+ if (!validates(value))
192
+ throw new Error(
193
+ `${file}: ${validates.errors.map((e) => `${e.instancePath || '/'} ${e.message}${e.params.additionalProperty ? ` (${e.params.additionalProperty})` : ''}`).join('; ')}`,
194
+ );
195
+ // YAML aliases may share nodes, but cycles cannot be represented in exported JSON.
196
+ try {
197
+ JSON.stringify(value);
198
+ } catch {
199
+ throw new Error(`${file}: cyclic or excessively deep input cannot be exported as JSON`);
200
+ }
201
+ return value;
202
+ }
@@ -0,0 +1,228 @@
1
+ // Scoped, deterministic retrieval. Images, logs and repeated schemas stay outside packets.
2
+ import { localRef } from './core.mjs';
3
+ export function operationRuleIds(cap, op) {
4
+ const p = cap.presentation?.operations?.[op.operationId] ?? {};
5
+ return [
6
+ ...new Set(
7
+ [
8
+ ...(op.rules ?? []),
9
+ ...(op['x-preconditions'] ?? []).map((x) => x.fails?.clause),
10
+ ...Object.values(op.responses ?? {}).flatMap((r) =>
11
+ (r['x-error-codes'] ?? []).map((x) => x.clause),
12
+ ),
13
+ ...(p.rules ?? []),
14
+ ...(p.ruleGroups ?? []).flatMap((g) => g.rules ?? []),
15
+ ].filter(Boolean),
16
+ ),
17
+ ];
18
+ }
19
+ const pick = (value, keys) =>
20
+ Object.fromEntries(keys.filter((k) => value[k] !== undefined).map((k) => [k, value[k]]));
21
+ const schemaRefs = (value) => {
22
+ const names = new Set();
23
+ const visit = (node) => {
24
+ if (!node || typeof node !== 'object') return;
25
+ const match = node.$ref?.match(/^#\/components\/schemas\/([^/]+)(?:\/.*)?$/);
26
+ if (match) names.add(decodeURIComponent(match[1]).replaceAll('~1', '/').replaceAll('~0', '~'));
27
+ for (const item of Object.values(node)) visit(item);
28
+ };
29
+ visit(value);
30
+ return [...names];
31
+ };
32
+ function rootSchema(cap, value) {
33
+ const name = value?.$ref?.match(/^#\/components\/schemas\/([^/]+)$/)?.[1];
34
+ if (!name) return value;
35
+ const schema = localRef(cap.openapi, value.$ref);
36
+ return {
37
+ name: decodeURIComponent(name).replaceAll('~1', '/').replaceAll('~0', '~'),
38
+ ...(typeof schema === 'boolean' ? { schema } : schema),
39
+ ...Object.fromEntries(Object.entries(value).filter(([key]) => key !== '$ref')),
40
+ };
41
+ }
42
+ function evidence(cap, check) {
43
+ if (!cap.evidence) return 'unchecked';
44
+ if (cap.evidence.stale || cap.evidence.changedDuringRun) return 'stale';
45
+ return cap.evidence.results.find((r) => r.id === check.id)?.status ?? 'unchecked';
46
+ }
47
+ export function contextPacket(project, options = {}) {
48
+ const selectors = ['operation', 'rule', 'schema', 'example', 'check', 'visual'].filter(
49
+ (k) => options[k],
50
+ );
51
+ if (selectors.length > 1)
52
+ throw new Error('Choose one selector: operation, rule, schema, example, check or visual');
53
+ const visualMetadata = (project.visuals ?? []).map((v) =>
54
+ pick(v, ['id', 'title', 'caption', 'kind', 'target', 'file', 'url', 'mime', 'sha256']),
55
+ );
56
+ if (options.visual) {
57
+ const visual = visualMetadata.find((v) => v.id === options.visual);
58
+ if (!visual) throw new Error(`Unknown visual: ${options.visual}`);
59
+ return {
60
+ project: project.name,
61
+ visual,
62
+ instruction: 'Open the local file or design link only when the visual is needed.',
63
+ };
64
+ }
65
+ if (!options.capability && !selectors.length)
66
+ return {
67
+ project: project.name,
68
+ features: project.capabilities.map((c) => ({
69
+ id: c.id,
70
+ title: c.title,
71
+ url: c.url,
72
+ operations: c.operations.map((o) => pick(o, ['operationId', 'method', 'path', 'summary'])),
73
+ })),
74
+ read: 'mhproto context --capability ID --operation ID; use --rule, --schema, --example, --check or --visual for detail.',
75
+ };
76
+ const cap = options.capability
77
+ ? project.capabilities.find((c) => c.id === options.capability)
78
+ : project.capabilities.length === 1
79
+ ? project.capabilities[0]
80
+ : null;
81
+ if (!cap) throw new Error('Select a known --capability ID');
82
+ const base = { project: project.name, capability: cap.id, digest: cap.digest };
83
+ if (selectors.length && !options.operation) {
84
+ const kind = selectors[0],
85
+ id = options[kind];
86
+ const item =
87
+ kind === 'schema'
88
+ ? cap.openapi.components?.schemas?.[id]
89
+ : cap[kind === 'rule' ? 'rules' : kind === 'example' ? 'examples' : 'checks'].find(
90
+ (x) => x.id === id,
91
+ );
92
+ if (item === undefined) throw new Error(`Unknown ${kind}: ${id}`);
93
+ const result = {
94
+ ...base,
95
+ [kind]: item,
96
+ visuals: visualMetadata.filter(
97
+ (v) => v.target.capability === cap.id && v.target.kind === kind && v.target.id === id,
98
+ ),
99
+ };
100
+ if (kind === 'check')
101
+ result.evidence = {
102
+ status: evidence(cap, item),
103
+ ...pick(cap.evidence?.results.find((r) => r.id === id) ?? {}, [
104
+ 'error',
105
+ 'missing',
106
+ 'skipped',
107
+ 'durationMs',
108
+ ]),
109
+ };
110
+ if (kind === 'schema') result.referencedSchemas = schemaRefs(item);
111
+ return result;
112
+ }
113
+ if (!options.operation)
114
+ return {
115
+ ...base,
116
+ ...pick(cap, ['title', 'description', 'url']),
117
+ operations: cap.operations.map((o) => pick(o, ['operationId', 'method', 'path', 'summary'])),
118
+ rules: cap.rules.map((r) => r.id),
119
+ examples: cap.examples.map((e) => ({ id: e.id, title: e.title })),
120
+ visuals: visualMetadata.filter(
121
+ (v) => v.target.capability === cap.id && v.target.kind === 'feature',
122
+ ),
123
+ sources: cap.files,
124
+ };
125
+ const op = cap.operations.find((o) => o.operationId === options.operation);
126
+ if (!op) throw new Error(`Unknown operation: ${options.operation}`);
127
+ const p = cap.presentation?.operations?.[op.operationId] ?? {},
128
+ ids = operationRuleIds(cap, op);
129
+ const grouped = new Set((p.ruleGroups ?? []).flatMap((g) => g.rules ?? []));
130
+ const examples = cap.examples.filter((e) => e.operations?.includes(op.operationId));
131
+ const request = {
132
+ parameters: (op.parameters ?? []).map((p) =>
133
+ Object.fromEntries(
134
+ Object.entries(p).filter(([key]) => !['example', 'examples'].includes(key)),
135
+ ),
136
+ ),
137
+ body: rootSchema(cap, op.requestBody?.content?.['application/json']?.schema),
138
+ required: op.requestBody?.required,
139
+ };
140
+ const response = Object.fromEntries(
141
+ Object.entries(op.responses ?? {})
142
+ .filter(([status]) => /^2/.test(status))
143
+ .map(([status, r]) => [
144
+ status,
145
+ {
146
+ description: r.description,
147
+ schema: rootSchema(cap, r.content?.['application/json']?.schema),
148
+ },
149
+ ]),
150
+ );
151
+ const errorResponses = Object.entries(op.responses ?? {}).filter(
152
+ ([status]) => !/^2/.test(status),
153
+ );
154
+ const errorSchemas = [
155
+ ...new Map(
156
+ errorResponses
157
+ .map(([, r]) => rootSchema(cap, r.content?.['application/json']?.schema))
158
+ .filter((schema) => schema !== undefined)
159
+ .map((s) => [JSON.stringify(s), s]),
160
+ ).values(),
161
+ ];
162
+ const fields = {
163
+ request,
164
+ response,
165
+ behaviour: {
166
+ rules: cap.rules.filter((r) => ids.includes(r.id) && !grouped.has(r.id)),
167
+ preconditions: (op['x-preconditions'] ?? []).map((x) =>
168
+ pick(x, ['needs', 'requires', 'description', 'fails']),
169
+ ),
170
+ deferredGroups: (p.ruleGroups ?? []).map((g) => ({
171
+ title: g.title,
172
+ rules: g.rules,
173
+ read: 'Fetch each --rule ID before changing this behaviour.',
174
+ })),
175
+ },
176
+ errors: {
177
+ cases: errorResponses.flatMap(([status, r]) =>
178
+ (r['x-error-codes'] ?? [{ when: r.description }]).map((e) => ({ status, ...e })),
179
+ ),
180
+ schemas: errorSchemas,
181
+ },
182
+ examples: examples.map((e) => pick(e, ['id', 'title', 'rules', 'given', 'when', 'then'])),
183
+ checks: cap.checks
184
+ .filter(
185
+ (c) =>
186
+ c.rules?.some((id) => ids.includes(id)) ||
187
+ c.examples?.some((id) => examples.some((e) => e.id === id)),
188
+ )
189
+ .map((c) => ({ ...pick(c, ['id', 'title', 'rules', 'examples']), status: evidence(cap, c) })),
190
+ visuals: visualMetadata.filter(
191
+ (v) =>
192
+ v.target.capability === cap.id &&
193
+ ((v.target.id === op.operationId &&
194
+ ['operation', 'request', 'response', 'field'].includes(v.target.kind)) ||
195
+ (v.target.kind === 'schema' && schemaRefs(op).includes(v.target.id)) ||
196
+ (v.target.kind === 'rule' && ids.includes(v.target.id)) ||
197
+ (v.target.kind === 'example' && examples.some((e) => e.id === v.target.id))),
198
+ ),
199
+ sources: { ...cap.files, implementation: cap.sources },
200
+ };
201
+ const selected = options.section
202
+ ? options.section.split(',')
203
+ : ['request', 'response', 'behaviour', 'errors', 'examples', 'checks', 'visuals'];
204
+ if (selected.some((k) => !(k in fields)))
205
+ throw new Error('Sections: request,response,behaviour,errors,examples,checks,visuals,sources');
206
+ const result = {
207
+ ...base,
208
+ operation: pick(op, ['operationId', 'method', 'path', 'summary']),
209
+ ...Object.fromEntries(selected.map((k) => [k, fields[k]])),
210
+ };
211
+ const refs = schemaRefs({
212
+ request: result.request,
213
+ response: result.response,
214
+ errors: result.errors,
215
+ });
216
+ if (refs.length) result.referencedSchemas = refs;
217
+ return result;
218
+ }
219
+ export function encodeContext(packet, maxChars = 12000) {
220
+ if (!Number.isInteger(maxChars) || maxChars < 100)
221
+ throw new Error('--max-chars must be an integer of at least 100');
222
+ const text = JSON.stringify(packet);
223
+ if (text.length > maxChars)
224
+ throw new Error(
225
+ `Context is ${text.length} characters, above budget ${maxChars}. Select --section or a specific --rule/--schema/--example/--check, or increase --max-chars. No content was truncated.`,
226
+ );
227
+ return text;
228
+ }