@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,329 @@
1
+ /**
2
+ * Rosetta Stone — Pure Contracts and Deterministic Normalization Utilities
3
+ *
4
+ * This module re-exports schema-derived interfaces from `../schemas.ts` and provides
5
+ * deterministic normalization utilities for the Rosetta Stone translation engine.
6
+ *
7
+ * CONSTRAINTS:
8
+ * - NO filesystem, process, clock, random, Git, or network imports
9
+ * - ALL public data shapes remain owned by `src/schemas.ts` — only re-export, never redefine
10
+ * - Pure functions only
11
+ *
12
+ * Requirements: 5.6, 12.1, 12.2, 12.3, 12.6, 12.7
13
+ */
14
+
15
+ // ═══════════════════════════════════════════════════════════════════════════════
16
+ // Re-export schema-derived types (owned by ../schemas.ts)
17
+ // ═══════════════════════════════════════════════════════════════════════════════
18
+
19
+ export type {
20
+ AppliedDefault,
21
+ AppliedNormalization,
22
+ CanonicalCapability,
23
+ CanonicalDiagnosticLocation,
24
+ CanonicalOutputOptions,
25
+ CanonicalSchemaVersion,
26
+ CanonicalVersionRange,
27
+ ContractVersion,
28
+ DegradationDetail,
29
+ DegradationRecord,
30
+ DetectionCandidate,
31
+ DetectionContract,
32
+ DetectionEvidence,
33
+ DetectionRule,
34
+ DetectionRuleKind,
35
+ DiagnosticsEnvelope,
36
+ Direction,
37
+ FormatContract,
38
+ FormatIdentifier,
39
+ FormatOptionDefinition,
40
+ FormatSecurityPolicy,
41
+ FormatSelection,
42
+ InboundTranslationRequest,
43
+ InspectionReportEnvelope,
44
+ JsonValue,
45
+ LifecycleMetadata,
46
+ LifecycleStatus,
47
+ NormalizationRule,
48
+ NormalizedRelativePath,
49
+ OutboundTranslationRequest,
50
+ OutputFile,
51
+ PathConvention,
52
+ PlanOperation,
53
+ ProvenanceRecord,
54
+ RegistryFailure,
55
+ ResolvedFormatSummary,
56
+ RosettaCompatibilityEntry,
57
+ RosettaCompatibilityProfile,
58
+ RosettaSeverity,
59
+ SchemaReference,
60
+ SourceDiagnosticLocation,
61
+ SourceDocument,
62
+ SourceLocation,
63
+ TranscodeTranslationRequest,
64
+ TranslationDiagnostic,
65
+ TranslationPhase,
66
+ TranslationPlan,
67
+ TranslationProfile,
68
+ TranslationRequest,
69
+ TranslationResult,
70
+ VariantContract,
71
+ } from "../schemas";
72
+
73
+ // ═══════════════════════════════════════════════════════════════════════════════
74
+ // Translation Phase Ordering
75
+ // ═══════════════════════════════════════════════════════════════════════════════
76
+
77
+ /**
78
+ * Canonical ordering of translation phases. Used for diagnostic sorting.
79
+ * Matches the enum order in TranslationPhaseSchema.
80
+ */
81
+ export const TRANSLATION_PHASE_ORDER: readonly string[] = [
82
+ "request",
83
+ "registry",
84
+ "detection",
85
+ "source-validation",
86
+ "source-translation",
87
+ "canonical-validation",
88
+ "compatibility",
89
+ "target-translation",
90
+ "plan-validation",
91
+ "redaction",
92
+ ] as const;
93
+
94
+ // ═══════════════════════════════════════════════════════════════════════════════
95
+ // Immutable Context Type Helpers
96
+ // ═══════════════════════════════════════════════════════════════════════════════
97
+
98
+ import type { JsonValue } from "../schemas";
99
+
100
+ /** Immutable record type for readonly translation contexts */
101
+ export type ImmutableRecord<K extends string, V> = Readonly<Record<K, V>>;
102
+
103
+ /** Immutable context for translation operations */
104
+ export type ImmutableContext = Readonly<Record<string, JsonValue>>;
105
+
106
+ // ═══════════════════════════════════════════════════════════════════════════════
107
+ // Code-Point Ordering Comparator
108
+ // ═══════════════════════════════════════════════════════════════════════════════
109
+
110
+ /**
111
+ * Compare two strings by Unicode code points (not locale).
112
+ * Uses simple char-by-char comparison with code point values.
113
+ *
114
+ * This ensures deterministic ordering independent of locale settings.
115
+ */
116
+ export function codePointCompare(a: string, b: string): number {
117
+ const minLen = Math.min(a.length, b.length);
118
+ for (let i = 0; i < minLen; i++) {
119
+ const aCode = a.codePointAt(i) as number;
120
+ const bCode = b.codePointAt(i) as number;
121
+ if (aCode !== bCode) {
122
+ return aCode - bCode;
123
+ }
124
+ }
125
+ return a.length - b.length;
126
+ }
127
+
128
+ // ═══════════════════════════════════════════════════════════════════════════════
129
+ // Stable JSON Serialization
130
+ // ═══════════════════════════════════════════════════════════════════════════════
131
+
132
+ /**
133
+ * Recursively sort object keys by code-point order and serialize to JSON
134
+ * with 2-space indentation. Produces deterministic output regardless of
135
+ * object key insertion order.
136
+ */
137
+ export function stableJsonStringify(value: unknown): string {
138
+ return JSON.stringify(sortKeysDeep(value), null, 2);
139
+ }
140
+
141
+ /**
142
+ * Recursively sort all object keys by code-point order.
143
+ * Arrays are preserved as-is (order is semantic).
144
+ */
145
+ function sortKeysDeep(value: unknown): unknown {
146
+ if (value === null || value === undefined) {
147
+ return value;
148
+ }
149
+ if (Array.isArray(value)) {
150
+ return value.map(sortKeysDeep);
151
+ }
152
+ if (typeof value === "object") {
153
+ const sorted: Record<string, unknown> = {};
154
+ const keys = Object.keys(value as Record<string, unknown>).sort(
155
+ codePointCompare,
156
+ );
157
+ for (const key of keys) {
158
+ sorted[key] = sortKeysDeep((value as Record<string, unknown>)[key]);
159
+ }
160
+ return sorted;
161
+ }
162
+ return value;
163
+ }
164
+
165
+ // ═══════════════════════════════════════════════════════════════════════════════
166
+ // Stable YAML Key Ordering
167
+ // ═══════════════════════════════════════════════════════════════════════════════
168
+
169
+ /**
170
+ * Sort keys by a priority table first (preserving priority order),
171
+ * then remaining keys by code-point order.
172
+ *
173
+ * @param keys - The keys to sort
174
+ * @param priorityOrder - Optional ordered list of priority keys
175
+ * @returns Sorted keys array
176
+ */
177
+ export function yamlKeyOrder(
178
+ keys: string[],
179
+ priorityOrder?: string[],
180
+ ): string[] {
181
+ if (!priorityOrder || priorityOrder.length === 0) {
182
+ return [...keys].sort(codePointCompare);
183
+ }
184
+
185
+ const prioritySet = new Set(priorityOrder);
186
+ const priorityKeys: string[] = [];
187
+ const remainingKeys: string[] = [];
188
+
189
+ for (const key of keys) {
190
+ if (prioritySet.has(key)) {
191
+ priorityKeys.push(key);
192
+ } else {
193
+ remainingKeys.push(key);
194
+ }
195
+ }
196
+
197
+ // Sort priority keys by their position in the priority table
198
+ priorityKeys.sort(
199
+ (a, b) => priorityOrder.indexOf(a) - priorityOrder.indexOf(b),
200
+ );
201
+
202
+ // Sort remaining keys by code-point order
203
+ remainingKeys.sort(codePointCompare);
204
+
205
+ return [...priorityKeys, ...remainingKeys];
206
+ }
207
+
208
+ // ═══════════════════════════════════════════════════════════════════════════════
209
+ // Canonical Comparison Normalization
210
+ // ═══════════════════════════════════════════════════════════════════════════════
211
+
212
+ /**
213
+ * Deep-clone and normalize a value for canonical comparison.
214
+ * Recursively sorts object keys by code-point order.
215
+ * Arrays are preserved in their original order (semantic ordering).
216
+ *
217
+ * Used to compare two values for semantic equivalence regardless of
218
+ * object key insertion order.
219
+ */
220
+ export function normalizeForComparison(value: unknown): unknown {
221
+ return sortKeysDeep(structuredClone(value));
222
+ }
223
+
224
+ // ═══════════════════════════════════════════════════════════════════════════════
225
+ // Deep Freeze Utility
226
+ // ═══════════════════════════════════════════════════════════════════════════════
227
+
228
+ /**
229
+ * Recursively Object.freeze all nested objects and arrays.
230
+ * Returns the same reference, now deeply frozen.
231
+ */
232
+ export function deepFreeze<T>(obj: T): Readonly<T> {
233
+ if (obj === null || obj === undefined || typeof obj !== "object") {
234
+ return obj;
235
+ }
236
+
237
+ Object.freeze(obj);
238
+
239
+ const entries = Object.getOwnPropertyNames(obj);
240
+ for (const key of entries) {
241
+ const value = (obj as Record<string, unknown>)[key];
242
+ if (
243
+ value !== null &&
244
+ value !== undefined &&
245
+ typeof value === "object" &&
246
+ !Object.isFrozen(value)
247
+ ) {
248
+ deepFreeze(value);
249
+ }
250
+ }
251
+
252
+ return obj;
253
+ }
254
+
255
+ // ═══════════════════════════════════════════════════════════════════════════════
256
+ // Diagnostic Sorting Comparator
257
+ // ═══════════════════════════════════════════════════════════════════════════════
258
+
259
+ import type { TranslationDiagnostic } from "../schemas";
260
+
261
+ /** Severity rank: error = 0, warning = 1, info = 2 */
262
+ const SEVERITY_RANK: Record<string, number> = {
263
+ error: 0,
264
+ warning: 1,
265
+ info: 2,
266
+ };
267
+
268
+ /**
269
+ * Compare two translation diagnostics for deterministic sorting.
270
+ *
271
+ * Sort order:
272
+ * 1. Severity (error > warning > info)
273
+ * 2. Phase order (per TRANSLATION_PHASE_ORDER)
274
+ * 3. Path (source location path, code-point order)
275
+ * 4. Location (line, then column)
276
+ * 5. Code (diagnostic code, code-point order)
277
+ * 6. Format identifier (code-point order)
278
+ */
279
+ export function compareDiagnostics(
280
+ a: TranslationDiagnostic,
281
+ b: TranslationDiagnostic,
282
+ ): number {
283
+ // 1. Severity
284
+ const sevA = SEVERITY_RANK[a.severity] ?? 3;
285
+ const sevB = SEVERITY_RANK[b.severity] ?? 3;
286
+ if (sevA !== sevB) return sevA - sevB;
287
+
288
+ // 2. Phase order
289
+ const phaseA = TRANSLATION_PHASE_ORDER.indexOf(a.phase);
290
+ const phaseB = TRANSLATION_PHASE_ORDER.indexOf(b.phase);
291
+ if (phaseA !== phaseB) return phaseA - phaseB;
292
+
293
+ // 3. Path (from source location)
294
+ const pathA = a.source?.path ?? "";
295
+ const pathB = b.source?.path ?? "";
296
+ const pathCmp = codePointCompare(pathA, pathB);
297
+ if (pathCmp !== 0) return pathCmp;
298
+
299
+ // 4. Location (line, then column)
300
+ const lineA = a.source?.line ?? 0;
301
+ const lineB = b.source?.line ?? 0;
302
+ if (lineA !== lineB) return lineA - lineB;
303
+ const colA = a.source?.column ?? 0;
304
+ const colB = b.source?.column ?? 0;
305
+ if (colA !== colB) return colA - colB;
306
+
307
+ // 5. Code
308
+ const codeCmp = codePointCompare(a.code, b.code);
309
+ if (codeCmp !== 0) return codeCmp;
310
+
311
+ // 6. Format identifier
312
+ const fmtA = a.formatId ?? "";
313
+ const fmtB = b.formatId ?? "";
314
+ return codePointCompare(fmtA, fmtB);
315
+ }
316
+
317
+ // ═══════════════════════════════════════════════════════════════════════════════
318
+ // Plan File Sorting Comparator
319
+ // ═══════════════════════════════════════════════════════════════════════════════
320
+
321
+ /**
322
+ * Compare plan files by normalized relative path using code-point ordering.
323
+ */
324
+ export function comparePlanFiles(
325
+ a: { relativePath: string },
326
+ b: { relativePath: string },
327
+ ): number {
328
+ return codePointCompare(a.relativePath, b.relativePath);
329
+ }