@thinkingsage/kanon 0.8.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 (199) hide show
  1. package/CHANGELOG.md +410 -0
  2. package/LICENSE +21 -0
  3. package/README.md +168 -0
  4. package/bridge/mcp-server.cjs +14171 -0
  5. package/package.json +98 -0
  6. package/src/adapters/capabilities.ts +178 -0
  7. package/src/adapters/claude-code.ts +110 -0
  8. package/src/adapters/cline.ts +98 -0
  9. package/src/adapters/codex.ts +173 -0
  10. package/src/adapters/copilot.ts +106 -0
  11. package/src/adapters/cursor.ts +97 -0
  12. package/src/adapters/degradation.ts +95 -0
  13. package/src/adapters/index.ts +324 -0
  14. package/src/adapters/kiro-frontmatter.ts +139 -0
  15. package/src/adapters/kiro-inclusion.ts +86 -0
  16. package/src/adapters/kiro.ts +412 -0
  17. package/src/adapters/qdeveloper.ts +115 -0
  18. package/src/adapters/types.ts +81 -0
  19. package/src/adapters/windsurf.ts +96 -0
  20. package/src/admin.ts +283 -0
  21. package/src/asset-conventions.ts +118 -0
  22. package/src/attribution-backfill.ts +319 -0
  23. package/src/attribution-report.ts +95 -0
  24. package/src/attribution.ts +239 -0
  25. package/src/backends/github.ts +194 -0
  26. package/src/backends/http.ts +122 -0
  27. package/src/backends/index.ts +39 -0
  28. package/src/backends/local.ts +47 -0
  29. package/src/backends/s3.ts +157 -0
  30. package/src/backends/types.ts +59 -0
  31. package/src/base-cache.ts +270 -0
  32. package/src/browse-ui.ts +3754 -0
  33. package/src/browse.ts +1038 -0
  34. package/src/build.ts +1108 -0
  35. package/src/catalog.ts +204 -0
  36. package/src/cli-deprecated.ts +29 -0
  37. package/src/cli.ts +773 -0
  38. package/src/collection-admin.ts +287 -0
  39. package/src/collection-builder.ts +464 -0
  40. package/src/collections.ts +116 -0
  41. package/src/compatibility.ts +105 -0
  42. package/src/config.ts +743 -0
  43. package/src/eval/rubrics/kiro-progressive-steering.ts +841 -0
  44. package/src/eval.ts +1169 -0
  45. package/src/file-writer.ts +61 -0
  46. package/src/format-registry.ts +141 -0
  47. package/src/guild/auto-updater.ts +163 -0
  48. package/src/guild/backend-resolver.ts +49 -0
  49. package/src/guild/cli.ts +592 -0
  50. package/src/guild/collection-expander.ts +47 -0
  51. package/src/guild/global-cache.ts +247 -0
  52. package/src/guild/hook-generator.ts +100 -0
  53. package/src/guild/manifest.ts +154 -0
  54. package/src/guild/path-utils.ts +12 -0
  55. package/src/guild/sync.ts +622 -0
  56. package/src/guild/version-resolver.ts +42 -0
  57. package/src/help/metadata.ts +445 -0
  58. package/src/help/renderer.ts +265 -0
  59. package/src/help/typo-suggester.ts +25 -0
  60. package/src/hooks/expression.ts +493 -0
  61. package/src/hooks/pipeline.ts +141 -0
  62. package/src/import.ts +773 -0
  63. package/src/importers/claude-code.ts +134 -0
  64. package/src/importers/cline.ts +103 -0
  65. package/src/importers/codex.ts +140 -0
  66. package/src/importers/copilot.ts +103 -0
  67. package/src/importers/cursor.ts +105 -0
  68. package/src/importers/index.ts +390 -0
  69. package/src/importers/kiro.ts +110 -0
  70. package/src/importers/qdeveloper.ts +103 -0
  71. package/src/importers/types.ts +54 -0
  72. package/src/importers/windsurf.ts +104 -0
  73. package/src/install.ts +1005 -0
  74. package/src/manifest-admin.ts +306 -0
  75. package/src/mcp-bridge.ts +240 -0
  76. package/src/mutation/delta.ts +50 -0
  77. package/src/mutation/history.ts +66 -0
  78. package/src/mutation/operators.ts +524 -0
  79. package/src/mutation/runner.ts +332 -0
  80. package/src/new.ts +106 -0
  81. package/src/outcomes/collision.ts +127 -0
  82. package/src/outcomes/normalize.ts +208 -0
  83. package/src/outcomes/registry.ts +173 -0
  84. package/src/parser.ts +446 -0
  85. package/src/provenance-backfill-cli.ts +319 -0
  86. package/src/provenance-backfill.ts +520 -0
  87. package/src/publish.ts +354 -0
  88. package/src/reconcile-orchestrator.ts +502 -0
  89. package/src/reconcile-report-renderer.ts +176 -0
  90. package/src/resolve-body.ts +15 -0
  91. package/src/rosetta/builtins/compatibility-profiles.ts +297 -0
  92. package/src/rosetta/builtins/contracts.ts +1033 -0
  93. package/src/rosetta/builtins/pretty-printers/claude-code-native.ts +122 -0
  94. package/src/rosetta/builtins/pretty-printers/cline-native.ts +50 -0
  95. package/src/rosetta/builtins/pretty-printers/codex-native.ts +127 -0
  96. package/src/rosetta/builtins/pretty-printers/copilot-native.ts +50 -0
  97. package/src/rosetta/builtins/pretty-printers/cursor-native.ts +50 -0
  98. package/src/rosetta/builtins/pretty-printers/index.ts +81 -0
  99. package/src/rosetta/builtins/pretty-printers/kiro-native.ts +166 -0
  100. package/src/rosetta/builtins/pretty-printers/kiro-power.ts +108 -0
  101. package/src/rosetta/builtins/pretty-printers/kiro-skill.ts +88 -0
  102. package/src/rosetta/builtins/pretty-printers/qdeveloper-native.ts +51 -0
  103. package/src/rosetta/builtins/pretty-printers/superpowers.ts +97 -0
  104. package/src/rosetta/builtins/pretty-printers/windsurf-native.ts +50 -0
  105. package/src/rosetta/builtins/sources/claude-code-native.ts +348 -0
  106. package/src/rosetta/builtins/sources/cline-native.ts +176 -0
  107. package/src/rosetta/builtins/sources/codex-native.ts +343 -0
  108. package/src/rosetta/builtins/sources/copilot-native.ts +178 -0
  109. package/src/rosetta/builtins/sources/cursor-native.ts +176 -0
  110. package/src/rosetta/builtins/sources/index.ts +95 -0
  111. package/src/rosetta/builtins/sources/kiro-native.ts +462 -0
  112. package/src/rosetta/builtins/sources/kiro-power.ts +285 -0
  113. package/src/rosetta/builtins/sources/kiro-skill.ts +230 -0
  114. package/src/rosetta/builtins/sources/qdeveloper-native.ts +181 -0
  115. package/src/rosetta/builtins/sources/superpowers.ts +240 -0
  116. package/src/rosetta/builtins/sources/windsurf-native.ts +176 -0
  117. package/src/rosetta/builtins/targets/claude-code.ts +181 -0
  118. package/src/rosetta/builtins/targets/cline.ts +87 -0
  119. package/src/rosetta/builtins/targets/codex.ts +226 -0
  120. package/src/rosetta/builtins/targets/copilot.ts +103 -0
  121. package/src/rosetta/builtins/targets/cursor.ts +87 -0
  122. package/src/rosetta/builtins/targets/index.ts +60 -0
  123. package/src/rosetta/builtins/targets/kiro.ts +278 -0
  124. package/src/rosetta/builtins/targets/qdeveloper.ts +103 -0
  125. package/src/rosetta/builtins/targets/windsurf.ts +87 -0
  126. package/src/rosetta/canonical.ts +729 -0
  127. package/src/rosetta/compatibility.ts +432 -0
  128. package/src/rosetta/contracts.ts +329 -0
  129. package/src/rosetta/detector.ts +724 -0
  130. package/src/rosetta/diagnostics.ts +630 -0
  131. package/src/rosetta/engine-bootstrap.ts +103 -0
  132. package/src/rosetta/engine.ts +744 -0
  133. package/src/rosetta/index.ts +381 -0
  134. package/src/rosetta/inspection.ts +530 -0
  135. package/src/rosetta/plan.ts +448 -0
  136. package/src/rosetta/provenance-digest.ts +369 -0
  137. package/src/rosetta/reconcile.ts +812 -0
  138. package/src/rosetta/redaction.ts +467 -0
  139. package/src/rosetta/registry.ts +712 -0
  140. package/src/rosetta/renderers.ts +571 -0
  141. package/src/rosetta/request-guard.ts +335 -0
  142. package/src/rosetta/resolution.ts +419 -0
  143. package/src/rosetta/source-accounting.ts +233 -0
  144. package/src/rosetta/templates.ts +129 -0
  145. package/src/rosetta-cli.ts +717 -0
  146. package/src/rosetta-docs-generator.ts +793 -0
  147. package/src/rosetta-profiles-cli.ts +367 -0
  148. package/src/schemas.ts +1712 -0
  149. package/src/spec-coordination.ts +1141 -0
  150. package/src/temper.ts +747 -0
  151. package/src/template-bundle-loader.ts +312 -0
  152. package/src/template-engine.ts +53 -0
  153. package/src/translation-application-policy.ts +496 -0
  154. package/src/translation-orchestrator.ts +1013 -0
  155. package/src/translation-plan-applier.ts +473 -0
  156. package/src/tutorial.ts +305 -0
  157. package/src/validate.ts +1093 -0
  158. package/src/versioning.ts +553 -0
  159. package/src/wizard.ts +660 -0
  160. package/src/workspace.ts +237 -0
  161. package/templates/eval-contexts/claude-code.md.njk +6 -0
  162. package/templates/eval-contexts/cline.md.njk +6 -0
  163. package/templates/eval-contexts/copilot.md.njk +6 -0
  164. package/templates/eval-contexts/cursor.md.njk +6 -0
  165. package/templates/eval-contexts/kiro.md.njk +10 -0
  166. package/templates/eval-contexts/qdeveloper.md.njk +6 -0
  167. package/templates/eval-contexts/windsurf.md.njk +6 -0
  168. package/templates/harness-adapters/_base/attribution-footer.md.njk +17 -0
  169. package/templates/harness-adapters/_base/base.md.njk +16 -0
  170. package/templates/harness-adapters/claude-code/claude.md.njk +1 -0
  171. package/templates/harness-adapters/claude-code/mcp.json.njk +1 -0
  172. package/templates/harness-adapters/claude-code/settings.json.njk +1 -0
  173. package/templates/harness-adapters/claude-code/skill-library-index.md.njk +13 -0
  174. package/templates/harness-adapters/claude-code/skill.md.njk +19 -0
  175. package/templates/harness-adapters/cline/hook.sh.njk +4 -0
  176. package/templates/harness-adapters/cline/mcp.json.njk +1 -0
  177. package/templates/harness-adapters/cline/rule.md.njk +1 -0
  178. package/templates/harness-adapters/codex/agents-md.md.njk +6 -0
  179. package/templates/harness-adapters/codex/agents-pointer.md.njk +16 -0
  180. package/templates/harness-adapters/codex/skill.md.njk +27 -0
  181. package/templates/harness-adapters/copilot/agents.md.njk +1 -0
  182. package/templates/harness-adapters/copilot/instructions.md.njk +1 -0
  183. package/templates/harness-adapters/copilot/scoped.md.njk +6 -0
  184. package/templates/harness-adapters/cursor/mcp.json.njk +1 -0
  185. package/templates/harness-adapters/cursor/rule.md.njk +6 -0
  186. package/templates/harness-adapters/kiro/hook.json.njk +1 -0
  187. package/templates/harness-adapters/kiro/mcp.json.njk +1 -0
  188. package/templates/harness-adapters/kiro/power-steering.md.njk +3 -0
  189. package/templates/harness-adapters/kiro/power.md.njk +12 -0
  190. package/templates/harness-adapters/kiro/steering.md.njk +16 -0
  191. package/templates/harness-adapters/qdeveloper/agent.md.njk +1 -0
  192. package/templates/harness-adapters/qdeveloper/mcp.json.njk +1 -0
  193. package/templates/harness-adapters/qdeveloper/rule.md.njk +1 -0
  194. package/templates/harness-adapters/windsurf/mcp.json.njk +1 -0
  195. package/templates/harness-adapters/windsurf/rule.md.njk +1 -0
  196. package/templates/harness-adapters/windsurf/workflow.md.njk +1 -0
  197. package/templates/knowledge/hooks.yaml.njk +4 -0
  198. package/templates/knowledge/knowledge.md.njk +53 -0
  199. package/templates/knowledge/mcp-servers.yaml.njk +2 -0
