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/viewer/diff.js ADDED
@@ -0,0 +1,245 @@
1
+ // Pure contract comparison, shared by the CLI, server and browser.
2
+ const unordered = new Set([
3
+ 'required',
4
+ 'enum',
5
+ 'type',
6
+ 'rules',
7
+ 'operations',
8
+ 'examples',
9
+ 'sources',
10
+ 'testNames',
11
+ 'x-mhproto-rules',
12
+ 'x-clauses',
13
+ ]);
14
+ export function canonical(value, key = '', parents = []) {
15
+ const path = [...parents, key];
16
+ const literal =
17
+ path.some(
18
+ (part, i) =>
19
+ ['example', 'default', 'const', 'value'].includes(part) && path[i - 1] !== 'properties',
20
+ ) || path.some((part, i) => part === 'body' && ['request', 'response'].includes(path[i - 1]));
21
+ if (Array.isArray(value)) {
22
+ const items = value.map((v) => canonical(v, '', path));
23
+ return !literal && unordered.has(key)
24
+ ? items.sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b)))
25
+ : items;
26
+ }
27
+ if (value && typeof value === 'object')
28
+ return Object.fromEntries(
29
+ Object.keys(value)
30
+ .sort()
31
+ .filter((k) => value[k] !== undefined)
32
+ .map((k) => [k, canonical(value[k], k, path)]),
33
+ );
34
+ return value;
35
+ }
36
+ export function snapshotProject(value) {
37
+ const wrapped = ['mhproto-snapshot', 'bive-snapshot'].includes(value?.format);
38
+ if (wrapped && value.version !== 1) throw new Error('Unsupported MHProto snapshot version.');
39
+ const project = wrapped ? value.project : value;
40
+ if (
41
+ !project ||
42
+ !Array.isArray(project.capabilities) ||
43
+ !project.capabilities.length ||
44
+ typeof project.name !== 'string'
45
+ )
46
+ throw new Error('Choose a MHProto snapshot JSON or exported preview HTML.');
47
+ const ids = new Set();
48
+ for (const cap of project.capabilities) {
49
+ if (
50
+ !cap ||
51
+ typeof cap.id !== 'string' ||
52
+ ids.has(cap.id) ||
53
+ !Array.isArray(cap.operations) ||
54
+ !Array.isArray(cap.rules) ||
55
+ !Array.isArray(cap.examples) ||
56
+ !Array.isArray(cap.checks) ||
57
+ !cap.openapi
58
+ )
59
+ throw new Error('The baseline does not contain a valid MHProto contract.');
60
+ ids.add(cap.id);
61
+ }
62
+ return project;
63
+ }
64
+ export function contractSnapshot(
65
+ value,
66
+ { label = 'Iteration baseline', createdAt = new Date().toISOString() } = {},
67
+ ) {
68
+ const project = snapshotProject(value);
69
+ // Detach nested schemas and visuals: later viewer edits must not move the baseline.
70
+ return JSON.parse(
71
+ JSON.stringify({
72
+ format: 'mhproto-snapshot',
73
+ version: 1,
74
+ label,
75
+ createdAt,
76
+ project: {
77
+ name: project.name,
78
+ system: project.system ?? '',
79
+ visuals: project.visuals ?? [],
80
+ capabilities: project.capabilities.map(({ evidence, digest, ...cap }) => cap),
81
+ },
82
+ }),
83
+ );
84
+ }
85
+ const status = (a, b) => (a === undefined ? 'added' : b === undefined ? 'removed' : 'changed');
86
+ export function fieldChanges(before, after, path = []) {
87
+ if (
88
+ JSON.stringify(canonical(before, path.at(-1), path.slice(0, -1))) ===
89
+ JSON.stringify(canonical(after, path.at(-1), path.slice(0, -1)))
90
+ )
91
+ return [];
92
+ const object = (value) => value && typeof value === 'object' && !Array.isArray(value);
93
+ if (object(before) && object(after))
94
+ return [...new Set([...Object.keys(before), ...Object.keys(after)])]
95
+ .sort()
96
+ .flatMap((key) => fieldChanges(before[key], after[key], [...path, key]));
97
+ return [
98
+ {
99
+ path,
100
+ status: status(before, after),
101
+ beforePresent: before !== undefined,
102
+ afterPresent: after !== undefined,
103
+ before: before ?? null,
104
+ after: after ?? null,
105
+ },
106
+ ];
107
+ }
108
+ function behaviourText(text = '') {
109
+ let rule = false,
110
+ fence;
111
+ return text
112
+ .replaceAll('\r\n', '\n')
113
+ .split('\n')
114
+ .filter((line) => {
115
+ const marker = /^\s{0,3}(`{3,}|~{3,})/.exec(line)?.[1];
116
+ if (marker) {
117
+ if (!fence) fence = marker;
118
+ else if (marker[0] === fence[0] && marker.length >= fence.length) fence = null;
119
+ rule = false;
120
+ return true;
121
+ }
122
+ if (fence) return true;
123
+ if (/^\s*-\s+\*\*[A-Z][A-Z0-9-]*-\d+\*\*/.test(line)) {
124
+ rule = true;
125
+ return false;
126
+ }
127
+ if (rule && /^\s{2,}\S/.test(line)) return false;
128
+ rule = false;
129
+ return true;
130
+ })
131
+ .map((line) => line.trimEnd())
132
+ .join('\n')
133
+ .replace(/\n{3,}/g, '\n\n')
134
+ .trim();
135
+ }
136
+ export function compareModels(beforeValue, afterValue) {
137
+ const before = snapshotProject(beforeValue),
138
+ after = snapshotProject(afterValue),
139
+ changes = [];
140
+ const add = (capability, kind, id, a, b) => {
141
+ const fields = fieldChanges(a, b);
142
+ if (fields.length)
143
+ changes.push({
144
+ capability,
145
+ kind,
146
+ id,
147
+ status: status(a, b),
148
+ before: a ?? null,
149
+ after: b ?? null,
150
+ fields,
151
+ });
152
+ };
153
+ const compare = (capability, kind, oldItems, newItems, key) => {
154
+ const oldMap = new Map(oldItems.map((x) => [x[key], x])),
155
+ newMap = new Map(newItems.map((x) => [x[key], x]));
156
+ for (const id of new Set([...oldMap.keys(), ...newMap.keys()]))
157
+ add(capability, kind, id, oldMap.get(id), newMap.get(id));
158
+ };
159
+ add(
160
+ null,
161
+ 'system',
162
+ 'system',
163
+ { name: before.name, system: before.system ?? '' },
164
+ { name: after.name, system: after.system ?? '' },
165
+ );
166
+ const oldCaps = new Map(before.capabilities.map((c) => [c.id, c])),
167
+ newCaps = new Map(after.capabilities.map((c) => [c.id, c]));
168
+ const feature = (cap) => {
169
+ if (!cap) return undefined;
170
+ const { operations, ruleTitles, ...presentation } = cap.presentation ?? {};
171
+ const { paths, components, ...api } = cap.openapi,
172
+ { schemas, ...shared } = components ?? {};
173
+ const pathItems = Object.fromEntries(
174
+ Object.entries(paths ?? {}).map(([url, item]) => [
175
+ url,
176
+ Object.fromEntries(
177
+ Object.entries(item).filter(
178
+ ([key]) =>
179
+ ![
180
+ 'get',
181
+ 'post',
182
+ 'put',
183
+ 'patch',
184
+ 'delete',
185
+ 'options',
186
+ 'head',
187
+ 'trace',
188
+ 'parameters',
189
+ ].includes(key),
190
+ ),
191
+ ),
192
+ ]),
193
+ );
194
+ return {
195
+ title: cap.title ?? cap.id,
196
+ description: cap.description ?? '',
197
+ url: cap.url ?? '',
198
+ behaviour: behaviourText(cap.prose),
199
+ api: { ...api, components: shared, pathItems },
200
+ transitions: cap.transitions ?? [],
201
+ nonTransitions: cap.nonTransitions ?? [],
202
+ presentation,
203
+ files: cap.files ?? {},
204
+ sources: cap.sources ?? [],
205
+ gaps: cap.gaps ?? [],
206
+ };
207
+ };
208
+ const rules = (cap) =>
209
+ (cap?.rules ?? []).map((r) => ({ ...r, title: cap.presentation?.ruleTitles?.[r.id] ?? r.id }));
210
+ const operations = (cap) =>
211
+ (cap?.operations ?? []).map((op) => ({
212
+ ...op,
213
+ presentation: cap.presentation?.operations?.[op.operationId] ?? {},
214
+ }));
215
+ for (const id of new Set([...oldCaps.keys(), ...newCaps.keys()])) {
216
+ const a = oldCaps.get(id),
217
+ b = newCaps.get(id);
218
+ add(id, 'feature', id, feature(a), feature(b));
219
+ compare(id, 'rule', rules(a), rules(b), 'id');
220
+ compare(id, 'operation', operations(a), operations(b), 'operationId');
221
+ compare(id, 'example', a?.examples ?? [], b?.examples ?? [], 'id');
222
+ compare(id, 'check', a?.checks ?? [], b?.checks ?? [], 'id');
223
+ compare(
224
+ id,
225
+ 'schema',
226
+ Object.entries(a?.openapi.components?.schemas ?? {}).map(([name, schema]) => ({
227
+ name,
228
+ schema,
229
+ })),
230
+ Object.entries(b?.openapi.components?.schemas ?? {}).map(([name, schema]) => ({
231
+ name,
232
+ schema,
233
+ })),
234
+ 'name',
235
+ );
236
+ compare(
237
+ id,
238
+ 'visual',
239
+ (before.visuals ?? []).filter((v) => v.target?.capability === id),
240
+ (after.visuals ?? []).filter((v) => v.target?.capability === id),
241
+ 'id',
242
+ );
243
+ }
244
+ return changes;
245
+ }
@@ -0,0 +1,51 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width,initial-scale=1" />
6
+ <title>MHProto</title>
7
+ <meta
8
+ name="description"
9
+ content="A shared contract for humans and agents: linked behaviour, API types, examples and verification."
10
+ />
11
+ <link
12
+ rel="icon"
13
+ type="image/svg+xml"
14
+ href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Crect width='64' height='64' fill='white'/%3E%3Ctext x='3' y='43' font-family='monospace' font-size='26' font-weight='700' fill='%23141414'%3E%5Bmh%5D%3C/text%3E%3C/svg%3E"
15
+ />
16
+ <link rel="stylesheet" href="/style.css" />
17
+ </head>
18
+ <body>
19
+ <aside class="sidebar">
20
+ <a class="brand" id="home" href="https://mhproto.dev/" aria-label="MHProto homepage"
21
+ ><span class="brand-mark">[mh]</span><span>mhproto</span></a
22
+ >
23
+ <div class="nav-label">Project</div>
24
+ <div id="project-name" class="project-name">Loading…</div>
25
+ <div class="nav-label">Features</div>
26
+ <nav id="features" aria-label="Features"></nav>
27
+ <div class="sidebar-footer">
28
+ <a id="changes-link" href="#/changes">Changes</a><a id="sources-link" href="#">Sources</a
29
+ ><button id="save-preview" class="text-button" hidden>Save preview</button
30
+ ><span id="view-mode"></span>
31
+ </div>
32
+ </aside>
33
+ <main>
34
+ <div class="search-row">
35
+ <label class="sr-only" for="search">Search this project</label
36
+ ><input
37
+ id="search"
38
+ type="search"
39
+ placeholder="Search this project…"
40
+ autocomplete="off"
41
+ /><kbd>/</kbd>
42
+ </div>
43
+ <div id="content" aria-live="polite">Loading…</div>
44
+ <footer class="page-footer">
45
+ <span id="freshness"></span><span id="read-mode">Read-only</span>
46
+ </footer>
47
+ </main>
48
+ <script src="/vendor/mermaid.min.js"></script>
49
+ <script type="module" src="/app.js"></script>
50
+ </body>
51
+ </html>