@weftlet/toolkit 0.0.0-stage → 0.1.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.
Files changed (157) hide show
  1. package/LICENSE +176 -0
  2. package/NOTICE +6 -0
  3. package/README.md +162 -2
  4. package/THIRD_PARTY_NOTICES.md +610 -0
  5. package/dist/application/agent-context.d.ts +23 -0
  6. package/dist/application/agent-context.js +94 -0
  7. package/dist/application/application.d.ts +23 -0
  8. package/dist/application/application.js +91 -0
  9. package/dist/application/hydrator.d.ts +49 -0
  10. package/dist/application/hydrator.js +275 -0
  11. package/dist/application/overlay.d.ts +6 -0
  12. package/dist/application/overlay.js +110 -0
  13. package/dist/application/saved-evidence.d.ts +38 -0
  14. package/dist/application/saved-evidence.js +59 -0
  15. package/dist/application/snapshot-checks.d.ts +16 -0
  16. package/dist/application/snapshot-checks.js +51 -0
  17. package/dist/cli.d.ts +3 -0
  18. package/dist/cli.js +4 -0
  19. package/dist/configuration/cache.d.ts +2 -0
  20. package/dist/configuration/cache.js +10 -0
  21. package/dist/configuration/config.d.ts +79 -0
  22. package/dist/configuration/config.js +165 -0
  23. package/dist/core.d.ts +1 -0
  24. package/dist/core.js +2 -0
  25. package/dist/extraction/codex-auth.d.ts +12 -0
  26. package/dist/extraction/codex-auth.js +74 -0
  27. package/dist/extraction/document.d.ts +5 -0
  28. package/dist/extraction/document.js +54 -0
  29. package/dist/extraction/ontology.d.ts +32 -0
  30. package/dist/extraction/ontology.js +164 -0
  31. package/dist/extraction/providers.d.ts +8 -0
  32. package/dist/extraction/providers.js +58 -0
  33. package/dist/extraction/runtime-evidence.d.ts +2 -0
  34. package/dist/extraction/runtime-evidence.js +84 -0
  35. package/dist/extraction/semantic-citations.d.ts +7 -0
  36. package/dist/extraction/semantic-citations.js +27 -0
  37. package/dist/extraction/semantic-process.d.ts +11 -0
  38. package/dist/extraction/semantic-process.js +68 -0
  39. package/dist/extraction/semantic.d.ts +73 -0
  40. package/dist/extraction/semantic.js +414 -0
  41. package/dist/extraction/static.d.ts +52 -0
  42. package/dist/extraction/static.js +322 -0
  43. package/dist/extraction/telemetry.d.ts +19 -0
  44. package/dist/extraction/telemetry.js +259 -0
  45. package/dist/governance/governance.d.ts +60 -0
  46. package/dist/governance/governance.js +209 -0
  47. package/dist/graph/authority.d.ts +2 -0
  48. package/dist/graph/authority.js +13 -0
  49. package/dist/graph/diagnostics.d.ts +5 -0
  50. package/dist/graph/diagnostics.js +61 -0
  51. package/dist/graph/query.d.ts +16 -0
  52. package/dist/graph/query.js +373 -0
  53. package/dist/graph/rdf.d.ts +26 -0
  54. package/dist/graph/rdf.js +144 -0
  55. package/dist/index.d.ts +11 -0
  56. package/dist/index.js +12 -0
  57. package/dist/infrastructure/module-resolution.d.ts +2 -0
  58. package/dist/infrastructure/module-resolution.js +10 -0
  59. package/dist/infrastructure/process.d.ts +5 -0
  60. package/dist/infrastructure/process.js +27 -0
  61. package/dist/infrastructure/util.d.ts +1 -0
  62. package/dist/infrastructure/util.js +2 -0
  63. package/dist/interfaces/cli.d.ts +4 -0
  64. package/dist/interfaces/cli.js +197 -0
  65. package/dist/interfaces/doctor.d.ts +25 -0
  66. package/dist/interfaces/doctor.js +49 -0
  67. package/dist/interfaces/init.d.ts +24 -0
  68. package/dist/interfaces/init.js +154 -0
  69. package/dist/interfaces/mcp-facade.d.ts +3 -0
  70. package/dist/interfaces/mcp-facade.js +4 -0
  71. package/dist/interfaces/mcp.d.ts +2 -0
  72. package/dist/interfaces/mcp.js +81 -0
  73. package/dist/interfaces/request.d.ts +2 -0
  74. package/dist/interfaces/request.js +59 -0
  75. package/dist/mcp.d.ts +3 -0
  76. package/dist/mcp.js +4 -0
  77. package/dist/repository/committed-tree.d.ts +2 -0
  78. package/dist/repository/committed-tree.js +35 -0
  79. package/dist/repository/git.d.ts +26 -0
  80. package/dist/repository/git.js +163 -0
  81. package/dist/repository/glob.d.ts +2 -0
  82. package/dist/repository/glob.js +46 -0
  83. package/dist/repository/working-content.d.ts +3 -0
  84. package/dist/repository/working-content.js +34 -0
  85. package/dist/storage/atomic-rename.d.ts +2 -0
  86. package/dist/storage/atomic-rename.js +22 -0
  87. package/dist/storage/storage.d.ts +15 -0
  88. package/dist/storage/storage.js +115 -0
  89. package/dist/telemetry.d.ts +1 -0
  90. package/dist/telemetry.js +2 -0
  91. package/dist/visualization/viewer-interaction.d.ts +12 -0
  92. package/dist/visualization/viewer-interaction.js +123 -0
  93. package/dist/visualization/visualization.d.ts +26 -0
  94. package/dist/visualization/visualization.js +491 -0
  95. package/docs/ADR-001-standalone-baseline.md +16 -0
  96. package/docs/ADR-002-saved-evidence.md +21 -0
  97. package/docs/ADR-003-draft-overlays.md +23 -0
  98. package/docs/ADR-004-rdf-query-path.md +37 -0
  99. package/docs/ADR-005-governance-and-optional-oml.md +37 -0
  100. package/docs/ADR-006-provider-boundaries.md +15 -0
  101. package/docs/ADR-007-toolkit-modules.md +53 -0
  102. package/docs/ADR-008-weftlet-packages.md +32 -0
  103. package/docs/CLOUD-ENVIRONMENT.md +56 -0
  104. package/docs/ENVIRONMENT-VALIDATION.md +83 -0
  105. package/docs/MIGRATING-TO-WEFTLET.md +54 -0
  106. package/docs/PROVIDERS.md +121 -0
  107. package/docs/RELEASING.md +146 -0
  108. package/docs/ROADMAP.md +55 -0
  109. package/docs/SOURCE-INVENTORY.md +48 -0
  110. package/docs/VALIDATION.md +530 -0
  111. package/docs/VIEWER.md +36 -0
  112. package/docs/architecture-baseline.json +57 -0
  113. package/docs/architecture-policy.md +41 -0
  114. package/docs/extraction-manifest.json +721 -0
  115. package/docs/licenses/REVIEW.md +16 -0
  116. package/docs/licenses/lru-cache-11.5.3-BlueOak.md +55 -0
  117. package/docs/licenses/negotiate-1.0.1-BSD.txt +22 -0
  118. package/docs/licenses/spark-md5-3.0.2-MIT.txt +7 -0
  119. package/examples/generic-typescript/context-graph.config.json +47 -0
  120. package/examples/generic-typescript/context-graph.oml.config.json +49 -0
  121. package/examples/generic-typescript/docs/search-capability.md +14 -0
  122. package/examples/generic-typescript/ontology/build.gradle +38 -0
  123. package/examples/generic-typescript/ontology/catalog.xml +5 -0
  124. package/examples/generic-typescript/ontology/settings.gradle +1 -0
  125. package/examples/generic-typescript/ontology/src/oml/context-graph.dev/core.oml +99 -0
  126. package/examples/generic-typescript/package.json +6 -0
  127. package/examples/generic-typescript/src/search.ts +11 -0
  128. package/examples/generic-typescript/test/search.test.ts +8 -0
  129. package/examples/generic-typescript/tsconfig.json +10 -0
  130. package/examples/pipeline/context-graph.config.json +49 -0
  131. package/examples/pipeline/docs/admission.md +6 -0
  132. package/examples/pipeline/docs/analysis.md +6 -0
  133. package/examples/pipeline/docs/discovery.md +6 -0
  134. package/examples/pipeline/docs/execution.md +6 -0
  135. package/examples/pipeline/docs/outcomes.md +6 -0
  136. package/examples/pipeline/docs/planning.md +6 -0
  137. package/examples/pipeline/package.json +6 -0
  138. package/examples/pipeline/src/admission.ts +9 -0
  139. package/examples/pipeline/src/analysis.ts +9 -0
  140. package/examples/pipeline/src/contracts/admission.ts +1 -0
  141. package/examples/pipeline/src/contracts/analysis.ts +1 -0
  142. package/examples/pipeline/src/contracts/discovery.ts +1 -0
  143. package/examples/pipeline/src/contracts/execution.ts +1 -0
  144. package/examples/pipeline/src/contracts/outcomes.ts +1 -0
  145. package/examples/pipeline/src/contracts/planning.ts +1 -0
  146. package/examples/pipeline/src/discovery.ts +9 -0
  147. package/examples/pipeline/src/execution.ts +9 -0
  148. package/examples/pipeline/src/outcomes.ts +9 -0
  149. package/examples/pipeline/src/planning.ts +9 -0
  150. package/examples/pipeline/src/queue.ts +12 -0
  151. package/examples/pipeline/test/pipeline.test.ts +17 -0
  152. package/examples/pipeline/tsconfig.json +12 -0
  153. package/package.json +116 -3
  154. package/runtime-check.d.mts +2 -0
  155. package/runtime-check.mjs +12 -0
  156. package/templates/context-graph-development/SKILL.md +37 -0
  157. package/third-party-tools.json +34 -0
