@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,419 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rosetta Stone — Shared Format Variant and Option Resolution
|
|
3
|
+
*
|
|
4
|
+
* Resolves which variant to use and which effective options apply for a given
|
|
5
|
+
* format contract by applying a strict precedence chain:
|
|
6
|
+
* explicit request > translation profile > canonical harness-config > contract default
|
|
7
|
+
*
|
|
8
|
+
* Preserves Kiro `power: true` fallback only when `format` is absent and emits
|
|
9
|
+
* stable deprecation guidance.
|
|
10
|
+
*
|
|
11
|
+
* All functions are pure. No filesystem, process, clock, random, Git,
|
|
12
|
+
* or network imports.
|
|
13
|
+
*
|
|
14
|
+
* Requirements: 6.2, 6.3, 6.4, 10.3, 10.8, 14.6, 14.7, 14.11
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type {
|
|
18
|
+
FormatContract,
|
|
19
|
+
FormatOptionDefinition,
|
|
20
|
+
TranslationDiagnostic,
|
|
21
|
+
} from "../schemas";
|
|
22
|
+
import { codePointCompare } from "./contracts";
|
|
23
|
+
import { createDiagnostic } from "./diagnostics";
|
|
24
|
+
|
|
25
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
26
|
+
// Types
|
|
27
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Context for resolving which variant to use for a translation.
|
|
31
|
+
*/
|
|
32
|
+
export interface VariantResolutionContext {
|
|
33
|
+
/** Explicit variant from CLI or request — highest precedence */
|
|
34
|
+
readonly explicitVariant?: string;
|
|
35
|
+
/** Variant from a named translation profile */
|
|
36
|
+
readonly profileVariant?: string;
|
|
37
|
+
/** Artifact's harness-config section (may contain legacy kiro.power or kiro.format) */
|
|
38
|
+
readonly harnessConfig?: Record<string, unknown>;
|
|
39
|
+
/** Default variant declared by the format contract */
|
|
40
|
+
readonly contractDefault?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Result of variant resolution.
|
|
45
|
+
*/
|
|
46
|
+
export interface VariantResolutionResult {
|
|
47
|
+
/** The resolved variant identifier, or undefined if no variant applies */
|
|
48
|
+
readonly variant: string | undefined;
|
|
49
|
+
/** Where the variant came from */
|
|
50
|
+
readonly origin: string;
|
|
51
|
+
/** Any diagnostics produced during resolution */
|
|
52
|
+
readonly diagnostics: readonly TranslationDiagnostic[];
|
|
53
|
+
/** Deprecation guidance if legacy path was used */
|
|
54
|
+
readonly deprecation?: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Context for resolving effective option values.
|
|
59
|
+
*/
|
|
60
|
+
export interface OptionResolutionContext {
|
|
61
|
+
/** Explicit options from CLI or request — highest precedence */
|
|
62
|
+
readonly explicitOptions: Record<string, unknown>;
|
|
63
|
+
/** Options from a named translation profile */
|
|
64
|
+
readonly profileOptions?: Record<string, unknown>;
|
|
65
|
+
/** Options from artifact's canonical harness-config */
|
|
66
|
+
readonly canonicalOptions?: Record<string, unknown>;
|
|
67
|
+
/** Default option values from the format contract */
|
|
68
|
+
readonly contractDefaults: Record<string, unknown>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Result of option resolution.
|
|
73
|
+
*/
|
|
74
|
+
export interface OptionResolutionResult {
|
|
75
|
+
/** The effective resolved options */
|
|
76
|
+
readonly effective: Record<string, unknown>;
|
|
77
|
+
/** The origin of each effective option value */
|
|
78
|
+
readonly origins: Record<string, string>;
|
|
79
|
+
/** The contract defaults that were applied (subset of effective) */
|
|
80
|
+
readonly defaults: Record<string, unknown>;
|
|
81
|
+
/** Any diagnostics produced during resolution */
|
|
82
|
+
readonly diagnostics: readonly TranslationDiagnostic[];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
86
|
+
// Constants
|
|
87
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
88
|
+
|
|
89
|
+
/** Deprecation message for legacy kiro.power: true usage */
|
|
90
|
+
const KIRO_POWER_DEPRECATION =
|
|
91
|
+
'Use `harness-config.kiro.format: "power"` instead of `power: true`.';
|
|
92
|
+
|
|
93
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
94
|
+
// resolveVariant
|
|
95
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Resolves which variant to use for a format contract.
|
|
99
|
+
*
|
|
100
|
+
* Precedence: explicit > profile > harness-config > contract default.
|
|
101
|
+
*
|
|
102
|
+
* For Kiro harness-config:
|
|
103
|
+
* - `kiro.format: "power"` maps to variant "power"
|
|
104
|
+
* - `kiro.power: true` (without `format`) maps to variant "power" with deprecation
|
|
105
|
+
*
|
|
106
|
+
* @param contract - The format contract with declared variants
|
|
107
|
+
* @param context - Resolution context with values from each precedence layer
|
|
108
|
+
* @returns Resolved variant, origin, diagnostics, and optional deprecation
|
|
109
|
+
*/
|
|
110
|
+
export function resolveVariant(
|
|
111
|
+
contract: FormatContract,
|
|
112
|
+
context: VariantResolutionContext,
|
|
113
|
+
): VariantResolutionResult {
|
|
114
|
+
const diagnostics: TranslationDiagnostic[] = [];
|
|
115
|
+
|
|
116
|
+
// 1. Explicit request — highest precedence, always wins
|
|
117
|
+
if (context.explicitVariant !== undefined) {
|
|
118
|
+
const variantId = context.explicitVariant;
|
|
119
|
+
|
|
120
|
+
// Validate that the explicit variant exists in the contract
|
|
121
|
+
if (
|
|
122
|
+
Object.keys(contract.variants).length > 0 &&
|
|
123
|
+
!(variantId in contract.variants)
|
|
124
|
+
) {
|
|
125
|
+
diagnostics.push(
|
|
126
|
+
createDiagnostic("RS_INVALID_REQUEST", {
|
|
127
|
+
message: `Requested variant "${variantId}" does not exist in format "${contract.id}". Valid variants: ${listVariantIds(contract).join(", ")}.`,
|
|
128
|
+
remediation: `Use one of the declared variants for format "${contract.id}": ${listVariantIds(contract).join(", ")}.`,
|
|
129
|
+
formatId: contract.id,
|
|
130
|
+
}),
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
variant: variantId,
|
|
136
|
+
origin: "explicit",
|
|
137
|
+
diagnostics,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// 2. Translation profile variant
|
|
142
|
+
if (context.profileVariant !== undefined) {
|
|
143
|
+
return {
|
|
144
|
+
variant: context.profileVariant,
|
|
145
|
+
origin: "profile",
|
|
146
|
+
diagnostics,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// 3. Harness-config — with Kiro power backward compatibility
|
|
151
|
+
if (context.harnessConfig !== undefined) {
|
|
152
|
+
const harnessConfigResult = resolveVariantFromHarnessConfig(
|
|
153
|
+
contract,
|
|
154
|
+
context.harnessConfig,
|
|
155
|
+
);
|
|
156
|
+
if (harnessConfigResult !== undefined) {
|
|
157
|
+
if (harnessConfigResult.deprecation) {
|
|
158
|
+
diagnostics.push(
|
|
159
|
+
createDiagnostic("RS_LIFECYCLE_DEPRECATED", {
|
|
160
|
+
message: harnessConfigResult.deprecation,
|
|
161
|
+
remediation: KIRO_POWER_DEPRECATION,
|
|
162
|
+
formatId: contract.id,
|
|
163
|
+
severityOverride: "info",
|
|
164
|
+
}),
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
variant: harnessConfigResult.variant,
|
|
169
|
+
origin: "harness-config",
|
|
170
|
+
diagnostics,
|
|
171
|
+
deprecation: harnessConfigResult.deprecation,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// 4. Contract default
|
|
177
|
+
if (context.contractDefault !== undefined) {
|
|
178
|
+
return {
|
|
179
|
+
variant: context.contractDefault,
|
|
180
|
+
origin: "contract-default",
|
|
181
|
+
diagnostics,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// No variant resolved — contract may not require one
|
|
186
|
+
return {
|
|
187
|
+
variant: contract.defaultVariant,
|
|
188
|
+
origin: contract.defaultVariant !== undefined ? "contract-default" : "none",
|
|
189
|
+
diagnostics,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
194
|
+
// resolveOptions
|
|
195
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Resolves effective option values for a format contract.
|
|
199
|
+
*
|
|
200
|
+
* Precedence per key: explicit > profile > canonical > contract default.
|
|
201
|
+
*
|
|
202
|
+
* Validates each effective value against the contract's optionDefinitions
|
|
203
|
+
* schema (if any) and emits error diagnostics for invalid values.
|
|
204
|
+
*
|
|
205
|
+
* @param contract - The format contract with declared option definitions
|
|
206
|
+
* @param context - Resolution context with values from each precedence layer
|
|
207
|
+
* @returns Effective options, origins, defaults, and diagnostics
|
|
208
|
+
*/
|
|
209
|
+
export function resolveOptions(
|
|
210
|
+
contract: FormatContract,
|
|
211
|
+
context: OptionResolutionContext,
|
|
212
|
+
): OptionResolutionResult {
|
|
213
|
+
const diagnostics: TranslationDiagnostic[] = [];
|
|
214
|
+
const effective: Record<string, unknown> = {};
|
|
215
|
+
const origins: Record<string, string> = {};
|
|
216
|
+
const defaults: Record<string, unknown> = {};
|
|
217
|
+
|
|
218
|
+
// Gather all option keys from all layers and contract definitions
|
|
219
|
+
const allKeys = new Set<string>();
|
|
220
|
+
for (const key of Object.keys(context.explicitOptions)) allKeys.add(key);
|
|
221
|
+
if (context.profileOptions) {
|
|
222
|
+
for (const key of Object.keys(context.profileOptions)) allKeys.add(key);
|
|
223
|
+
}
|
|
224
|
+
if (context.canonicalOptions) {
|
|
225
|
+
for (const key of Object.keys(context.canonicalOptions)) allKeys.add(key);
|
|
226
|
+
}
|
|
227
|
+
for (const key of Object.keys(context.contractDefaults)) allKeys.add(key);
|
|
228
|
+
for (const key of Object.keys(contract.optionDefinitions)) allKeys.add(key);
|
|
229
|
+
|
|
230
|
+
// Resolve each key with precedence
|
|
231
|
+
for (const key of allKeys) {
|
|
232
|
+
if (key in context.explicitOptions) {
|
|
233
|
+
effective[key] = context.explicitOptions[key];
|
|
234
|
+
origins[key] = "explicit";
|
|
235
|
+
} else if (context.profileOptions && key in context.profileOptions) {
|
|
236
|
+
effective[key] = context.profileOptions[key];
|
|
237
|
+
origins[key] = "profile";
|
|
238
|
+
} else if (context.canonicalOptions && key in context.canonicalOptions) {
|
|
239
|
+
effective[key] = context.canonicalOptions[key];
|
|
240
|
+
origins[key] = "canonical";
|
|
241
|
+
} else if (key in context.contractDefaults) {
|
|
242
|
+
effective[key] = context.contractDefaults[key];
|
|
243
|
+
origins[key] = "contract-default";
|
|
244
|
+
defaults[key] = context.contractDefaults[key];
|
|
245
|
+
} else if (key in contract.optionDefinitions) {
|
|
246
|
+
// Use option definition's declared default if available
|
|
247
|
+
const def = contract.optionDefinitions[key];
|
|
248
|
+
if (def.defaultValue !== undefined) {
|
|
249
|
+
effective[key] = def.defaultValue;
|
|
250
|
+
origins[key] = "contract-default";
|
|
251
|
+
defaults[key] = def.defaultValue;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Validate effective values against option definitions
|
|
257
|
+
for (const [key, value] of Object.entries(effective)) {
|
|
258
|
+
const definition = contract.optionDefinitions[key];
|
|
259
|
+
if (!definition) {
|
|
260
|
+
continue; // No schema to validate against
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const validationError = validateOptionValue(key, value, definition);
|
|
264
|
+
if (validationError) {
|
|
265
|
+
diagnostics.push(
|
|
266
|
+
createDiagnostic("RS_INVALID_REQUEST", {
|
|
267
|
+
message: validationError,
|
|
268
|
+
remediation: buildOptionRemediation(key, definition),
|
|
269
|
+
formatId: contract.id,
|
|
270
|
+
}),
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return { effective, origins, defaults, diagnostics };
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
279
|
+
// listValidChoices
|
|
280
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Lists sorted valid values for a variant or option field in a contract.
|
|
284
|
+
*
|
|
285
|
+
* @param contract - The format contract
|
|
286
|
+
* @param field - Either "variant" for variant IDs, or an option key name
|
|
287
|
+
* @returns Sorted list of valid values, or empty array if the field is unknown
|
|
288
|
+
*/
|
|
289
|
+
export function listValidChoices(
|
|
290
|
+
contract: FormatContract,
|
|
291
|
+
field: string,
|
|
292
|
+
): readonly string[] {
|
|
293
|
+
if (field === "variant") {
|
|
294
|
+
return listVariantIds(contract);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Look up option definition
|
|
298
|
+
const definition = contract.optionDefinitions[field];
|
|
299
|
+
if (!definition) {
|
|
300
|
+
return [];
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// For enum-type options, return sorted enum values
|
|
304
|
+
if (definition.type === "enum" && definition.enumValues) {
|
|
305
|
+
return [...definition.enumValues].sort(codePointCompare);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// For boolean-type options, return true/false
|
|
309
|
+
if (definition.type === "boolean") {
|
|
310
|
+
return ["false", "true"];
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// For other types, no enumerable choices
|
|
314
|
+
return [];
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
318
|
+
// Internal Helpers
|
|
319
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Returns sorted variant IDs from a format contract.
|
|
323
|
+
*/
|
|
324
|
+
function listVariantIds(contract: FormatContract): readonly string[] {
|
|
325
|
+
return Object.keys(contract.variants).sort(codePointCompare);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Attempts to resolve a variant from harness-config values.
|
|
330
|
+
* Handles Kiro-specific `power: true` and `format` field patterns.
|
|
331
|
+
*
|
|
332
|
+
* Returns undefined if harness-config doesn't specify a variant.
|
|
333
|
+
*/
|
|
334
|
+
function resolveVariantFromHarnessConfig(
|
|
335
|
+
contract: FormatContract,
|
|
336
|
+
harnessConfig: Record<string, unknown>,
|
|
337
|
+
): { variant: string; deprecation?: string } | undefined {
|
|
338
|
+
// Check for harness-config entries keyed by harness name (e.g., kiro: { format: "power" })
|
|
339
|
+
// The harness-config may be the harness-specific sub-object already,
|
|
340
|
+
// or it may be the full harness-config with harness keys at top level.
|
|
341
|
+
|
|
342
|
+
// Check direct `format` field (e.g., from kiro.format: "power")
|
|
343
|
+
if (typeof harnessConfig.format === "string") {
|
|
344
|
+
return { variant: harnessConfig.format };
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Kiro backward compatibility: kiro.power: true without format field
|
|
348
|
+
if (
|
|
349
|
+
contract.harness === "kiro" &&
|
|
350
|
+
harnessConfig.power === true &&
|
|
351
|
+
harnessConfig.format === undefined
|
|
352
|
+
) {
|
|
353
|
+
return {
|
|
354
|
+
variant: "power",
|
|
355
|
+
deprecation: KIRO_POWER_DEPRECATION,
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return undefined;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Validates a single option value against its definition.
|
|
364
|
+
* Returns an error message string if invalid, or undefined if valid.
|
|
365
|
+
*/
|
|
366
|
+
function validateOptionValue(
|
|
367
|
+
key: string,
|
|
368
|
+
value: unknown,
|
|
369
|
+
definition: FormatOptionDefinition,
|
|
370
|
+
): string | undefined {
|
|
371
|
+
switch (definition.type) {
|
|
372
|
+
case "string":
|
|
373
|
+
if (typeof value !== "string") {
|
|
374
|
+
return `Option "${key}" expects a string value, got ${typeof value}.`;
|
|
375
|
+
}
|
|
376
|
+
break;
|
|
377
|
+
|
|
378
|
+
case "boolean":
|
|
379
|
+
if (typeof value !== "boolean") {
|
|
380
|
+
return `Option "${key}" expects a boolean value, got ${typeof value}.`;
|
|
381
|
+
}
|
|
382
|
+
break;
|
|
383
|
+
|
|
384
|
+
case "number":
|
|
385
|
+
if (typeof value !== "number") {
|
|
386
|
+
return `Option "${key}" expects a number value, got ${typeof value}.`;
|
|
387
|
+
}
|
|
388
|
+
break;
|
|
389
|
+
|
|
390
|
+
case "enum":
|
|
391
|
+
if (typeof value !== "string") {
|
|
392
|
+
return `Option "${key}" expects a string enum value, got ${typeof value}.`;
|
|
393
|
+
}
|
|
394
|
+
if (
|
|
395
|
+
definition.enumValues &&
|
|
396
|
+
!definition.enumValues.includes(value as string)
|
|
397
|
+
) {
|
|
398
|
+
const sorted = [...definition.enumValues].sort(codePointCompare);
|
|
399
|
+
return `Option "${key}" has invalid value "${value}". Valid choices: ${sorted.join(", ")}.`;
|
|
400
|
+
}
|
|
401
|
+
break;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
return undefined;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Builds a remediation string for an invalid option value.
|
|
409
|
+
*/
|
|
410
|
+
function buildOptionRemediation(
|
|
411
|
+
key: string,
|
|
412
|
+
definition: FormatOptionDefinition,
|
|
413
|
+
): string {
|
|
414
|
+
if (definition.type === "enum" && definition.enumValues) {
|
|
415
|
+
const sorted = [...definition.enumValues].sort(codePointCompare);
|
|
416
|
+
return `Set option "${key}" to one of: ${sorted.join(", ")}.`;
|
|
417
|
+
}
|
|
418
|
+
return `Set option "${key}" to a valid ${definition.type} value.`;
|
|
419
|
+
}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rosetta Stone — Source Accounting and Mapping Helpers
|
|
3
|
+
*
|
|
4
|
+
* Tracks consumed/preserved paths, field mappings, namespaced lossless
|
|
5
|
+
* extraFields, undeclared-loss diagnostics, default diagnostics, and
|
|
6
|
+
* source-document ordering normalization during source translation.
|
|
7
|
+
*
|
|
8
|
+
* CONSTRAINTS:
|
|
9
|
+
* - NO filesystem, process, clock, random, Git, or network imports
|
|
10
|
+
* - Pure functions only
|
|
11
|
+
*
|
|
12
|
+
* Requirements: 4.1, 4.2, 4.3, 4.4, 4.9, 7.7
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type {
|
|
16
|
+
NormalizedRelativePath,
|
|
17
|
+
SourceDocument,
|
|
18
|
+
TranslationDiagnostic,
|
|
19
|
+
} from "../schemas";
|
|
20
|
+
|
|
21
|
+
import { codePointCompare } from "./contracts";
|
|
22
|
+
import { createDiagnostic } from "./diagnostics";
|
|
23
|
+
|
|
24
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
25
|
+
// Types
|
|
26
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Records a field-level mapping from source to canonical.
|
|
30
|
+
*/
|
|
31
|
+
export interface FieldMapping {
|
|
32
|
+
/** Source document path */
|
|
33
|
+
readonly sourcePath: string;
|
|
34
|
+
/** Field name in source */
|
|
35
|
+
readonly sourceField: string;
|
|
36
|
+
/** Target canonical field */
|
|
37
|
+
readonly canonicalField: string;
|
|
38
|
+
/** Whether value was modified during mapping */
|
|
39
|
+
readonly transformed: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Records a default value application during translation.
|
|
44
|
+
* (Named SourceAppliedDefault to distinguish from the schema-level AppliedDefault.)
|
|
45
|
+
*/
|
|
46
|
+
export interface SourceAppliedDefault {
|
|
47
|
+
/** Field that received a default */
|
|
48
|
+
readonly canonicalField: string;
|
|
49
|
+
/** The value applied */
|
|
50
|
+
readonly defaultValue: unknown;
|
|
51
|
+
/** Which contract rule triggered the default */
|
|
52
|
+
readonly contractRuleId: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
56
|
+
// SourceAccountant Class
|
|
57
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Tracks consumed/preserved paths and field mappings during source translation.
|
|
61
|
+
*
|
|
62
|
+
* Source translators use this to account for every input document and field,
|
|
63
|
+
* enabling the engine to detect undeclared loss.
|
|
64
|
+
*/
|
|
65
|
+
export class SourceAccountant {
|
|
66
|
+
private readonly _consumed: Set<string> = new Set();
|
|
67
|
+
private readonly _preserved: Set<string> = new Set();
|
|
68
|
+
private readonly _mappings: FieldMapping[] = [];
|
|
69
|
+
private readonly _defaults: SourceAppliedDefault[] = [];
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Marks a document path as consumed (content was parsed into canonical fields).
|
|
73
|
+
*/
|
|
74
|
+
consume(path: string): void {
|
|
75
|
+
this._consumed.add(path);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Marks a document path as preserved (carried into canonical as-is, e.g., workflow files).
|
|
80
|
+
*/
|
|
81
|
+
preserve(path: string): void {
|
|
82
|
+
this._preserved.add(path);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Records a field-level mapping from source to canonical.
|
|
87
|
+
*/
|
|
88
|
+
mapField(
|
|
89
|
+
sourcePath: string,
|
|
90
|
+
sourceField: string,
|
|
91
|
+
canonicalField: string,
|
|
92
|
+
transformed = false,
|
|
93
|
+
): void {
|
|
94
|
+
this._mappings.push({
|
|
95
|
+
sourcePath,
|
|
96
|
+
sourceField,
|
|
97
|
+
canonicalField,
|
|
98
|
+
transformed,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Records a default value application.
|
|
104
|
+
*/
|
|
105
|
+
applyDefault(
|
|
106
|
+
canonicalField: string,
|
|
107
|
+
defaultValue: unknown,
|
|
108
|
+
contractRuleId: string,
|
|
109
|
+
): void {
|
|
110
|
+
this._defaults.push({ canonicalField, defaultValue, contractRuleId });
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Returns sorted consumed paths (code-point order).
|
|
115
|
+
*/
|
|
116
|
+
getConsumedPaths(): readonly NormalizedRelativePath[] {
|
|
117
|
+
return [...this._consumed].sort(
|
|
118
|
+
codePointCompare,
|
|
119
|
+
) as NormalizedRelativePath[];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Returns sorted preserved paths (code-point order).
|
|
124
|
+
*/
|
|
125
|
+
getPreservedPaths(): readonly NormalizedRelativePath[] {
|
|
126
|
+
return [...this._preserved].sort(
|
|
127
|
+
codePointCompare,
|
|
128
|
+
) as NormalizedRelativePath[];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Returns all field mappings in recording order.
|
|
133
|
+
*/
|
|
134
|
+
getMappings(): readonly FieldMapping[] {
|
|
135
|
+
return [...this._mappings];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Returns all applied defaults in recording order.
|
|
140
|
+
*/
|
|
141
|
+
getDefaults(): readonly SourceAppliedDefault[] {
|
|
142
|
+
return [...this._defaults];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Returns paths from allPaths that are neither consumed nor preserved.
|
|
147
|
+
* Result is sorted by code-point order.
|
|
148
|
+
*/
|
|
149
|
+
getUnaccountedPaths(allPaths: readonly string[]): readonly string[] {
|
|
150
|
+
return allPaths
|
|
151
|
+
.filter((p) => !this._consumed.has(p) && !this._preserved.has(p))
|
|
152
|
+
.sort(codePointCompare);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
157
|
+
// Validation
|
|
158
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Validates completeness of source accounting.
|
|
162
|
+
*
|
|
163
|
+
* Checks every document path is either consumed or preserved,
|
|
164
|
+
* emitting diagnostics for unaccounted paths.
|
|
165
|
+
*
|
|
166
|
+
* @param accountant - The accountant tracking consumed/preserved paths
|
|
167
|
+
* @param documents - All source documents provided to the translator
|
|
168
|
+
* @param formatId - The source format identifier for diagnostic context
|
|
169
|
+
* @returns Diagnostics array for any accounting issues
|
|
170
|
+
*/
|
|
171
|
+
export function validateSourceAccounting(
|
|
172
|
+
accountant: SourceAccountant,
|
|
173
|
+
documents: readonly SourceDocument[],
|
|
174
|
+
formatId: string,
|
|
175
|
+
): TranslationDiagnostic[] {
|
|
176
|
+
const diagnostics: TranslationDiagnostic[] = [];
|
|
177
|
+
const allPaths = documents.map((d) => d.path);
|
|
178
|
+
const unaccounted = accountant.getUnaccountedPaths(allPaths);
|
|
179
|
+
|
|
180
|
+
for (const path of unaccounted) {
|
|
181
|
+
diagnostics.push(
|
|
182
|
+
createDiagnostic("RS_SOURCE_UNACCOUNTED", {
|
|
183
|
+
formatId,
|
|
184
|
+
message: `Source document "${path}" was neither consumed nor preserved.`,
|
|
185
|
+
source: { path },
|
|
186
|
+
}),
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return diagnostics;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
194
|
+
// Namespaced Extra Fields
|
|
195
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Generates a namespaced extra field key for lossless preservation.
|
|
199
|
+
*
|
|
200
|
+
* Format: `source.<format-id>.<source-path>[.<field-name>]`
|
|
201
|
+
*
|
|
202
|
+
* @example
|
|
203
|
+
* namespacedExtraField("kiro-power", "POWER.md", "author")
|
|
204
|
+
* // => "source.kiro-power.POWER.md.author"
|
|
205
|
+
*
|
|
206
|
+
* @example
|
|
207
|
+
* namespacedExtraField("cursor", "rule")
|
|
208
|
+
* // => "source.cursor.rule"
|
|
209
|
+
*/
|
|
210
|
+
export function namespacedExtraField(
|
|
211
|
+
formatId: string,
|
|
212
|
+
sourcePath: string,
|
|
213
|
+
fieldName?: string,
|
|
214
|
+
): string {
|
|
215
|
+
if (fieldName !== undefined) {
|
|
216
|
+
return `source.${formatId}.${sourcePath}.${fieldName}`;
|
|
217
|
+
}
|
|
218
|
+
return `source.${formatId}.${sourcePath}`;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
222
|
+
// Document Order Normalization
|
|
223
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Sorts documents deterministically by path using code-point comparison.
|
|
227
|
+
* Returns a new sorted array without mutating the input.
|
|
228
|
+
*/
|
|
229
|
+
export function normalizeDocumentOrder<T extends { readonly path: string }>(
|
|
230
|
+
documents: readonly T[],
|
|
231
|
+
): T[] {
|
|
232
|
+
return [...documents].sort((a, b) => codePointCompare(a.path, b.path));
|
|
233
|
+
}
|