@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,432 @@
1
+ /**
2
+ * Rosetta Stone — Compatibility Evaluator
3
+ *
4
+ * Computes effective compatibility profiles from format contracts and variants,
5
+ * identifies used capabilities from an artifact, emits degradation diagnostics
6
+ * per affected canonical field group, aggregates counts, and promotes
7
+ * compatibility/loss diagnostics uniformly in strict mode.
8
+ *
9
+ * All functions are pure. No filesystem, process, clock, random, Git,
10
+ * or network imports.
11
+ *
12
+ * Requirements: 7.1, 7.2, 7.3, 7.4, 7.5, 7.6, 7.7, 7.8
13
+ */
14
+
15
+ import type {
16
+ CanonicalCapability,
17
+ DegradationDetail,
18
+ DegradationRecord,
19
+ DegradationStrategy,
20
+ FormatContract,
21
+ KnowledgeArtifact,
22
+ RosettaCompatibilityEntry,
23
+ TranslationDiagnostic,
24
+ VariantContract,
25
+ } from "../schemas";
26
+ import { CanonicalCapabilitySchema } from "../schemas";
27
+ import { codePointCompare } from "./contracts";
28
+ import { createDiagnostic } from "./diagnostics";
29
+
30
+ // ═══════════════════════════════════════════════════════════════════════════════
31
+ // Types
32
+ // ═══════════════════════════════════════════════════════════════════════════════
33
+
34
+ /**
35
+ * A complete, frozen record of all canonical capabilities mapped to entries.
36
+ * Every capability in `CanonicalCapabilitySchema` is present.
37
+ */
38
+ export type EffectiveCompatibilityProfile = Readonly<
39
+ Record<CanonicalCapability, RosettaCompatibilityEntry>
40
+ >;
41
+
42
+ /**
43
+ * Captures a single degradation action: capability, action, affected count,
44
+ * semantic change description, and remediation text.
45
+ */
46
+ export interface DegradationRecordOutput {
47
+ readonly capability: CanonicalCapability;
48
+ readonly action: DegradationStrategy;
49
+ readonly affectedValueCount: number;
50
+ readonly canonicalPaths: readonly string[];
51
+ readonly expectedSemanticChange?: string;
52
+ readonly remediation: string;
53
+ }
54
+
55
+ /**
56
+ * The result of evaluating compatibility for a set of used capabilities
57
+ * against an effective profile.
58
+ */
59
+ export interface CompatibilityEvaluation {
60
+ readonly diagnostics: readonly TranslationDiagnostic[];
61
+ readonly degradations: readonly DegradationRecord[];
62
+ readonly affectedCounts: Readonly<Record<string, number>>;
63
+ }
64
+
65
+ // ═══════════════════════════════════════════════════════════════════════════════
66
+ // Constants
67
+ // ═══════════════════════════════════════════════════════════════════════════════
68
+
69
+ /** All canonical capabilities in schema-declared order */
70
+ const ALL_CAPABILITIES: readonly CanonicalCapability[] =
71
+ CanonicalCapabilitySchema.options;
72
+
73
+ /**
74
+ * Mapping from CanonicalCapability to the canonical field paths
75
+ * that constitute that capability's "field group" in KnowledgeArtifact.
76
+ */
77
+ const CAPABILITY_FIELD_GROUPS: Readonly<
78
+ Record<CanonicalCapability, readonly string[]>
79
+ > = {
80
+ frontmatter: ["frontmatter"],
81
+ body: ["body"],
82
+ hooks: ["hooks"],
83
+ "mcp-servers": ["mcpServers"],
84
+ workflows: ["workflows"],
85
+ "body-overrides": ["bodyOverrides"],
86
+ "extra-fields": ["extraFields"],
87
+ "path-scoping": ["frontmatter.inclusion", "frontmatter.file_patterns"],
88
+ "toggleable-rules": ["frontmatter.inclusion"],
89
+ "file-match-inclusion": ["frontmatter.file_patterns"],
90
+ "system-prompt-merging": ["body"],
91
+ // Asset-type capabilities map to frontmatter.type
92
+ skill: ["frontmatter.type"],
93
+ power: ["frontmatter.type"],
94
+ rule: ["frontmatter.type"],
95
+ workflow: ["frontmatter.type"],
96
+ agent: ["frontmatter.type"],
97
+ prompt: ["frontmatter.type"],
98
+ template: ["frontmatter.type"],
99
+ "reference-pack": ["frontmatter.type"],
100
+ };
101
+
102
+ // ═══════════════════════════════════════════════════════════════════════════════
103
+ // resolveEffectiveProfile
104
+ // ═══════════════════════════════════════════════════════════════════════════════
105
+
106
+ /**
107
+ * Resolves a complete EffectiveCompatibilityProfile from a contract's base
108
+ * compatibility plus any variant overrides.
109
+ *
110
+ * @param contract - The format contract with base compatibility profile
111
+ * @param variant - Optional variant contract that may override profile entries
112
+ * @returns The complete, frozen effective profile
113
+ * @throws Error if the resolved profile is incomplete (not all capabilities covered)
114
+ */
115
+ export function resolveEffectiveProfile(
116
+ contract: FormatContract,
117
+ _variant?: VariantContract,
118
+ ): EffectiveCompatibilityProfile {
119
+ // Start with the contract's base compatibility profile
120
+ const baseProfile = contract.compatibility;
121
+
122
+ // Verify completeness of the base profile
123
+ const missingCapabilities: CanonicalCapability[] = [];
124
+ for (const cap of ALL_CAPABILITIES) {
125
+ if (!(cap in baseProfile)) {
126
+ missingCapabilities.push(cap);
127
+ }
128
+ }
129
+
130
+ if (missingCapabilities.length > 0) {
131
+ throw new Error(
132
+ `Incomplete compatibility profile for format "${contract.id}": ` +
133
+ `missing capabilities: ${missingCapabilities.sort(codePointCompare).join(", ")}`,
134
+ );
135
+ }
136
+
137
+ // The base profile is already validated as complete by the schema.
138
+ // Variant overrides (if any) are applied on top.
139
+ // Note: VariantContract doesn't currently have compatibility overrides,
140
+ // so the effective profile equals the base profile. This function exists
141
+ // as the extension point for when variant compatibility is added.
142
+ const effectiveProfile = { ...baseProfile } as Record<
143
+ CanonicalCapability,
144
+ RosettaCompatibilityEntry
145
+ >;
146
+
147
+ // Freeze and return
148
+ return Object.freeze(effectiveProfile) as EffectiveCompatibilityProfile;
149
+ }
150
+
151
+ // ═══════════════════════════════════════════════════════════════════════════════
152
+ // identifyUsedCapabilities
153
+ // ═══════════════════════════════════════════════════════════════════════════════
154
+
155
+ /**
156
+ * Examines a KnowledgeArtifact and returns which canonical capabilities are
157
+ * actually used. A capability is "used" if the artifact has non-empty/non-default
158
+ * values for the fields in that capability's field group.
159
+ *
160
+ * @param artifact - The canonical artifact to inspect
161
+ * @returns Set of capabilities that are used by the artifact
162
+ */
163
+ export function identifyUsedCapabilities(
164
+ artifact: KnowledgeArtifact,
165
+ ): ReadonlySet<CanonicalCapability> {
166
+ const used = new Set<CanonicalCapability>();
167
+
168
+ // frontmatter is always used (every artifact has frontmatter)
169
+ used.add("frontmatter");
170
+
171
+ // body is used if non-empty
172
+ if (artifact.body.trim().length > 0) {
173
+ used.add("body");
174
+ // system-prompt-merging is relevant when body content exists
175
+ used.add("system-prompt-merging");
176
+ }
177
+
178
+ // hooks is used if the array is non-empty
179
+ if (artifact.hooks.length > 0) {
180
+ used.add("hooks");
181
+ }
182
+
183
+ // mcp-servers is used if the array is non-empty
184
+ if (artifact.mcpServers.length > 0) {
185
+ used.add("mcp-servers");
186
+ }
187
+
188
+ // workflows is used if the array is non-empty
189
+ if (artifact.workflows.length > 0) {
190
+ used.add("workflows");
191
+ }
192
+
193
+ // body-overrides is used if there are any harness-specific body overrides
194
+ if (Object.keys(artifact.bodyOverrides).length > 0) {
195
+ used.add("body-overrides");
196
+ }
197
+
198
+ // extra-fields is used if there are any extra fields
199
+ if (Object.keys(artifact.extraFields).length > 0) {
200
+ used.add("extra-fields");
201
+ }
202
+
203
+ // path-scoping is used if inclusion mode implies path scoping
204
+ const inclusion = artifact.frontmatter.inclusion;
205
+ if (inclusion === "fileMatch" || inclusion === "auto") {
206
+ used.add("path-scoping");
207
+ }
208
+
209
+ // toggleable-rules is used when inclusion is manual (user can toggle)
210
+ if (inclusion === "manual") {
211
+ used.add("toggleable-rules");
212
+ }
213
+
214
+ // file-match-inclusion is used if file_patterns are specified
215
+ const filePatterns = artifact.frontmatter.file_patterns;
216
+ if (filePatterns && filePatterns.length > 0) {
217
+ used.add("file-match-inclusion");
218
+ }
219
+
220
+ // Asset-type capability: the artifact's type is a used capability
221
+ const artifactType = artifact.frontmatter.type;
222
+ if (ALL_CAPABILITIES.includes(artifactType as CanonicalCapability)) {
223
+ used.add(artifactType as CanonicalCapability);
224
+ }
225
+
226
+ return used;
227
+ }
228
+
229
+ // ═══════════════════════════════════════════════════════════════════════════════
230
+ // evaluateCompatibility
231
+ // ═══════════════════════════════════════════════════════════════════════════════
232
+
233
+ /**
234
+ * For each used capability that is `partial` or `none` in the profile:
235
+ * - Emits one diagnostic per affected canonical field group
236
+ * - Records the number of affected values
237
+ * - Stores `unavailableDetails` when details cannot be determined
238
+ *
239
+ * @param profile - The effective compatibility profile
240
+ * @param usedCapabilities - Set of capabilities used by the artifact
241
+ * @param artifact - The artifact being evaluated (for counting affected values)
242
+ * @returns CompatibilityEvaluation with diagnostics, degradation records, and counts
243
+ */
244
+ export function evaluateCompatibility(
245
+ profile: EffectiveCompatibilityProfile,
246
+ usedCapabilities: ReadonlySet<CanonicalCapability>,
247
+ artifact: KnowledgeArtifact,
248
+ ): CompatibilityEvaluation {
249
+ const diagnostics: TranslationDiagnostic[] = [];
250
+ const degradations: DegradationRecord[] = [];
251
+ const affectedCounts: Record<string, number> = {};
252
+
253
+ // Process capabilities in deterministic code-point order
254
+ const sortedCapabilities = [...usedCapabilities].sort(codePointCompare);
255
+
256
+ for (const capability of sortedCapabilities) {
257
+ const entry = profile[capability];
258
+ if (!entry || entry.support === "full") {
259
+ continue;
260
+ }
261
+
262
+ const affectedCount = countAffectedValues(capability, artifact);
263
+ const canonicalPaths = [...CAPABILITY_FIELD_GROUPS[capability]].sort(
264
+ codePointCompare,
265
+ );
266
+ const degradationAction = entry.degradation as DegradationStrategy;
267
+
268
+ // Record affected count
269
+ affectedCounts[capability] = affectedCount;
270
+
271
+ // Build degradation detail for the diagnostic
272
+ const degradationDetail: DegradationDetail = {
273
+ capability,
274
+ action: degradationAction,
275
+ affectedValueCount: affectedCount,
276
+ };
277
+
278
+ // Determine unavailable details
279
+ const unavailableDetails: string[] = [];
280
+ // We always have capability, action, and canonical field.
281
+ // semanticChange may not be available.
282
+ unavailableDetails.push("expectedSemanticChange");
283
+
284
+ // Determine diagnostic code based on support level
285
+ const code =
286
+ entry.support === "partial"
287
+ ? "RS_COMPATIBILITY_PARTIAL"
288
+ : "RS_COMPATIBILITY_NONE";
289
+
290
+ // Emit one diagnostic per capability (which maps to one field group)
291
+ const diagnostic = createDiagnostic(code, {
292
+ canonical: {
293
+ artifactName: artifact.name,
294
+ fieldPath: canonicalPaths.join(", "),
295
+ },
296
+ degradation: degradationDetail,
297
+ unavailableDetails,
298
+ message:
299
+ entry.support === "partial"
300
+ ? `Capability "${capability}" is partially supported. ` +
301
+ `${affectedCount} value(s) affected. Degradation action: ${degradationAction}.`
302
+ : `Capability "${capability}" is not supported. ` +
303
+ `${affectedCount} value(s) will be omitted. Degradation action: ${degradationAction}.`,
304
+ remediation:
305
+ entry.support === "partial"
306
+ ? `Review the degradation action "${degradationAction}" for "${capability}" and verify semantic equivalence.`
307
+ : `The affected canonical data for "${capability}" will be handled via "${degradationAction}" in the target output.`,
308
+ });
309
+
310
+ diagnostics.push(diagnostic);
311
+
312
+ // Build DegradationRecord (matches the DegradationRecordSchema)
313
+ degradations.push({
314
+ capability,
315
+ canonicalPaths: [...canonicalPaths],
316
+ action: degradationAction,
317
+ affectedValueCount: affectedCount,
318
+ });
319
+ }
320
+
321
+ return {
322
+ diagnostics,
323
+ degradations,
324
+ affectedCounts,
325
+ };
326
+ }
327
+
328
+ // ═══════════════════════════════════════════════════════════════════════════════
329
+ // promoteInStrictMode
330
+ // ═══════════════════════════════════════════════════════════════════════════════
331
+
332
+ /**
333
+ * Takes all compatibility diagnostics (warnings) and promotes their severity
334
+ * to `error`, making them blocking. Does NOT change translator branches or
335
+ * non-compatibility diagnostics.
336
+ *
337
+ * @param evaluation - The compatibility evaluation result to promote
338
+ * @returns A new CompatibilityEvaluation with promoted diagnostics
339
+ */
340
+ export function promoteInStrictMode(
341
+ evaluation: CompatibilityEvaluation,
342
+ ): CompatibilityEvaluation {
343
+ const promotedDiagnostics = evaluation.diagnostics.map((diagnostic) => {
344
+ // Only promote compatibility-phase diagnostics
345
+ if (diagnostic.phase !== "compatibility") {
346
+ return diagnostic;
347
+ }
348
+
349
+ // Promote warning -> error and mark as blocking
350
+ if (diagnostic.severity === "warning") {
351
+ return {
352
+ ...diagnostic,
353
+ severity: "error" as const,
354
+ blocking: true,
355
+ };
356
+ }
357
+
358
+ return diagnostic;
359
+ });
360
+
361
+ return {
362
+ ...evaluation,
363
+ diagnostics: promotedDiagnostics,
364
+ };
365
+ }
366
+
367
+ // ═══════════════════════════════════════════════════════════════════════════════
368
+ // Internal Helpers
369
+ // ═══════════════════════════════════════════════════════════════════════════════
370
+
371
+ /**
372
+ * Counts the number of affected values for a given capability in the artifact.
373
+ * Returns the count of items/values that would be degraded.
374
+ */
375
+ function countAffectedValues(
376
+ capability: CanonicalCapability,
377
+ artifact: KnowledgeArtifact,
378
+ ): number {
379
+ switch (capability) {
380
+ case "frontmatter":
381
+ // Count non-default frontmatter fields
382
+ return Object.keys(artifact.frontmatter).length;
383
+
384
+ case "body":
385
+ // Body is one value (the content string)
386
+ return artifact.body.trim().length > 0 ? 1 : 0;
387
+
388
+ case "hooks":
389
+ return artifact.hooks.length;
390
+
391
+ case "mcp-servers":
392
+ return artifact.mcpServers.length;
393
+
394
+ case "workflows":
395
+ return artifact.workflows.length;
396
+
397
+ case "body-overrides":
398
+ return Object.keys(artifact.bodyOverrides).length;
399
+
400
+ case "extra-fields":
401
+ return Object.keys(artifact.extraFields).length;
402
+
403
+ case "path-scoping":
404
+ // Count path-related config items
405
+ return (artifact.frontmatter.file_patterns?.length ?? 0) + 1; // +1 for inclusion mode
406
+
407
+ case "toggleable-rules":
408
+ // One value: the toggleable inclusion mode
409
+ return 1;
410
+
411
+ case "file-match-inclusion":
412
+ return artifact.frontmatter.file_patterns?.length ?? 0;
413
+
414
+ case "system-prompt-merging":
415
+ // Body is the merged content
416
+ return artifact.body.trim().length > 0 ? 1 : 0;
417
+
418
+ // Asset-type capabilities: always 1 (the type declaration itself)
419
+ case "skill":
420
+ case "power":
421
+ case "rule":
422
+ case "workflow":
423
+ case "agent":
424
+ case "prompt":
425
+ case "template":
426
+ case "reference-pack":
427
+ return 1;
428
+
429
+ default:
430
+ return 0;
431
+ }
432
+ }