@@ -0,0 +1,491 @@
1
+ // Modified for standalone extraction; see docs/extraction-manifest.json.
2
+ import { mkdir, writeFile } from 'node:fs/promises';
3
+ import { dirname, extname, resolve } from 'node:path';
4
+ import { contextPackEvidenceAuthority, contextPackLaneForFact } from "../graph/query.js";
5
+ import { viewerScript } from "./viewer-interaction.js";
6
+ import { diagnosticSummary } from "../graph/diagnostics.js";
7
+ export const MAX_VISUALIZATION_FACTS = 200;
8
+ export const MAX_VISUALIZATION_RUNTIME_REFERENCES = 500;
9
+ const columns = ['intent', 'contracts', 'implementation', 'evidence'];
10
+ const columnLabels = {
11
+ intent: 'Product intent',
12
+ contracts: 'Contracts & invariants',
13
+ implementation: 'Implementation',
14
+ evidence: 'Validation & evidence',
15
+ };
16
+ const authorityColors = {
17
+ inferred: '#64748b',
18
+ draft: '#d97706',
19
+ declared: '#7c3aed',
20
+ implemented: '#0284c7',
21
+ observed: '#ea580c',
22
+ validated: '#059669',
23
+ };
24
+ const columnColors = {
25
+ intent: '#f3e8ff',
26
+ contracts: '#fef3c7',
27
+ implementation: '#e0f2fe',
28
+ evidence: '#d1fae5',
29
+ };
30
+ function escapeXml(value) {
31
+ return value
32
+ .replaceAll('&', '&')
33
+ .replaceAll('<', '&lt;')
34
+ .replaceAll('>', '&gt;')
35
+ .replaceAll('"', '&quot;')
36
+ .replaceAll("'", '&#39;');
37
+ }
38
+ function escapeDot(value) {
39
+ return `"${value
40
+ .replaceAll('\\', '\\\\')
41
+ .replaceAll('"', '\\"')
42
+ .replaceAll('\r', '\\r')
43
+ .replaceAll('\n', '\\n')}"`;
44
+ }
45
+ function jsonForHtml(value) {
46
+ return JSON.stringify(value)
47
+ .replaceAll('&', '\\u0026')
48
+ .replaceAll('<', '\\u003c')
49
+ .replaceAll('>', '\\u003e')
50
+ .replaceAll('\u2028', '\\u2028')
51
+ .replaceAll('\u2029', '\\u2029');
52
+ }
53
+ function visualColumn(kind, selectedLanes) {
54
+ if (['Contract', 'Invariant', 'PersistenceModel'].includes(kind))
55
+ return 'contracts';
56
+ if (['Evidence', 'Snapshot'].includes(kind))
57
+ return 'evidence';
58
+ if (['Project', 'Document', 'Capability', 'Workflow', 'Scope'].includes(kind)) {
59
+ if (selectedLanes?.has('productIntent'))
60
+ return 'intent';
61
+ if (selectedLanes?.has('contractsAndInvariants'))
62
+ return 'contracts';
63
+ if (selectedLanes?.has('implementationAndConsumers'))
64
+ return 'implementation';
65
+ if (selectedLanes?.has('validationEvidence'))
66
+ return 'evidence';
67
+ }
68
+ return 'implementation';
69
+ }
70
+ function addNode(nodes, node, selectedLanes) {
71
+ if (nodes.has(node.id))
72
+ return;
73
+ nodes.set(node.id, {
74
+ id: node.id,
75
+ label: node.label,
76
+ kind: node.kind,
77
+ cluster: String(node.attributes?.module ?? node.attributes?.layer ?? node.kind),
78
+ column: visualColumn(node.kind, selectedLanes),
79
+ });
80
+ }
81
+ function assertVisualizationBounds(pack) {
82
+ if (pack.facts.length > MAX_VISUALIZATION_FACTS) {
83
+ throw new Error(`Context Pack contains ${pack.facts.length} facts; visualization limit is ${MAX_VISUALIZATION_FACTS}`);
84
+ }
85
+ if (pack.runtimeEvidence.length > MAX_VISUALIZATION_RUNTIME_REFERENCES) {
86
+ throw new Error(`Context Pack contains ${pack.runtimeEvidence.length} runtime references; visualization limit is ${MAX_VISUALIZATION_RUNTIME_REFERENCES}`);
87
+ }
88
+ }
89
+ export function sourceLink(source, template) {
90
+ if (!template)
91
+ return undefined;
92
+ if (!template.includes('{commit}') || !template.includes('{path}'))
93
+ throw new Error('Source URL template must include {commit} and {path}');
94
+ if (source.path.startsWith('/') || source.path.includes('\\') || source.path.split('/').some(part => part === '..' || part === '.'))
95
+ throw new Error('Source path must be repository relative');
96
+ const url = new URL(template.replaceAll('{commit}', encodeURIComponent(source.commitSha))
97
+ .replaceAll('{path}', source.path.split('/').map(encodeURIComponent).join('/'))
98
+ .replaceAll('{line}', String(source.startLine)));
99
+ if (url.protocol !== 'https:' || url.username || url.password)
100
+ throw new Error('Source URL template must use HTTPS without credentials');
101
+ return url.href;
102
+ }
103
+ function visualModel(pack, sourceUrlTemplate) {
104
+ assertVisualizationBounds(pack);
105
+ const nodes = new Map();
106
+ const selectedLanes = new Map();
107
+ const edges = [];
108
+ for (const fact of pack.facts) {
109
+ const lane = contextPackLaneForFact(fact);
110
+ if (!lane)
111
+ continue;
112
+ for (const node of [fact.subject, fact.object]) {
113
+ const lanes = selectedLanes.get(node.id) ?? new Set();
114
+ lanes.add(lane);
115
+ selectedLanes.set(node.id, lanes);
116
+ }
117
+ }
118
+ for (const fact of pack.facts) {
119
+ addNode(nodes, fact.subject, selectedLanes.get(fact.subject.id));
120
+ addNode(nodes, fact.object, selectedLanes.get(fact.object.id));
121
+ edges.push({
122
+ id: fact.id,
123
+ from: fact.subject.id,
124
+ to: fact.object.id,
125
+ label: fact.predicate,
126
+ authority: contextPackEvidenceAuthority(fact),
127
+ provenance: `${fact.provenance.path}:${fact.provenance.startLine}`,
128
+ detail: `confidence ${fact.confidence}; ${fact.extractor.id}@${fact.extractor.version}; source ${fact.sourceAuthority ?? 'see citation'}; extraction ${fact.extractionReview ?? 'unspecified'}`,
129
+ source: fact.provenance,
130
+ sourceUrl: sourceLink(fact.provenance, sourceUrlTemplate),
131
+ });
132
+ }
133
+ return {
134
+ nodes: [...nodes.values()].sort((left, right) => columns.indexOf(left.column) - columns.indexOf(right.column) ||
135
+ left.cluster.localeCompare(right.cluster) ||
136
+ left.label.localeCompare(right.label) ||
137
+ left.id.localeCompare(right.id)),
138
+ edges,
139
+ };
140
+ }
141
+ function truncated(value, limit = 34) {
142
+ return value.length <= limit ? value : `${value.slice(0, limit - 1)}…`;
143
+ }
144
+ function positionedNodes(model) {
145
+ const margin = 36;
146
+ const columnWidth = 284;
147
+ const rowHeight = 78;
148
+ const headerHeight = 104;
149
+ const positioned = [];
150
+ let maxHeight = 320;
151
+ for (const [columnIndex, column] of columns.entries()) {
152
+ const members = model.nodes.filter((node) => node.column === column);
153
+ let y = headerHeight;
154
+ let cluster = '';
155
+ members.forEach((node) => {
156
+ if (node.cluster !== cluster) {
157
+ y += 24;
158
+ cluster = node.cluster;
159
+ }
160
+ positioned.push({
161
+ ...node,
162
+ x: margin + columnIndex * columnWidth,
163
+ y,
164
+ });
165
+ y += rowHeight;
166
+ });
167
+ maxHeight = Math.max(maxHeight, y);
168
+ }
169
+ return {
170
+ nodes: positioned,
171
+ width: margin * 2 + columnWidth * columns.length - 32,
172
+ height: Math.max(420, maxHeight + margin),
173
+ };
174
+ }
175
+ function edgePath(source, target, offset) {
176
+ const nodeWidth = 228;
177
+ const nodeHeight = 52;
178
+ const sourceOnLeft = source.x < target.x;
179
+ const sameColumn = source.x === target.x;
180
+ const startX = sameColumn ? source.x + nodeWidth : sourceOnLeft ? source.x + nodeWidth : source.x;
181
+ const endX = sameColumn ? target.x + nodeWidth : sourceOnLeft ? target.x : target.x + nodeWidth;
182
+ const startY = source.y + nodeHeight / 2;
183
+ const endY = target.y + nodeHeight / 2;
184
+ if (source.id === target.id) {
185
+ return `M ${startX} ${startY} C ${startX + 72} ${startY - 50}, ${startX + 72} ${startY + 50}, ${startX} ${startY + 6}`;
186
+ }
187
+ if (sameColumn) {
188
+ const controlX = startX + 48 + Math.abs(offset);
189
+ return `M ${startX} ${startY} C ${controlX} ${startY}, ${controlX} ${endY}, ${endX} ${endY}`;
190
+ }
191
+ const middleX = (startX + endX) / 2 + offset;
192
+ return `M ${startX} ${startY} C ${middleX} ${startY}, ${middleX} ${endY}, ${endX} ${endY}`;
193
+ }
194
+ function edgeLabelPosition(source, target, offset) {
195
+ const nodeWidth = 228;
196
+ const nodeHeight = 52;
197
+ if (source.id === target.id) {
198
+ return { x: source.x + nodeWidth + 58, y: source.y + nodeHeight / 2 - 28 };
199
+ }
200
+ if (source.x === target.x) {
201
+ return {
202
+ x: source.x + nodeWidth + 54 + Math.abs(offset),
203
+ y: (source.y + target.y + nodeHeight) / 2 - 4,
204
+ };
205
+ }
206
+ return {
207
+ x: (source.x + target.x + nodeWidth) / 2 + offset / 2,
208
+ y: (source.y + target.y + nodeHeight) / 2 - 5,
209
+ };
210
+ }
211
+ export function renderContextPackDot(pack) {
212
+ const model = visualModel(pack);
213
+ const lines = [
214
+ 'digraph ContextPack {',
215
+ ` graph [rankdir=LR, bgcolor="#f8fafc", label=${escapeDot(`Context Pack ${pack.snapshot.commitSha.slice(0, 12)} · ${model.edges.length} relationships`)}, labelloc=t, fontname="Arial"];`,
216
+ ' node [shape=box, style="rounded,filled", fontname="Arial", color="#334155"];',
217
+ ' edge [fontname="Arial", fontsize=9];',
218
+ ];
219
+ for (const column of columns) {
220
+ lines.push(` subgraph ${escapeDot(`cluster-${column}`)} {`);
221
+ lines.push(` label=${escapeDot(columnLabels[column])};`);
222
+ lines.push(` color=${escapeDot(columnColors[column])};`);
223
+ for (const node of model.nodes.filter((item) => item.column === column)) {
224
+ lines.push(` ${escapeDot(node.id)} [label=${escapeDot(`${node.label}\n${node.kind}`)}, fillcolor=${escapeDot(columnColors[column])}];`);
225
+ }
226
+ lines.push(' }');
227
+ }
228
+ for (const edge of model.edges) {
229
+ lines.push(` ${escapeDot(edge.from)} -> ${escapeDot(edge.to)} [label=${escapeDot(edge.label)}, color=${escapeDot(authorityColors[edge.authority])}, tooltip=${escapeDot(`${edge.provenance}; ${edge.detail}`)}];`);
230
+ }
231
+ lines.push('}', '');
232
+ return lines.join('\n');
233
+ }
234
+ export function renderContextPackSvg(pack) {
235
+ const model = visualModel(pack);
236
+ const layout = positionedNodes(model);
237
+ const positions = new Map(layout.nodes.map((node) => [node.id, node]));
238
+ const edgeMarkup = model.edges
239
+ .map((edge, index) => {
240
+ const source = positions.get(edge.from);
241
+ const target = positions.get(edge.to);
242
+ if (!source || !target)
243
+ return '';
244
+ const offset = ((index % 7) - 3) * 7;
245
+ const label = edgeLabelPosition(source, target, offset);
246
+ const search = [source.label, target.label, edge.label, edge.provenance, edge.detail].join(' ');
247
+ return `<g class="visual-edge authority-${edge.authority}" data-visual-edge="true" data-edge-id="${escapeXml(edge.id)}" data-authority="${edge.authority}" data-from="${escapeXml(source.id)}" data-to="${escapeXml(target.id)}" data-search="${escapeXml(search.toLowerCase())}">
248
+ <title>${escapeXml(`${source.label} —${edge.label}→ ${target.label}; ${edge.provenance}; ${edge.detail}`)}</title>
249
+ <path d="${edgePath(source, target, offset)}" marker-end="url(#arrow-${edge.authority})" />
250
+ <text class="edge-label" x="${label.x}" y="${label.y}" text-anchor="middle">${escapeXml(truncated(edge.label, 22))}</text>
251
+ </g>`;
252
+ })
253
+ .join('\n');
254
+ const nodeMarkup = layout.nodes
255
+ .map((node) => `<g class="visual-node column-${node.column}" data-visual-node="true" data-node-id="${escapeXml(node.id)}" role="button" tabindex="0" aria-label="${escapeXml(node.label)}" data-cluster="${escapeXml(node.cluster)}" data-search="${escapeXml(`${node.label} ${node.kind} ${node.id}`.toLowerCase())}" transform="translate(${node.x} ${node.y})">
256
+ <title>${escapeXml(`${node.label} (${node.kind}) · ${node.id}`)}</title>
257
+ <rect width="228" height="52" rx="9" />
258
+ <text class="node-label" x="12" y="22">${escapeXml(truncated(node.label))}</text>
259
+ <text class="node-kind" x="12" y="40">${escapeXml(node.kind)}</text>
260
+ </g>`)
261
+ .join('\n');
262
+ const headings = columns
263
+ .map((column, index) => `<text class="column-heading" x="${36 + index * 284}" y="76">${escapeXml(columnLabels[column])}</text>`)
264
+ .join('\n');
265
+ const clusterHeadings = layout.nodes.filter((node, index, nodes) => index === 0 ||
266
+ node.column !== nodes[index - 1].column || node.cluster !== nodes[index - 1].cluster)
267
+ .map((node) => `<text data-cluster-heading="${escapeXml(node.cluster)}" x="${node.x}" y="${node.y - 8}" class="node-kind">${escapeXml(truncated(node.cluster))}</text>`).join('');
268
+ const markers = Object.entries(authorityColors)
269
+ .map(([authority, color]) => `<marker id="arrow-${authority}" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path d="M0,0 L8,4 L0,8 z" fill="${color}" /></marker>`)
270
+ .join('');
271
+ return `<svg xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="graph-title graph-description" width="${layout.width}" height="${layout.height}" viewBox="0 0 ${layout.width} ${layout.height}">
272
+ <title id="graph-title">Context Pack ${escapeXml(pack.snapshot.commitSha.slice(0, 12))}</title>
273
+ <desc id="graph-description">${model.nodes.length} nodes and ${model.edges.length} bounded relationships from snapshot ${escapeXml(pack.snapshot.snapshotId)}</desc>
274
+ <defs>${markers}</defs>
275
+ <style>
276
+ svg { background: #f8fafc; color: #0f172a; font-family: Arial, sans-serif; }
277
+ .column-heading { fill: #334155; font-size: 15px; font-weight: 700; }
278
+ .visual-node rect { stroke: #64748b; stroke-width: 1.2; }
279
+ .column-intent rect { fill: ${columnColors.intent}; }
280
+ .column-contracts rect { fill: ${columnColors.contracts}; }
281
+ .column-implementation rect { fill: ${columnColors.implementation}; }
282
+ .column-evidence rect { fill: ${columnColors.evidence}; }
283
+ .node-label { fill: #0f172a; font-size: 12px; font-weight: 700; }
284
+ .node-kind { fill: #475569; font-size: 10px; }
285
+ .visual-edge { fill: none; opacity: .72; }
286
+ .visual-edge path { stroke-width: 1.4; }
287
+ .edge-label { fill: #334155; font-size: 9px; paint-order: stroke; stroke: #f8fafc; stroke-width: 3px; }
288
+ .authority-declared path { stroke: ${authorityColors.declared}; }
289
+ .authority-implemented path { stroke: ${authorityColors.implemented}; }
290
+ .authority-observed path { stroke: ${authorityColors.observed}; }
291
+ .authority-validated path { stroke: ${authorityColors.validated}; }
292
+ .authority-inferred path { stroke: ${authorityColors.inferred}; }
293
+ .authority-draft path { stroke: ${authorityColors.draft}; }
294
+ .is-hidden { display: none; }
295
+ </style>
296
+ <rect width="100%" height="100%" fill="#f8fafc" />
297
+ <text x="36" y="34" fill="#0f172a" font-size="19" font-weight="700">Context Pack ${escapeXml(pack.snapshot.commitSha.slice(0, 12))}</text>
298
+ <text x="36" y="53" fill="#64748b" font-size="11">${model.nodes.length} nodes · ${model.edges.length} relationships · bounded to ${pack.facts.length} facts</text>
299
+ ${headings}
300
+ ${clusterHeadings}
301
+ ${edgeMarkup}
302
+ ${nodeMarkup}
303
+ </svg>`;
304
+ }
305
+ function detailList(items, empty) {
306
+ return items.length
307
+ ? `<ul>${items.map((item) => `<li>${escapeXml(item)}</li>`).join('')}</ul>`
308
+ : `<p>${escapeXml(empty)}</p>`;
309
+ }
310
+ export function renderContextPackHtml(pack, sourceUrlTemplate) {
311
+ const model = visualModel(pack, sourceUrlTemplate);
312
+ const svg = renderContextPackSvg(pack);
313
+ const nodes = new Map(model.nodes.map((node) => [node.id, node]));
314
+ const relationships = model.edges
315
+ .map((edge) => {
316
+ const source = nodes.get(edge.from);
317
+ const target = nodes.get(edge.to);
318
+ const sourceLabel = source?.label ?? edge.from;
319
+ const targetLabel = target?.label ?? edge.to;
320
+ return `<tr data-relationship-row data-edge-id="${escapeXml(edge.id)}" data-authority="${edge.authority}" data-search="${escapeXml(`${sourceLabel} ${edge.from} ${targetLabel} ${edge.to} ${edge.label} ${edge.provenance} ${edge.detail}`.toLowerCase())}"><td><span class="layer layer-${edge.authority}">${edge.authority}</span></td><td><strong>${escapeXml(sourceLabel)}</strong><br /><code>${escapeXml(edge.from)}</code></td><td>${escapeXml(edge.label)}</td><td><strong>${escapeXml(targetLabel)}</strong><br /><code>${escapeXml(edge.to)}</code></td><td>${edge.sourceUrl ? `<a href="${escapeXml(edge.sourceUrl)}" target="_blank" rel="noopener noreferrer">${escapeXml(edge.provenance)}</a>` : escapeXml(edge.provenance)}</td></tr>`;
321
+ })
322
+ .join('');
323
+ const controls = Object.keys(authorityColors)
324
+ .map((authority) => `<label><input type="checkbox" data-layer="${authority}" checked /> ${authority}</label>`)
325
+ .join('');
326
+ return `<!doctype html>
327
+ <html lang="en">
328
+ <head>
329
+ <meta charset="utf-8" />
330
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
331
+ <meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; script-src 'unsafe-inline'" />
332
+ <title>Context Pack ${escapeXml(pack.snapshot.commitSha.slice(0, 12))}</title>
333
+ <style>
334
+ :root { color-scheme: light; font-family: Inter, ui-sans-serif, system-ui, sans-serif; color: #0f172a; background: #e2e8f0; }
335
+ * { box-sizing: border-box; }
336
+ body { margin: 0; }
337
+ header { background: #0f172a; color: #f8fafc; padding: 20px 24px; }
338
+ header h1 { font-size: 21px; margin: 0 0 6px; }
339
+ header p { color: #cbd5e1; margin: 0; font-size: 13px; }
340
+ .controls { align-items: center; background: #fff; border-bottom: 1px solid #cbd5e1; display: flex; flex-wrap: wrap; gap: 14px; padding: 12px 24px; position: sticky; top: 0; z-index: 2; }
341
+ .controls input[type="search"] { border: 1px solid #94a3b8; border-radius: 6px; min-width: 270px; padding: 7px 9px; }
342
+ .controls label { font-size: 13px; }
343
+ button { background: #334155; border: 0; border-radius: 6px; color: white; cursor: pointer; padding: 7px 11px; }
344
+ main { display: grid; gap: 16px; grid-template-columns: minmax(0, 1fr) 340px; padding: 16px; }
345
+ .panel { background: white; border: 1px solid #cbd5e1; border-radius: 9px; box-shadow: 0 1px 2px rgb(15 23 42 / 8%); }
346
+ .canvas { max-height: calc(100vh - 155px); overflow: auto; }
347
+ .canvas svg { display: block; }
348
+ aside { display: flex; flex-direction: column; gap: 12px; }
349
+ aside section, details { padding: 14px; }
350
+ h2 { font-size: 15px; margin: 0 0 9px; }
351
+ ul { margin: 6px 0; padding-left: 21px; }
352
+ li, p, summary { font-size: 12px; line-height: 1.45; }
353
+ .metric { background: #f1f5f9; border-radius: 6px; display: inline-block; font-size: 12px; margin: 2px; padding: 5px 7px; }
354
+ .relationships { grid-column: 1 / -1; overflow: auto; }
355
+ table { border-collapse: collapse; font-size: 11px; width: 100%; }
356
+ th, td { border-bottom: 1px solid #e2e8f0; padding: 7px; text-align: left; vertical-align: top; }
357
+ .layer { border-radius: 999px; color: white; display: inline-block; padding: 2px 7px; }
358
+ .layer-declared { background: ${authorityColors.declared}; }
359
+ .layer-implemented { background: ${authorityColors.implemented}; }
360
+ .layer-observed { background: ${authorityColors.observed}; }
361
+ .layer-validated { background: ${authorityColors.validated}; }
362
+ .layer-inferred { background: ${authorityColors.inferred}; }
363
+ .layer-draft { background: ${authorityColors.draft}; }
364
+ pre { font-size: 11px; overflow: auto; white-space: pre-wrap; }
365
+ .is-hidden { display: none !important; }
366
+ .visual-node { cursor: pointer; }
367
+ .visual-node.is-selected rect { stroke: #be123c; stroke-width: 3; }
368
+ .visual-edge.is-highlighted { opacity: 1; }
369
+ .visual-edge.is-highlighted path { stroke: #be123c; stroke-width: 4; }
370
+ #node-detail { overflow-wrap: anywhere; max-height: 440px; overflow: auto; }
371
+ #path-status { max-width: 450px; }
372
+ select { max-width: 260px; padding: 6px; }
373
+ .evidence-status { background: #fff7ed; padding: 12px 24px; overflow-wrap: anywhere; }
374
+ @media (max-width: 900px) { main { grid-template-columns: 1fr; } .relationships { grid-column: 1; } }
375
+ </style>
376
+ </head>
377
+ <body>
378
+ <header>
379
+ <h1>Context Pack ${escapeXml(pack.snapshot.commitSha.slice(0, 12))}</h1>
380
+ <p>Snapshot ${escapeXml(pack.snapshot.snapshotId)} · generated from the bounded Context Pack service</p>
381
+ </header>
382
+ <div class="controls" aria-label="Graph filters">
383
+ <input id="graph-search" type="search" placeholder="Search nodes, relations, or source paths" aria-label="Search graph" />
384
+ ${controls}
385
+ <label>Cluster <select id="graph-cluster"><option value="">All</option>${[...new Set(model.nodes.map(node => node.cluster))].sort().map(cluster => `<option value="${escapeXml(cluster)}">${escapeXml(cluster)}</option>`).join('')}</select></label>
386
+ <label><input type="checkbox" id="neighbors-only" /> Selected node and neighbors</label>
387
+ <label>Path target <select id="path-target">${model.nodes.map(node => `<option value="${escapeXml(node.id)}">${escapeXml(node.label)}</option>`).join('')}</select></label>
388
+ <button id="highlight-path" type="button">Highlight directed path</button>
389
+ <span id="path-status" aria-live="polite"></span>
390
+ <button id="reset-filters" type="button">Reset</button>
391
+ <span id="visible-count" aria-live="polite"></span>
392
+ </div>
393
+ <section class="evidence-status" aria-label="Evidence status">
394
+ <strong>${pack.snapshot.overlayDigest ? 'DRAFT overlay' : pack.evidenceState?.stale ? 'HISTORICAL evidence' : pack.evidenceState ? 'Saved evidence at current commit' : 'Saved evidence — current checkout not assessed'}</strong>
395
+ <p>Configuration: ${escapeXml(pack.snapshot.configurationDigest ?? 'unknown')}; ${pack.evidenceState ? (pack.evidenceState.configurationMatches ? 'matches current configuration' : 'configuration mismatch') : 'configuration match unknown'}.
396
+ Scope: ${escapeXml(pack.snapshot.scope?.join(', ') ?? 'unknown')}.
397
+ Excluded working paths: ${pack.evidenceState?.excludedWorkingChanges.length ?? 'unknown'}.
398
+ ${pack.snapshot.overlayDigest ? `Overlay freshness: ${escapeXml(pack.evidenceState?.overlayFreshness ?? 'unknown')}.` : ''}
399
+ This view does not validate the current diff.</p>
400
+ <p>${pack.exploration ? `${pack.exploration.returned}/${pack.exploration.reachableFacts} reachable facts on this page; max depth ${pack.exploration.maxDepth}; ${pack.exploration.truncated ? 'TRUNCATED' : 'not truncated within this scope'}${pack.exploration.depthLimitReached ? '; depth limit reached — rerun with greater --max-depth' : ''}` : 'Query completeness metadata unavailable.'}</p>
401
+ ${pack.exploration?.continuation ? `<details><summary>Continue agent/CLI query with this token</summary><code>${escapeXml(pack.exploration.continuation)}</code></details>` : ''}
402
+ ${pack.evidenceState?.excludedWorkingChanges.length ? `<details><summary>Excluded working changes</summary>${detailList(pack.evidenceState.excludedWorkingChanges, '')}</details>` : ''}
403
+ ${pack.evidenceState?.changedSinceCapture?.length ? `<details><summary>Changed since capture</summary>${detailList(pack.evidenceState.changedSinceCapture, '')}</details>` : ''}
404
+ </section>
405
+ <main>
406
+ <section class="panel canvas" aria-label="Context graph">${svg}</section>
407
+ <aside>
408
+ <section class="panel"><h2>Node and source evidence</h2><div id="node-detail">Select a node to inspect its cited relationships.</div></section>
409
+ <section class="panel"><h2>Runtime references</h2>${detailList(pack.runtimeEvidence.map(reference => `${reference.serviceName}: ${reference.operation ?? 'observation'} · ${reference.sourcePath} · ${reference.observedAt ?? 'time unknown'}`), 'None in this page.')}<p>Edges are only the facts returned by the agent query. References do not create extra relationships.</p></section>
410
+ <section class="panel">
411
+ <h2>Coverage</h2>
412
+ ${Object.entries(pack.coverage.counts)
413
+ .map(([lane, count]) => `<span class="metric">${escapeXml(lane)}: ${count}</span>`)
414
+ .join('')}
415
+ ${detailList(pack.coverage.gaps, 'No Context Pack lane gaps detected.')}
416
+ </section>
417
+ <section class="panel">
418
+ <h2>Governing invariants</h2>
419
+ ${detailList(pack.invariants.map((item) => `${item.status}: ${item.message}`), 'No governing invariants matched this view.')}
420
+ </section>
421
+ <section class="panel">
422
+ <h2>Scoped diagnostics</h2>
423
+ ${detailList(diagnosticSummary(pack), 'None recorded.')}
424
+ </section>
425
+ <section class="panel">
426
+ <h2>Suggested files</h2>
427
+ ${detailList(pack.suggestedFiles, 'No files suggested.')}
428
+ </section>
429
+ </aside>
430
+ <details class="panel relationships">
431
+ <summary><strong>Relationships (${model.edges.length})</strong></summary>
432
+ <table>
433
+ <thead><tr><th>Authority</th><th>From</th><th>Relationship</th><th>To</th><th>Source</th></tr></thead>
434
+ <tbody>${relationships}</tbody>
435
+ </table>
436
+ </details>
437
+ <details class="panel relationships">
438
+ <summary><strong>Rendered Context Pack</strong></summary>
439
+ <pre>${escapeXml(pack.markdown)}</pre>
440
+ </details>
441
+ </main>
442
+ <script id="context-pack" type="application/json">${jsonForHtml(pack)}</script>
443
+ <script id="visual-model" type="application/json">${jsonForHtml(model)}</script>
444
+ <script>${viewerScript()}</script>
445
+ </body>
446
+ </html>
447
+ `;
448
+ }
449
+ export function resolveVisualizationFormat(outputPath, format) {
450
+ const candidate = format?.toLowerCase() ?? extname(outputPath).slice(1).toLowerCase();
451
+ if (candidate === 'dot' || candidate === 'html' || candidate === 'svg')
452
+ return candidate;
453
+ throw new Error('Visualization format must be html, dot, or svg (or use a matching extension)');
454
+ }
455
+ export function renderContextPackVisualization(pack, format, sourceUrlTemplate) {
456
+ if (format === 'dot')
457
+ return renderContextPackDot(pack);
458
+ if (format === 'svg')
459
+ return renderContextPackSvg(pack);
460
+ return renderContextPackHtml(pack, sourceUrlTemplate);
461
+ }
462
+ export async function writeContextPackVisualization(pack, options) {
463
+ if (!options.outputPath.trim())
464
+ throw new Error('Visualization output path must not be empty');
465
+ const format = resolveVisualizationFormat(options.outputPath, options.format);
466
+ const outputPath = resolve(options.outputPath);
467
+ await mkdir(dirname(outputPath), { recursive: true });
468
+ try {
469
+ await writeFile(outputPath, renderContextPackVisualization(pack, format, options.sourceUrlTemplate), {
470
+ encoding: 'utf8',
471
+ flag: options.force ? 'w' : 'wx',
472
+ });
473
+ }
474
+ catch (error) {
475
+ if (typeof error === 'object' && error !== null && 'code' in error && error.code === 'EEXIST') {
476
+ throw new Error(`Visualization output already exists: ${outputPath}; pass --force to replace it`);
477
+ }
478
+ throw error;
479
+ }
480
+ return {
481
+ outputPath,
482
+ format,
483
+ snapshotId: pack.snapshot.snapshotId,
484
+ commitSha: pack.snapshot.commitSha,
485
+ factCount: pack.facts.length,
486
+ runtimeEvidenceCount: pack.runtimeEvidence.length,
487
+ };
488
+ }
489
+ export async function visualizeContextPack(queryService, request, options) {
490
+ return writeContextPackVisualization(await queryService.scan(request), options);
491
+ }
@@ -0,0 +1,16 @@
1
+ # ADR 001: preserve the baseline before changing the graph
2
+
3
+ Status: accepted for the extraction milestone.
4
+
5
+ Keep the neutral core as a separate workspace package and the toolkit at repository root.
6
+ Retain the tested Codex, SCIP, semantic citation and ontology code initially. The source's
7
+ product adapter is design evidence only; an independent consumer needs no product dependency.
8
+ Use an explicit manifest rather than importing source history or the monorepo workspace.
9
+
10
+ The first gate is deterministic build/test plus installation of packed artifacts into an
11
+ independent Git repository with real TypeScript indexing and MCP initialization. Real model
12
+ and openCAESAR integration are distinct checks and must not be reported as passing when unrun.
13
+
14
+ Graph migration, snapshot lifecycle and governance changes have separate acceptance criteria.
15
+ No model-generated statement becomes approved intent by virtue of extraction or compilation.
16
+ An evidence record describes what its source supports, not a universal product truth.
@@ -0,0 +1,21 @@
1
+ # ADR 002: reads and refresh are separate operations
2
+
3
+ Status: implemented, with bounded validation.
4
+
5
+ `impact.scan` reads immutable snapshots and never starts a model, compiler or indexer.
6
+ Explicit hydration produces committed evidence in disposable archive directories, without
7
+ registering worktrees or requiring the active checkout to be clean. Symlinks/submodules and
8
+ archives larger than 128 MiB are currently rejected; they are not silently partially indexed.
9
+ The archive path requires `git` and `tar`; only Linux is validated.
10
+
11
+ Snapshot IDs bind configuration and semantic scope in addition to commit and runtime revisions.
12
+ User-scope cache heads are isolated by checkout identity. Snapshots publish via an atomic
13
+ create-if-absent operation; head pointers replace atomically only after a complete snapshot
14
+ exists. Concurrent conflicting content fails instead of replacing a winner.
15
+
16
+ Queries identify source commit, scope, configuration match and excluded working changes.
17
+ Final checks reject dirty working trees, old commits, configuration mismatch and draft overlays.
18
+ Historical evidence remains useful context; it never silently validates a current diff.
19
+
20
+ Draft overlays are a separate milestone. No inference about unchanged runtime behavior is
21
+ authorized merely because a committed snapshot is available.
@@ -0,0 +1,23 @@
1
+ # ADR 003: conservative draft implementation overlays
2
+
3
+ Status: implemented as a bounded submilestone.
4
+
5
+ `context-graph overlay --base-snapshot <id> --agent <name>` captures configured changed source
6
+ bytes against checkout HEAD. Identity binds base SHA, base snapshot digest and captured content
7
+ digest, including deletions. Code is reindexed in an independent temporary Git repository;
8
+ the active checkout and its index/branch remain unchanged.
9
+
10
+ All implementation relationships are rebuilt, so consumer links cannot retain deleted/renamed
11
+ definitions. Affected document assertions and links are invalidated. No model is called and no
12
+ new approved intent is invented. Remaining invariant results become unknown and runtime observations
13
+ are omitted. Code facts are explicitly draft, including in the viewer.
14
+
15
+ Overlay heads are separate from committed heads and isolated by checkout and agent name.
16
+ Select the returned snapshot ID explicitly for querying. Overlay snapshots never satisfy final
17
+ committed-evidence checks.
18
+
19
+ Bounds: 500 changed paths, 1 MiB per source file, source/configuration scope must match the base.
20
+ Changes outside configured source roots are reported as excluded. This first overlay implementation
21
+ reindexes implementation in full; semantic re-extraction and incremental SCIP are not claimed.
22
+ Git rename detection is not an identity authority: the old path is invalidated and captured bytes
23
+ are indexed at the new path. Arbitrary adapter implementation facts are conservatively dropped.
@@ -0,0 +1,37 @@
1
+ # ADR 004: a lossless RDF dataset shared by queries and validation
2
+
3
+ Status: implemented.
4
+
5
+ Immutable JSON snapshots remain the storage envelope. Each cited fact projects losslessly into
6
+ an RDF named graph, with reified provenance, authority, confidence, source digest, scope and
7
+ configuration metadata. A structured record preserves compatibility fields. The traversal
8
+ projection is read through SPARQL and has field-for-field equivalence tests, including order.
9
+ The viewer consumes the resulting Context Pack, not an independently extracted graph.
10
+
11
+ Comunica executes local SELECT queries. Parsed queries reject remote SERVICE/dataset access
12
+ and updates. `rdf-validate-shacl` checks structural integrity and identity-kind consistency.
13
+ Validation failures are extraction gaps, not proof of product-policy violations.
14
+ The direct command is `context-graph sparql --query-file query.rq --snapshot <id>`.
15
+ `context-graph validate --snapshot <id>` emits the SHACL report.
16
+
17
+ `impact.scan` defaults to 32 traversal hops, allows 1–128, and reports the reached scope,
18
+ depth exhaustion, result truncation and snapshot/query-bound continuation tokens. Page through
19
+ results using `--continuation`. Depth exhaustion requires an explicit larger `--max-depth`;
20
+ the result never represents that partial traversal as complete.
21
+ The graph projection itself has a fail-closed 100,000-fact bound.
22
+
23
+ Only paths explicitly listed in `approvedIntentSources` receive `sourceAuthority: declared`.
24
+ Their extracted assertions remain `authority: inferred` and `extractionReview: unreviewed`.
25
+ The intent lane can include such evidence, but labels and coverage notes require checking the
26
+ citation; source approval never approves a model assertion or creates a hard rule. Legacy Codex
27
+ records without authority also remain inferred. Approved deterministic policy is separate.
28
+
29
+ The public pipeline example has six asynchronous stages, shared message types and a runtime
30
+ test. Deterministic tests parse the example prose through a test provider, run real SCIP,
31
+ hydrate/link the sources, and retrieve downstream implementations/contracts/tests. This proves
32
+ the extraction and linkage plumbing, not live model comprehension.
33
+
34
+ RDF validation does not activate OWL reasoning or make compiled OML the query backend.
35
+ That compiler decision is a separate milestone. SPARQLJS 3.7.4 is pinned for syntax inspection
36
+ and is also a Comunica transitive dependency; upstream marks it deprecated, so replacement
37
+ should be evaluated on the next query-engine upgrade.
@@ -0,0 +1,37 @@
1
+ # ADR 005: direct RDF by default; formal OML remains optional
2
+
3
+ Status: implemented for the standalone core.
4
+
5
+ The source used openCAESAR to compile deterministic OML, but impact queries operated on separate
6
+ JSON facts and invariant checks were TypeScript. No active OWL reasoner supplied the baseline's
7
+ query answers or CI decisions. Requiring Docker/JDK/Gradle on every hydration imposed a substantial
8
+ startup and maintenance cost without providing active reasoning to that workflow.
9
+
10
+ The default compiler now writes the asserted semantic relationships directly as RDF. It preserves
11
+ validated assertions, citations in the unified record dataset, deterministic OML artifacts and
12
+ cache identities. A parity test checks the same cited assertion in deterministic OML and asserted
13
+ RDF, and the public pipeline hydrates/reuses evidence using the real direct-RDF compiler.
14
+ This is assertion-level parity, not a claim of equivalent OWL entailments.
15
+
16
+ The explicit `ontology.backend: "opencaesar"` path and generic OML assets remain available.
17
+ `examples/generic-typescript/context-graph.oml.config.json` retains the pinned compiler image.
18
+ Formal compilation is optional and not required for core tests, query access or installation.
19
+ Live OML/OWL parity remains unverified in this environment. An active OWL reasoner is not shipped.
20
+
21
+ ## Governance experiment
22
+
23
+ The canonical policy maps real source paths into domains, modules, layers, adapters and contract
24
+ owners. dependency-cruiser extracts imports and detects dependency/cycle violations; those facts
25
+ also become the RDF graph. A SPARQL `consumes+` query derives a transitive domain → adapter → UI
26
+ dependency and detects a forbidden indirect dependency in the negative fixture. Approved contract
27
+ ownership checks also reject contradictory owners.
28
+
29
+ This is a useful explicit formal inference over actual dependency facts. Direct RDF, SHACL and
30
+ deterministic policy checks provide it without an OWL runtime. Formal OML may still be useful for
31
+ external ontology interchange or richer class reasoning, but neither is needed to justify a
32
+ mandatory compiler here.
33
+
34
+ ESLint provides size/complexity signals; jscpd provides textual duplication signals. They are
35
+ advisory and require semantic review. Only approved rules with approval references become gates.
36
+ Reports separate rule violations, extraction gaps, infrastructure failures and advisory findings.
37
+ Baselines and expiring scoped exceptions require explicit entries and never advance automatically.
@@ -0,0 +1,15 @@
1
+ # ADR 006: explicit providers with lazy loading and bounded execution
2
+
3
+ The original Codex orchestration and structured output remain the first concrete adapter.
4
+ The neutral extractor contract is now exported and enforced at runtime for all providers.
5
+ Module adapters are trusted code and require an explicit identity; loading them only on
6
+ extraction preserves read access when providers are absent.
7
+
8
+ CLI subprocesses have cancellation, timeouts, bounded output acceptance and narrow retries.
9
+ Errors report safe status metadata instead of private stderr. Hydration limits source scope
10
+ before invoking a provider, stops new work after failures, and reuses immutable artifacts.
11
+ Reasoning settings now participate in Codex cache identity.
12
+
13
+ These changes provide an extension seam, not a claim of interoperability with arbitrary models.
14
+ Additional real adapters require their own live and deterministic tests. The current cloud's
15
+ read-only filesystem blocks a live Codex invocation; this limitation does not block graph queries.