@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.
- package/CHANGELOG.md +410 -0
- package/LICENSE +21 -0
- package/README.md +168 -0
- package/bridge/mcp-server.cjs +14171 -0
- package/package.json +98 -0
- package/src/adapters/capabilities.ts +178 -0
- package/src/adapters/claude-code.ts +110 -0
- package/src/adapters/cline.ts +98 -0
- package/src/adapters/codex.ts +173 -0
- package/src/adapters/copilot.ts +106 -0
- package/src/adapters/cursor.ts +97 -0
- package/src/adapters/degradation.ts +95 -0
- package/src/adapters/index.ts +324 -0
- package/src/adapters/kiro-frontmatter.ts +139 -0
- package/src/adapters/kiro-inclusion.ts +86 -0
- package/src/adapters/kiro.ts +412 -0
- package/src/adapters/qdeveloper.ts +115 -0
- package/src/adapters/types.ts +81 -0
- package/src/adapters/windsurf.ts +96 -0
- package/src/admin.ts +283 -0
- package/src/asset-conventions.ts +118 -0
- package/src/attribution-backfill.ts +319 -0
- package/src/attribution-report.ts +95 -0
- package/src/attribution.ts +239 -0
- package/src/backends/github.ts +194 -0
- package/src/backends/http.ts +122 -0
- package/src/backends/index.ts +39 -0
- package/src/backends/local.ts +47 -0
- package/src/backends/s3.ts +157 -0
- package/src/backends/types.ts +59 -0
- package/src/base-cache.ts +270 -0
- package/src/browse-ui.ts +3754 -0
- package/src/browse.ts +1038 -0
- package/src/build.ts +1108 -0
- package/src/catalog.ts +204 -0
- package/src/cli-deprecated.ts +29 -0
- package/src/cli.ts +773 -0
- package/src/collection-admin.ts +287 -0
- package/src/collection-builder.ts +464 -0
- package/src/collections.ts +116 -0
- package/src/compatibility.ts +105 -0
- package/src/config.ts +743 -0
- package/src/eval/rubrics/kiro-progressive-steering.ts +841 -0
- package/src/eval.ts +1169 -0
- package/src/file-writer.ts +61 -0
- package/src/format-registry.ts +141 -0
- package/src/guild/auto-updater.ts +163 -0
- package/src/guild/backend-resolver.ts +49 -0
- package/src/guild/cli.ts +592 -0
- package/src/guild/collection-expander.ts +47 -0
- package/src/guild/global-cache.ts +247 -0
- package/src/guild/hook-generator.ts +100 -0
- package/src/guild/manifest.ts +154 -0
- package/src/guild/path-utils.ts +12 -0
- package/src/guild/sync.ts +622 -0
- package/src/guild/version-resolver.ts +42 -0
- package/src/help/metadata.ts +445 -0
- package/src/help/renderer.ts +265 -0
- package/src/help/typo-suggester.ts +25 -0
- package/src/hooks/expression.ts +493 -0
- package/src/hooks/pipeline.ts +141 -0
- package/src/import.ts +773 -0
- package/src/importers/claude-code.ts +134 -0
- package/src/importers/cline.ts +103 -0
- package/src/importers/codex.ts +140 -0
- package/src/importers/copilot.ts +103 -0
- package/src/importers/cursor.ts +105 -0
- package/src/importers/index.ts +390 -0
- package/src/importers/kiro.ts +110 -0
- package/src/importers/qdeveloper.ts +103 -0
- package/src/importers/types.ts +54 -0
- package/src/importers/windsurf.ts +104 -0
- package/src/install.ts +1005 -0
- package/src/manifest-admin.ts +306 -0
- package/src/mcp-bridge.ts +240 -0
- package/src/mutation/delta.ts +50 -0
- package/src/mutation/history.ts +66 -0
- package/src/mutation/operators.ts +524 -0
- package/src/mutation/runner.ts +332 -0
- package/src/new.ts +106 -0
- package/src/outcomes/collision.ts +127 -0
- package/src/outcomes/normalize.ts +208 -0
- package/src/outcomes/registry.ts +173 -0
- package/src/parser.ts +446 -0
- package/src/provenance-backfill-cli.ts +319 -0
- package/src/provenance-backfill.ts +520 -0
- package/src/publish.ts +354 -0
- package/src/reconcile-orchestrator.ts +502 -0
- package/src/reconcile-report-renderer.ts +176 -0
- package/src/resolve-body.ts +15 -0
- package/src/rosetta/builtins/compatibility-profiles.ts +297 -0
- package/src/rosetta/builtins/contracts.ts +1033 -0
- package/src/rosetta/builtins/pretty-printers/claude-code-native.ts +122 -0
- package/src/rosetta/builtins/pretty-printers/cline-native.ts +50 -0
- package/src/rosetta/builtins/pretty-printers/codex-native.ts +127 -0
- package/src/rosetta/builtins/pretty-printers/copilot-native.ts +50 -0
- package/src/rosetta/builtins/pretty-printers/cursor-native.ts +50 -0
- package/src/rosetta/builtins/pretty-printers/index.ts +81 -0
- package/src/rosetta/builtins/pretty-printers/kiro-native.ts +166 -0
- package/src/rosetta/builtins/pretty-printers/kiro-power.ts +108 -0
- package/src/rosetta/builtins/pretty-printers/kiro-skill.ts +88 -0
- package/src/rosetta/builtins/pretty-printers/qdeveloper-native.ts +51 -0
- package/src/rosetta/builtins/pretty-printers/superpowers.ts +97 -0
- package/src/rosetta/builtins/pretty-printers/windsurf-native.ts +50 -0
- package/src/rosetta/builtins/sources/claude-code-native.ts +348 -0
- package/src/rosetta/builtins/sources/cline-native.ts +176 -0
- package/src/rosetta/builtins/sources/codex-native.ts +343 -0
- package/src/rosetta/builtins/sources/copilot-native.ts +178 -0
- package/src/rosetta/builtins/sources/cursor-native.ts +176 -0
- package/src/rosetta/builtins/sources/index.ts +95 -0
- package/src/rosetta/builtins/sources/kiro-native.ts +462 -0
- package/src/rosetta/builtins/sources/kiro-power.ts +285 -0
- package/src/rosetta/builtins/sources/kiro-skill.ts +230 -0
- package/src/rosetta/builtins/sources/qdeveloper-native.ts +181 -0
- package/src/rosetta/builtins/sources/superpowers.ts +240 -0
- package/src/rosetta/builtins/sources/windsurf-native.ts +176 -0
- package/src/rosetta/builtins/targets/claude-code.ts +181 -0
- package/src/rosetta/builtins/targets/cline.ts +87 -0
- package/src/rosetta/builtins/targets/codex.ts +226 -0
- package/src/rosetta/builtins/targets/copilot.ts +103 -0
- package/src/rosetta/builtins/targets/cursor.ts +87 -0
- package/src/rosetta/builtins/targets/index.ts +60 -0
- package/src/rosetta/builtins/targets/kiro.ts +278 -0
- package/src/rosetta/builtins/targets/qdeveloper.ts +103 -0
- package/src/rosetta/builtins/targets/windsurf.ts +87 -0
- package/src/rosetta/canonical.ts +729 -0
- package/src/rosetta/compatibility.ts +432 -0
- package/src/rosetta/contracts.ts +329 -0
- package/src/rosetta/detector.ts +724 -0
- package/src/rosetta/diagnostics.ts +630 -0
- package/src/rosetta/engine-bootstrap.ts +103 -0
- package/src/rosetta/engine.ts +744 -0
- package/src/rosetta/index.ts +381 -0
- package/src/rosetta/inspection.ts +530 -0
- package/src/rosetta/plan.ts +448 -0
- package/src/rosetta/provenance-digest.ts +369 -0
- package/src/rosetta/reconcile.ts +812 -0
- package/src/rosetta/redaction.ts +467 -0
- package/src/rosetta/registry.ts +712 -0
- package/src/rosetta/renderers.ts +571 -0
- package/src/rosetta/request-guard.ts +335 -0
- package/src/rosetta/resolution.ts +419 -0
- package/src/rosetta/source-accounting.ts +233 -0
- package/src/rosetta/templates.ts +129 -0
- package/src/rosetta-cli.ts +717 -0
- package/src/rosetta-docs-generator.ts +793 -0
- package/src/rosetta-profiles-cli.ts +367 -0
- package/src/schemas.ts +1712 -0
- package/src/spec-coordination.ts +1141 -0
- package/src/temper.ts +747 -0
- package/src/template-bundle-loader.ts +312 -0
- package/src/template-engine.ts +53 -0
- package/src/translation-application-policy.ts +496 -0
- package/src/translation-orchestrator.ts +1013 -0
- package/src/translation-plan-applier.ts +473 -0
- package/src/tutorial.ts +305 -0
- package/src/validate.ts +1093 -0
- package/src/versioning.ts +553 -0
- package/src/wizard.ts +660 -0
- package/src/workspace.ts +237 -0
- package/templates/eval-contexts/claude-code.md.njk +6 -0
- package/templates/eval-contexts/cline.md.njk +6 -0
- package/templates/eval-contexts/copilot.md.njk +6 -0
- package/templates/eval-contexts/cursor.md.njk +6 -0
- package/templates/eval-contexts/kiro.md.njk +10 -0
- package/templates/eval-contexts/qdeveloper.md.njk +6 -0
- package/templates/eval-contexts/windsurf.md.njk +6 -0
- package/templates/harness-adapters/_base/attribution-footer.md.njk +17 -0
- package/templates/harness-adapters/_base/base.md.njk +16 -0
- package/templates/harness-adapters/claude-code/claude.md.njk +1 -0
- package/templates/harness-adapters/claude-code/mcp.json.njk +1 -0
- package/templates/harness-adapters/claude-code/settings.json.njk +1 -0
- package/templates/harness-adapters/claude-code/skill-library-index.md.njk +13 -0
- package/templates/harness-adapters/claude-code/skill.md.njk +19 -0
- package/templates/harness-adapters/cline/hook.sh.njk +4 -0
- package/templates/harness-adapters/cline/mcp.json.njk +1 -0
- package/templates/harness-adapters/cline/rule.md.njk +1 -0
- package/templates/harness-adapters/codex/agents-md.md.njk +6 -0
- package/templates/harness-adapters/codex/agents-pointer.md.njk +16 -0
- package/templates/harness-adapters/codex/skill.md.njk +27 -0
- package/templates/harness-adapters/copilot/agents.md.njk +1 -0
- package/templates/harness-adapters/copilot/instructions.md.njk +1 -0
- package/templates/harness-adapters/copilot/scoped.md.njk +6 -0
- package/templates/harness-adapters/cursor/mcp.json.njk +1 -0
- package/templates/harness-adapters/cursor/rule.md.njk +6 -0
- package/templates/harness-adapters/kiro/hook.json.njk +1 -0
- package/templates/harness-adapters/kiro/mcp.json.njk +1 -0
- package/templates/harness-adapters/kiro/power-steering.md.njk +3 -0
- package/templates/harness-adapters/kiro/power.md.njk +12 -0
- package/templates/harness-adapters/kiro/steering.md.njk +16 -0
- package/templates/harness-adapters/qdeveloper/agent.md.njk +1 -0
- package/templates/harness-adapters/qdeveloper/mcp.json.njk +1 -0
- package/templates/harness-adapters/qdeveloper/rule.md.njk +1 -0
- package/templates/harness-adapters/windsurf/mcp.json.njk +1 -0
- package/templates/harness-adapters/windsurf/rule.md.njk +1 -0
- package/templates/harness-adapters/windsurf/workflow.md.njk +1 -0
- package/templates/knowledge/hooks.yaml.njk +4 -0
- package/templates/knowledge/knowledge.md.njk +53 -0
- 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
|
+
}
|