@@ -0,0 +1,530 @@
1
+ /**
2
+ * Rosetta Stone — Deterministic Inspection Models
3
+ *
4
+ * Builds a deterministic InspectionReport from resolved translation data.
5
+ * The report projects formats/variants, detection evidence, versions,
6
+ * canonical summaries, defaults, normalizations, option origins,
7
+ * compatibility/degradation counts, diagnostics, plan paths, collision data,
8
+ * and preview status.
9
+ *
10
+ * CONSTRAINTS:
11
+ * - NO filesystem, process, clock, random, Git, or network imports
12
+ * - Pure functions only
13
+ * - All output is deterministic — same input always produces same report
14
+ * - JSON-serializable (no functions, symbols, circular refs)
15
+ *
16
+ * Requirements: 7.8, 9.3, 9.6, 9.7, 9.8, 9.9
17
+ */
18
+
19
+ import type {
20
+ DegradationRecord,
21
+ DetectionCandidate,
22
+ Direction,
23
+ InspectionReportEnvelope,
24
+ JsonValue,
25
+ LifecycleStatus,
26
+ TranslationDiagnostic,
27
+ TranslationPlan,
28
+ } from "../schemas";
29
+ import { InspectionReportEnvelopeSchema } from "../schemas";
30
+ import { codePointCompare, compareDiagnostics, deepFreeze } from "./contracts";
31
+
32
+ // ═══════════════════════════════════════════════════════════════════════════════
33
+ // Sub-types
34
+ // ═══════════════════════════════════════════════════════════════════════════════
35
+
36
+ /** Summarized request details */
37
+ export interface RequestSummary {
38
+ readonly direction: Direction;
39
+ readonly sourceFormat?: string;
40
+ readonly targetFormat?: string;
41
+ readonly strict: boolean;
42
+ readonly dryRun: boolean;
43
+ }
44
+
45
+ /** Format detection results */
46
+ export interface DetectionSummary {
47
+ readonly candidates: readonly DetectionCandidateSummary[];
48
+ readonly selectedFormatId?: string;
49
+ readonly ambiguous: boolean;
50
+ }
51
+
52
+ /** Condensed detection candidate for the report */
53
+ export interface DetectionCandidateSummary {
54
+ readonly formatId: string;
55
+ readonly confidence: number;
56
+ readonly evidenceCount: number;
57
+ }
58
+
59
+ /** Selected format, variant, contract version, canonical version range, lifecycle */
60
+ export interface FormatSummary {
61
+ readonly formatId: string;
62
+ readonly variant?: string;
63
+ readonly contractVersion: string;
64
+ readonly canonicalVersionRange?: string;
65
+ readonly lifecycle: LifecycleStatus;
66
+ }
67
+
68
+ /** Canonical schema version, artifact summary */
69
+ export interface CanonicalSummary {
70
+ readonly schemaVersion: string;
71
+ readonly artifactName?: string;
72
+ readonly type?: string;
73
+ readonly harnesses: readonly string[];
74
+ readonly hookCount: number;
75
+ readonly mcpServerCount: number;
76
+ readonly workflowCount: number;
77
+ readonly bodyOverrideCount: number;
78
+ }
79
+
80
+ /** Resolved options with origins */
81
+ export interface OptionsSummary {
82
+ readonly effective: Readonly<Record<string, JsonValue>>;
83
+ readonly origins: Readonly<
84
+ Record<string, "explicit" | "profile" | "canonical" | "contract-default">
85
+ >;
86
+ readonly defaults: Readonly<Record<string, JsonValue>>;
87
+ }
88
+
89
+ /** Compatibility and degradation summary */
90
+ export interface CompatibilitySummary {
91
+ readonly fullCount: number;
92
+ readonly partialCount: number;
93
+ readonly noneCount: number;
94
+ readonly degradations: Readonly<Record<string, string>>;
95
+ readonly strictPromoted: boolean;
96
+ }
97
+
98
+ /** Diagnostics grouped by severity, sorted deterministically */
99
+ export interface DiagnosticsSummary {
100
+ readonly errorCount: number;
101
+ readonly warningCount: number;
102
+ readonly infoCount: number;
103
+ readonly blocking: readonly string[];
104
+ readonly diagnostics: readonly TranslationDiagnostic[];
105
+ }
106
+
107
+ /** Output file paths, operation counts, application state */
108
+ export interface PlanSummary {
109
+ readonly outputFilePaths: readonly string[];
110
+ readonly operationCount: number;
111
+ readonly applicationState: "eligible" | "policy-required" | "withheld";
112
+ readonly policyDiagnosticCodes: readonly string[];
113
+ }
114
+
115
+ /** Collision policy and outcomes */
116
+ export interface CollisionSummary {
117
+ readonly policy: string;
118
+ readonly collisionCount: number;
119
+ readonly outcomes: Readonly<Record<string, string>>;
120
+ }
121
+
122
+ /** Content preview availability */
123
+ export interface PreviewStatus {
124
+ readonly available: boolean;
125
+ readonly reason?: string;
126
+ }
127
+
128
+ // ═══════════════════════════════════════════════════════════════════════════════
129
+ // InspectionReport — the deterministic report model
130
+ // ═══════════════════════════════════════════════════════════════════════════════
131
+
132
+ /** The deterministic inspection report model */
133
+ export interface InspectionReport {
134
+ readonly schemaVersion: "1.0";
135
+ readonly timestamp?: string;
136
+ readonly request: RequestSummary;
137
+ readonly detection?: DetectionSummary;
138
+ readonly format: FormatSummary;
139
+ readonly canonical: CanonicalSummary;
140
+ readonly options: OptionsSummary;
141
+ readonly compatibility: CompatibilitySummary;
142
+ readonly diagnostics: DiagnosticsSummary;
143
+ readonly plan: PlanSummary;
144
+ readonly collision: CollisionSummary;
145
+ readonly preview: PreviewStatus;
146
+ }
147
+
148
+ // ═══════════════════════════════════════════════════════════════════════════════
149
+ // InspectionContext — inputs needed to build the report
150
+ // ═══════════════════════════════════════════════════════════════════════════════
151
+
152
+ /** All resolved translation data needed to build an InspectionReport */
153
+ export interface InspectionContext {
154
+ /** Resolved translation request info */
155
+ readonly request: {
156
+ readonly direction: Direction;
157
+ readonly sourceFormat?: string;
158
+ readonly targetFormat?: string;
159
+ readonly strict: boolean;
160
+ readonly dryRun: boolean;
161
+ };
162
+ /** Detection result (candidates, selection) */
163
+ readonly detection?: {
164
+ readonly candidates: readonly DetectionCandidate[];
165
+ readonly selectedFormatId?: string;
166
+ readonly ambiguous: boolean;
167
+ };
168
+ /** Resolved format contract */
169
+ readonly format: {
170
+ readonly formatId: string;
171
+ readonly contractVersion: string;
172
+ readonly canonicalVersionRange?: string;
173
+ readonly lifecycle: LifecycleStatus;
174
+ };
175
+ /** Resolved variant */
176
+ readonly variant?: {
177
+ readonly id: string;
178
+ };
179
+ /** Canonical artifact (if available) */
180
+ readonly artifact?: {
181
+ readonly schemaVersion: string;
182
+ readonly name?: string;
183
+ readonly type?: string;
184
+ readonly harnesses?: readonly string[];
185
+ readonly hookCount?: number;
186
+ readonly mcpServerCount?: number;
187
+ readonly workflowCount?: number;
188
+ readonly bodyOverrideCount?: number;
189
+ };
190
+ /** Resolved effective options with origins */
191
+ readonly options: {
192
+ readonly effective: Readonly<Record<string, JsonValue>>;
193
+ readonly origins: Readonly<
194
+ Record<string, "explicit" | "profile" | "canonical" | "contract-default">
195
+ >;
196
+ readonly defaults: Readonly<Record<string, JsonValue>>;
197
+ };
198
+ /** Compatibility evaluation result */
199
+ readonly compatibility?: {
200
+ readonly fullCount: number;
201
+ readonly partialCount: number;
202
+ readonly noneCount: number;
203
+ readonly degradations: readonly DegradationRecord[];
204
+ readonly strictPromoted: boolean;
205
+ };
206
+ /** All collected diagnostics */
207
+ readonly diagnostics: readonly TranslationDiagnostic[];
208
+ /** Translation plan (if produced) */
209
+ readonly plan?: TranslationPlan;
210
+ /** The collision policy used */
211
+ readonly collisionPolicy?: string;
212
+ /** Path→outcome map for collisions */
213
+ readonly collisionOutcomes?: Readonly<Record<string, string>>;
214
+ /** Whether content preview is safe */
215
+ readonly previewAvailable: boolean;
216
+ /** Reason string for preview unavailability */
217
+ readonly previewUnavailableReason?: string;
218
+ }
219
+
220
+ // ═══════════════════════════════════════════════════════════════════════════════
221
+ // buildInspectionReport — constructs a report from translation results
222
+ // ═══════════════════════════════════════════════════════════════════════════════
223
+
224
+ /**
225
+ * Constructs an InspectionReport from resolved translation data.
226
+ *
227
+ * All sections are projected deterministically: sorted keys use
228
+ * code-point ordering, arrays are sorted deterministically, and the
229
+ * returned report is deeply frozen.
230
+ */
231
+ export function buildInspectionReport(
232
+ context: InspectionContext,
233
+ ): Readonly<InspectionReport> {
234
+ const request: RequestSummary = {
235
+ direction: context.request.direction,
236
+ ...(context.request.sourceFormat !== undefined && {
237
+ sourceFormat: context.request.sourceFormat,
238
+ }),
239
+ ...(context.request.targetFormat !== undefined && {
240
+ targetFormat: context.request.targetFormat,
241
+ }),
242
+ strict: context.request.strict,
243
+ dryRun: context.request.dryRun,
244
+ };
245
+
246
+ const detection = buildDetectionSummary(context.detection);
247
+ const format = buildFormatSummary(context.format, context.variant);
248
+ const canonical = buildCanonicalSummary(context.artifact);
249
+ const options = buildOptionsSummary(context.options);
250
+ const compatibility = buildCompatibilitySummary(context.compatibility);
251
+ const diagnostics = buildDiagnosticsSummary(context.diagnostics);
252
+ const plan = buildPlanSummary(context.plan);
253
+ const collision = buildCollisionSummary(
254
+ context.collisionPolicy,
255
+ context.collisionOutcomes,
256
+ );
257
+ const preview: PreviewStatus = {
258
+ available: context.previewAvailable,
259
+ ...(context.previewUnavailableReason !== undefined && {
260
+ reason: context.previewUnavailableReason,
261
+ }),
262
+ };
263
+
264
+ const report: InspectionReport = {
265
+ schemaVersion: "1.0",
266
+ request,
267
+ ...(detection !== undefined && { detection }),
268
+ format,
269
+ canonical,
270
+ options,
271
+ compatibility,
272
+ diagnostics,
273
+ plan,
274
+ collision,
275
+ preview,
276
+ };
277
+
278
+ return deepFreeze(report) as Readonly<InspectionReport>;
279
+ }
280
+
281
+ // ═══════════════════════════════════════════════════════════════════════════════
282
+ // Internal projection helpers
283
+ // ═══════════════════════════════════════════════════════════════════════════════
284
+
285
+ function buildDetectionSummary(
286
+ detection: InspectionContext["detection"],
287
+ ): DetectionSummary | undefined {
288
+ if (!detection) return undefined;
289
+
290
+ // Sort candidates by confidence descending, then formatId ascending (code-point)
291
+ const sortedCandidates = [...detection.candidates].sort((a, b) => {
292
+ const confDiff = b.confidence - a.confidence;
293
+ if (confDiff !== 0) return confDiff;
294
+ return codePointCompare(a.formatId, b.formatId);
295
+ });
296
+
297
+ const candidates: DetectionCandidateSummary[] = sortedCandidates.map((c) => ({
298
+ formatId: c.formatId,
299
+ confidence: c.confidence,
300
+ evidenceCount: c.evidence.length,
301
+ }));
302
+
303
+ return {
304
+ candidates,
305
+ ...(detection.selectedFormatId !== undefined && {
306
+ selectedFormatId: detection.selectedFormatId,
307
+ }),
308
+ ambiguous: detection.ambiguous,
309
+ };
310
+ }
311
+
312
+ function buildFormatSummary(
313
+ format: InspectionContext["format"],
314
+ variant: InspectionContext["variant"],
315
+ ): FormatSummary {
316
+ return {
317
+ formatId: format.formatId,
318
+ ...(variant?.id !== undefined && { variant: variant.id }),
319
+ contractVersion: format.contractVersion,
320
+ ...(format.canonicalVersionRange !== undefined && {
321
+ canonicalVersionRange: format.canonicalVersionRange,
322
+ }),
323
+ lifecycle: format.lifecycle,
324
+ };
325
+ }
326
+
327
+ function buildCanonicalSummary(
328
+ artifact: InspectionContext["artifact"],
329
+ ): CanonicalSummary {
330
+ if (!artifact) {
331
+ return {
332
+ schemaVersion: "1.0",
333
+ harnesses: [],
334
+ hookCount: 0,
335
+ mcpServerCount: 0,
336
+ workflowCount: 0,
337
+ bodyOverrideCount: 0,
338
+ };
339
+ }
340
+
341
+ // Sort harnesses deterministically
342
+ const harnesses = [...(artifact.harnesses ?? [])].sort(codePointCompare);
343
+
344
+ return {
345
+ schemaVersion: artifact.schemaVersion,
346
+ ...(artifact.name !== undefined && { artifactName: artifact.name }),
347
+ ...(artifact.type !== undefined && { type: artifact.type }),
348
+ harnesses,
349
+ hookCount: artifact.hookCount ?? 0,
350
+ mcpServerCount: artifact.mcpServerCount ?? 0,
351
+ workflowCount: artifact.workflowCount ?? 0,
352
+ bodyOverrideCount: artifact.bodyOverrideCount ?? 0,
353
+ };
354
+ }
355
+
356
+ function buildOptionsSummary(
357
+ options: InspectionContext["options"],
358
+ ): OptionsSummary {
359
+ // Sort effective options keys by code-point order
360
+ const effectiveKeys = Object.keys(options.effective).sort(codePointCompare);
361
+ const effective: Record<string, JsonValue> = {};
362
+ for (const key of effectiveKeys) {
363
+ effective[key] = options.effective[key];
364
+ }
365
+
366
+ // Sort origins keys by code-point order
367
+ const originsKeys = Object.keys(options.origins).sort(codePointCompare);
368
+ const origins: Record<
369
+ string,
370
+ "explicit" | "profile" | "canonical" | "contract-default"
371
+ > = {};
372
+ for (const key of originsKeys) {
373
+ origins[key] = options.origins[key];
374
+ }
375
+
376
+ // Sort defaults keys by code-point order
377
+ const defaultsKeys = Object.keys(options.defaults).sort(codePointCompare);
378
+ const defaults: Record<string, JsonValue> = {};
379
+ for (const key of defaultsKeys) {
380
+ defaults[key] = options.defaults[key];
381
+ }
382
+
383
+ return { effective, origins, defaults };
384
+ }
385
+
386
+ function buildCompatibilitySummary(
387
+ compat: InspectionContext["compatibility"],
388
+ ): CompatibilitySummary {
389
+ if (!compat) {
390
+ return {
391
+ fullCount: 0,
392
+ partialCount: 0,
393
+ noneCount: 0,
394
+ degradations: {},
395
+ strictPromoted: false,
396
+ };
397
+ }
398
+
399
+ // Build degradations map: capability → action, sorted by capability (code-point)
400
+ const degradationEntries = [...compat.degradations]
401
+ .map((d) => [d.capability, d.action] as const)
402
+ .sort((a, b) => codePointCompare(a[0], b[0]));
403
+
404
+ const degradations: Record<string, string> = {};
405
+ for (const [capability, action] of degradationEntries) {
406
+ degradations[capability] = action;
407
+ }
408
+
409
+ return {
410
+ fullCount: compat.fullCount,
411
+ partialCount: compat.partialCount,
412
+ noneCount: compat.noneCount,
413
+ degradations,
414
+ strictPromoted: compat.strictPromoted,
415
+ };
416
+ }
417
+
418
+ function buildDiagnosticsSummary(
419
+ diagnostics: readonly TranslationDiagnostic[],
420
+ ): DiagnosticsSummary {
421
+ // Sort diagnostics deterministically
422
+ const sorted = [...diagnostics].sort(compareDiagnostics);
423
+
424
+ let errorCount = 0;
425
+ let warningCount = 0;
426
+ let infoCount = 0;
427
+ const blocking: string[] = [];
428
+
429
+ for (const d of sorted) {
430
+ switch (d.severity) {
431
+ case "error":
432
+ errorCount++;
433
+ break;
434
+ case "warning":
435
+ warningCount++;
436
+ break;
437
+ case "info":
438
+ infoCount++;
439
+ break;
440
+ }
441
+ if (d.blocking && !blocking.includes(d.code)) {
442
+ blocking.push(d.code);
443
+ }
444
+ }
445
+
446
+ // Sort blocking codes by code-point order
447
+ blocking.sort(codePointCompare);
448
+
449
+ return {
450
+ errorCount,
451
+ warningCount,
452
+ infoCount,
453
+ blocking,
454
+ diagnostics: sorted,
455
+ };
456
+ }
457
+
458
+ function buildPlanSummary(plan: TranslationPlan | undefined): PlanSummary {
459
+ if (!plan) {
460
+ return {
461
+ outputFilePaths: [],
462
+ operationCount: 0,
463
+ applicationState: "withheld",
464
+ policyDiagnosticCodes: [],
465
+ };
466
+ }
467
+
468
+ // Sort output file paths deterministically by code-point order
469
+ const outputFilePaths = plan.outputFiles
470
+ .map((f) => f.relativePath)
471
+ .sort(codePointCompare);
472
+
473
+ // Sort policy diagnostic codes deterministically
474
+ const policyDiagnosticCodes = [...plan.policyDiagnosticCodes].sort(
475
+ codePointCompare,
476
+ );
477
+
478
+ return {
479
+ outputFilePaths,
480
+ operationCount: plan.operations.length,
481
+ applicationState: plan.applicationState,
482
+ policyDiagnosticCodes,
483
+ };
484
+ }
485
+
486
+ function buildCollisionSummary(
487
+ policy: string | undefined,
488
+ outcomes: Readonly<Record<string, string>> | undefined,
489
+ ): CollisionSummary {
490
+ const resolvedPolicy = policy ?? "none";
491
+ const resolvedOutcomes = outcomes ?? {};
492
+
493
+ // Sort outcome keys by code-point order
494
+ const sortedKeys = Object.keys(resolvedOutcomes).sort(codePointCompare);
495
+ const sortedOutcomes: Record<string, string> = {};
496
+ for (const key of sortedKeys) {
497
+ sortedOutcomes[key] = resolvedOutcomes[key];
498
+ }
499
+
500
+ return {
501
+ policy: resolvedPolicy,
502
+ collisionCount: sortedKeys.length,
503
+ outcomes: sortedOutcomes,
504
+ };
505
+ }
506
+
507
+ // ═══════════════════════════════════════════════════════════════════════════════
508
+ // validateInspectionReport — validates against InspectionReportEnvelopeSchema
509
+ // ═══════════════════════════════════════════════════════════════════════════════
510
+
511
+ /**
512
+ * Validates an inspection report envelope against the InspectionReportEnvelopeSchema.
513
+ *
514
+ * Returns a result object with success status and either the validated data
515
+ * or an array of error messages.
516
+ */
517
+ export function validateInspectionReport(
518
+ report: unknown,
519
+ ):
520
+ | { success: true; data: InspectionReportEnvelope }
521
+ | { success: false; errors: string[] } {
522
+ const result = InspectionReportEnvelopeSchema.safeParse(report);
523
+ if (result.success) {
524
+ return { success: true, data: result.data };
525
+ }
526
+ const errors = result.error.issues.map(
527
+ (issue) => `${issue.path.join(".")}: ${issue.message}`,
528
+ );
529
+ return { success: false, errors };
530
+ }