@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,717 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rosetta Stone — CLI Command Handlers
|
|
3
|
+
*
|
|
4
|
+
* Commander-based handlers for the `kanon rosetta` command namespace:
|
|
5
|
+
* - formats: List all registered format contracts
|
|
6
|
+
* - detect: Detect source format for a given path
|
|
7
|
+
* - inspect: Dry-run inspection of a translation
|
|
8
|
+
* - translate: Execute a translation
|
|
9
|
+
*
|
|
10
|
+
* Each handler validates direction, supports named translation profiles,
|
|
11
|
+
* routes to inbound/outbound/transcode mode, and supports --dry-run,
|
|
12
|
+
* --strict, --variant, and --json flags.
|
|
13
|
+
*
|
|
14
|
+
* Requirements: 2.8, 10.1, 10.2, 10.3
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { dirname, resolve } from "node:path";
|
|
18
|
+
import chalk from "chalk";
|
|
19
|
+
import type { Command } from "commander";
|
|
20
|
+
import { loadForgeConfig } from "./config";
|
|
21
|
+
import { registerBackfillCommand } from "./provenance-backfill-cli";
|
|
22
|
+
import { getSharedEngine, getSharedRegistry } from "./rosetta/engine-bootstrap";
|
|
23
|
+
import {
|
|
24
|
+
type JsonRenderOptions,
|
|
25
|
+
renderHuman,
|
|
26
|
+
renderJson,
|
|
27
|
+
type TranslationRegistrySnapshot,
|
|
28
|
+
} from "./rosetta/index";
|
|
29
|
+
import { registerProfilesCommands } from "./rosetta-profiles-cli";
|
|
30
|
+
import type {
|
|
31
|
+
FormatContract,
|
|
32
|
+
SourceDocument,
|
|
33
|
+
TranslationProfile,
|
|
34
|
+
TranslationRequest,
|
|
35
|
+
} from "./schemas";
|
|
36
|
+
import {
|
|
37
|
+
readArtifactDocuments,
|
|
38
|
+
resolveAllowedRoot,
|
|
39
|
+
} from "./translation-orchestrator";
|
|
40
|
+
|
|
41
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
42
|
+
// Registry and Engine Bootstrap (lazy, cached — shared with the import facade)
|
|
43
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Registry accessor. Delegates to the shared engine bootstrap so this command
|
|
47
|
+
* namespace and the `kanon import` facade resolve against one registry snapshot
|
|
48
|
+
* (ADR-0065). Kept as a local alias for the call sites that read
|
|
49
|
+
* `registry.version` / pass `registrySnapshot`.
|
|
50
|
+
*/
|
|
51
|
+
function getRegistry(): TranslationRegistrySnapshot {
|
|
52
|
+
return getSharedRegistry();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
56
|
+
// Source Document Loading
|
|
57
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Load source documents from a given path for use with the Rosetta Stone engine.
|
|
61
|
+
* Resolves the path as an allowed root and reads all files in the artifact directory.
|
|
62
|
+
*/
|
|
63
|
+
async function loadSourceDocuments(
|
|
64
|
+
sourcePath: string,
|
|
65
|
+
): Promise<SourceDocument[]> {
|
|
66
|
+
const resolvedPath = resolve(sourcePath);
|
|
67
|
+
|
|
68
|
+
// The parent directory serves as the allowed root for containment
|
|
69
|
+
const parentDir = dirname(resolvedPath);
|
|
70
|
+
const root = await resolveAllowedRoot(parentDir, "cli-source");
|
|
71
|
+
|
|
72
|
+
const group = await readArtifactDocuments(resolvedPath, root);
|
|
73
|
+
return group.documents;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
77
|
+
// Direction Inference
|
|
78
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
79
|
+
|
|
80
|
+
interface DirectionResult {
|
|
81
|
+
mode: "inbound" | "outbound" | "transcode";
|
|
82
|
+
sourceFormatId?: string;
|
|
83
|
+
targetFormatId?: string;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Determine translation direction from --from and --to flags.
|
|
88
|
+
* - --from only → inbound
|
|
89
|
+
* - --to only → outbound
|
|
90
|
+
* - both --from and --to → transcode
|
|
91
|
+
* - neither → error
|
|
92
|
+
*/
|
|
93
|
+
function resolveDirection(
|
|
94
|
+
from?: string,
|
|
95
|
+
to?: string,
|
|
96
|
+
): DirectionResult | string {
|
|
97
|
+
if (from && to) {
|
|
98
|
+
return { mode: "transcode", sourceFormatId: from, targetFormatId: to };
|
|
99
|
+
}
|
|
100
|
+
if (from && !to) {
|
|
101
|
+
return { mode: "inbound", sourceFormatId: from };
|
|
102
|
+
}
|
|
103
|
+
if (!from && to) {
|
|
104
|
+
return { mode: "outbound", targetFormatId: to };
|
|
105
|
+
}
|
|
106
|
+
return "Direction is required: use --from for inbound, --to for outbound, or both for transcode.";
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
110
|
+
// Profile Resolution
|
|
111
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
112
|
+
|
|
113
|
+
interface ResolvedOptions {
|
|
114
|
+
from?: string;
|
|
115
|
+
to?: string;
|
|
116
|
+
variant?: string;
|
|
117
|
+
strict: boolean;
|
|
118
|
+
options: Record<string, unknown>;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Merge a named translation profile with explicit CLI flags.
|
|
123
|
+
* CLI flags take precedence over profile values.
|
|
124
|
+
*/
|
|
125
|
+
function mergeProfileWithFlags(
|
|
126
|
+
profile: TranslationProfile | undefined,
|
|
127
|
+
flags: {
|
|
128
|
+
from?: string;
|
|
129
|
+
to?: string;
|
|
130
|
+
variant?: string;
|
|
131
|
+
strict?: boolean;
|
|
132
|
+
},
|
|
133
|
+
): ResolvedOptions {
|
|
134
|
+
const resolved: ResolvedOptions = {
|
|
135
|
+
from: flags.from ?? profile?.sourceFormat,
|
|
136
|
+
to: flags.to ?? profile?.targetFormat,
|
|
137
|
+
variant: flags.variant ?? profile?.targetVariant,
|
|
138
|
+
strict: flags.strict ?? profile?.strict ?? false,
|
|
139
|
+
options: profile?.options ?? {},
|
|
140
|
+
};
|
|
141
|
+
return resolved;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
145
|
+
// kanon rosetta formats
|
|
146
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
147
|
+
|
|
148
|
+
interface FormatsOptions {
|
|
149
|
+
json?: boolean;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Handler for `kanon rosetta formats`.
|
|
154
|
+
* Lists all registered format contracts with identifier, direction, harness,
|
|
155
|
+
* aliases, lifecycle, and supported canonical schema versions.
|
|
156
|
+
*/
|
|
157
|
+
async function formatsCommand(options: FormatsOptions): Promise<void> {
|
|
158
|
+
const registry = getRegistry();
|
|
159
|
+
const contracts = registry.listContracts();
|
|
160
|
+
|
|
161
|
+
if (options.json) {
|
|
162
|
+
const output = contracts.map(formatContractToJson);
|
|
163
|
+
console.log(JSON.stringify(output, null, 2));
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Human-readable output
|
|
168
|
+
console.log(chalk.bold.cyan("Registered Format Contracts"));
|
|
169
|
+
console.log(chalk.dim("─".repeat(50)));
|
|
170
|
+
console.log();
|
|
171
|
+
|
|
172
|
+
for (const contract of contracts) {
|
|
173
|
+
const lifecycle = contract.lifecycle.status;
|
|
174
|
+
const lifecycleLabel =
|
|
175
|
+
lifecycle === "active"
|
|
176
|
+
? chalk.green(lifecycle)
|
|
177
|
+
: lifecycle === "deprecated"
|
|
178
|
+
? chalk.yellow(lifecycle)
|
|
179
|
+
: lifecycle === "experimental"
|
|
180
|
+
? chalk.blue(lifecycle)
|
|
181
|
+
: chalk.red(lifecycle);
|
|
182
|
+
|
|
183
|
+
console.log(
|
|
184
|
+
` ${chalk.bold(contract.id)} ${chalk.dim(contract.direction)} ${lifecycleLabel}`,
|
|
185
|
+
);
|
|
186
|
+
if (contract.harness) {
|
|
187
|
+
console.log(` Harness: ${contract.harness}`);
|
|
188
|
+
}
|
|
189
|
+
if (contract.aliases.length > 0) {
|
|
190
|
+
console.log(` Aliases: ${contract.aliases.join(", ")}`);
|
|
191
|
+
}
|
|
192
|
+
const variantIds = Object.keys(contract.variants);
|
|
193
|
+
if (variantIds.length > 0) {
|
|
194
|
+
const defaultVariant = contract.defaultVariant;
|
|
195
|
+
const display = variantIds.map((v) =>
|
|
196
|
+
v === defaultVariant ? `${v} (default)` : v,
|
|
197
|
+
);
|
|
198
|
+
console.log(` Variants: ${display.join(", ")}`);
|
|
199
|
+
}
|
|
200
|
+
console.log(` Schema versions: ${contract.canonicalVersions}`);
|
|
201
|
+
console.log();
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function formatContractToJson(
|
|
206
|
+
contract: FormatContract,
|
|
207
|
+
): Record<string, unknown> {
|
|
208
|
+
return {
|
|
209
|
+
formatId: contract.id,
|
|
210
|
+
direction: contract.direction,
|
|
211
|
+
harness: contract.harness ?? null,
|
|
212
|
+
aliases: contract.aliases,
|
|
213
|
+
lifecycle: contract.lifecycle.status,
|
|
214
|
+
contractVersion: contract.contractVersion,
|
|
215
|
+
canonicalVersions: contract.canonicalVersions,
|
|
216
|
+
variants: Object.keys(contract.variants),
|
|
217
|
+
defaultVariant: contract.defaultVariant ?? null,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
222
|
+
// kanon rosetta detect
|
|
223
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
224
|
+
|
|
225
|
+
interface DetectOptions {
|
|
226
|
+
format?: string;
|
|
227
|
+
json?: boolean;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Handler for `kanon rosetta detect <path>`.
|
|
232
|
+
* Detects the source format of documents at the given path.
|
|
233
|
+
*/
|
|
234
|
+
async function detectCommand(
|
|
235
|
+
sourcePath: string,
|
|
236
|
+
options: DetectOptions,
|
|
237
|
+
): Promise<void> {
|
|
238
|
+
const registry = getRegistry();
|
|
239
|
+
const engine = getSharedEngine();
|
|
240
|
+
|
|
241
|
+
const documents = await loadSourceDocuments(sourcePath);
|
|
242
|
+
|
|
243
|
+
if (documents.length === 0) {
|
|
244
|
+
console.error(
|
|
245
|
+
chalk.red("Error: No documents found at the specified path."),
|
|
246
|
+
);
|
|
247
|
+
process.exit(1);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const result = engine.detect({
|
|
251
|
+
documents,
|
|
252
|
+
registrySnapshot: registry,
|
|
253
|
+
explicitFormatId: options.format,
|
|
254
|
+
direction: "source",
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
if (options.json) {
|
|
258
|
+
const output = {
|
|
259
|
+
ok: result.ok,
|
|
260
|
+
candidates: result.candidates.map((c) => ({
|
|
261
|
+
formatId: c.formatId,
|
|
262
|
+
confidence: c.confidence,
|
|
263
|
+
evidence: c.evidence,
|
|
264
|
+
})),
|
|
265
|
+
selected: result.ok ? result.selected : null,
|
|
266
|
+
diagnostics: result.diagnostics,
|
|
267
|
+
};
|
|
268
|
+
console.log(JSON.stringify(output, null, 2));
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Human-readable output
|
|
273
|
+
console.log(chalk.bold.cyan("Format Detection"));
|
|
274
|
+
console.log(chalk.dim("─".repeat(40)));
|
|
275
|
+
console.log();
|
|
276
|
+
|
|
277
|
+
if (result.ok) {
|
|
278
|
+
console.log(` Selected: ${chalk.green.bold(result.selected)}`);
|
|
279
|
+
} else {
|
|
280
|
+
console.log(chalk.red(" No unique format match found."));
|
|
281
|
+
}
|
|
282
|
+
console.log();
|
|
283
|
+
|
|
284
|
+
if (result.candidates.length > 0) {
|
|
285
|
+
console.log(chalk.bold(" Candidates:"));
|
|
286
|
+
for (const candidate of result.candidates) {
|
|
287
|
+
const conf = (candidate.confidence * 100).toFixed(0);
|
|
288
|
+
console.log(
|
|
289
|
+
` ${candidate.formatId} (${conf}% confidence, ${candidate.evidence.length} evidence)`,
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
console.log();
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (result.diagnostics.length > 0) {
|
|
296
|
+
console.log(chalk.bold(" Diagnostics:"));
|
|
297
|
+
for (const diag of result.diagnostics) {
|
|
298
|
+
const icon =
|
|
299
|
+
diag.severity === "error"
|
|
300
|
+
? "✗"
|
|
301
|
+
: diag.severity === "warning"
|
|
302
|
+
? "⚠"
|
|
303
|
+
: "ℹ";
|
|
304
|
+
console.log(` ${icon} [${diag.code}] ${diag.message}`);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (!result.ok) {
|
|
309
|
+
process.exit(1);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
314
|
+
// kanon rosetta inspect
|
|
315
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
316
|
+
|
|
317
|
+
interface InspectOptions {
|
|
318
|
+
from?: string;
|
|
319
|
+
to?: string;
|
|
320
|
+
variant?: string;
|
|
321
|
+
strict?: boolean;
|
|
322
|
+
json?: boolean;
|
|
323
|
+
profile?: string;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Handler for `kanon rosetta inspect <path>`.
|
|
328
|
+
* Inspects a translation without writing (equivalent to --dry-run on translate).
|
|
329
|
+
*/
|
|
330
|
+
async function inspectCommand(
|
|
331
|
+
sourcePath: string,
|
|
332
|
+
options: InspectOptions,
|
|
333
|
+
): Promise<void> {
|
|
334
|
+
const config = await loadForgeConfig();
|
|
335
|
+
const profile = options.profile
|
|
336
|
+
? config.translations?.[options.profile]
|
|
337
|
+
: undefined;
|
|
338
|
+
|
|
339
|
+
if (options.profile && !profile) {
|
|
340
|
+
console.error(
|
|
341
|
+
chalk.red(
|
|
342
|
+
`Error: Translation profile "${options.profile}" not found in config.`,
|
|
343
|
+
),
|
|
344
|
+
);
|
|
345
|
+
process.exit(1);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const resolved = mergeProfileWithFlags(profile, options);
|
|
349
|
+
|
|
350
|
+
const direction = resolveDirection(resolved.from, resolved.to);
|
|
351
|
+
if (typeof direction === "string") {
|
|
352
|
+
console.error(chalk.red(`Error: ${direction}`));
|
|
353
|
+
process.exit(1);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const registry = getRegistry();
|
|
357
|
+
const engine = getSharedEngine();
|
|
358
|
+
|
|
359
|
+
// Validate explicit format identifiers against direction
|
|
360
|
+
if (direction.sourceFormatId) {
|
|
361
|
+
const sourceRes = registry.resolve(direction.sourceFormatId, "source");
|
|
362
|
+
if (!sourceRes.ok) {
|
|
363
|
+
console.error(
|
|
364
|
+
chalk.red(
|
|
365
|
+
`Error: "${direction.sourceFormatId}" is not a valid source format.`,
|
|
366
|
+
),
|
|
367
|
+
);
|
|
368
|
+
for (const d of sourceRes.diagnostics) {
|
|
369
|
+
console.error(chalk.dim(` ${d.message}`));
|
|
370
|
+
}
|
|
371
|
+
process.exit(1);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (direction.targetFormatId) {
|
|
376
|
+
const targetRes = registry.resolve(direction.targetFormatId, "target");
|
|
377
|
+
if (!targetRes.ok) {
|
|
378
|
+
console.error(
|
|
379
|
+
chalk.red(
|
|
380
|
+
`Error: "${direction.targetFormatId}" is not a valid target format.`,
|
|
381
|
+
),
|
|
382
|
+
);
|
|
383
|
+
for (const d of targetRes.diagnostics) {
|
|
384
|
+
console.error(chalk.dim(` ${d.message}`));
|
|
385
|
+
}
|
|
386
|
+
process.exit(1);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const documents = await loadSourceDocuments(sourcePath);
|
|
391
|
+
|
|
392
|
+
if (documents.length === 0) {
|
|
393
|
+
console.error(
|
|
394
|
+
chalk.red("Error: No documents found at the specified path."),
|
|
395
|
+
);
|
|
396
|
+
process.exit(1);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const request = buildTranslationRequest(direction, documents, resolved);
|
|
400
|
+
const report = engine.inspect(request);
|
|
401
|
+
|
|
402
|
+
if (options.json) {
|
|
403
|
+
const jsonOpts: JsonRenderOptions = {
|
|
404
|
+
generatedAt: new Date().toISOString(),
|
|
405
|
+
registryVersion: registry.version,
|
|
406
|
+
request,
|
|
407
|
+
defaults: [],
|
|
408
|
+
normalizations: [],
|
|
409
|
+
degradations: [],
|
|
410
|
+
};
|
|
411
|
+
console.log(renderJson(report, jsonOpts));
|
|
412
|
+
} else {
|
|
413
|
+
console.log(renderHuman(report));
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
418
|
+
// kanon rosetta translate
|
|
419
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
420
|
+
|
|
421
|
+
interface TranslateOptions {
|
|
422
|
+
from?: string;
|
|
423
|
+
to?: string;
|
|
424
|
+
variant?: string;
|
|
425
|
+
dryRun?: boolean;
|
|
426
|
+
strict?: boolean;
|
|
427
|
+
json?: boolean;
|
|
428
|
+
profile?: string;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Handler for `kanon rosetta translate <path>`.
|
|
433
|
+
* Executes a translation operation. With --dry-run, produces inspection
|
|
434
|
+
* output without writing files.
|
|
435
|
+
*/
|
|
436
|
+
async function translateCommand(
|
|
437
|
+
sourcePath: string,
|
|
438
|
+
options: TranslateOptions,
|
|
439
|
+
): Promise<void> {
|
|
440
|
+
const config = await loadForgeConfig();
|
|
441
|
+
const profile = options.profile
|
|
442
|
+
? config.translations?.[options.profile]
|
|
443
|
+
: undefined;
|
|
444
|
+
|
|
445
|
+
if (options.profile && !profile) {
|
|
446
|
+
console.error(
|
|
447
|
+
chalk.red(
|
|
448
|
+
`Error: Translation profile "${options.profile}" not found in config.`,
|
|
449
|
+
),
|
|
450
|
+
);
|
|
451
|
+
process.exit(1);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
const resolved = mergeProfileWithFlags(profile, options);
|
|
455
|
+
|
|
456
|
+
const direction = resolveDirection(resolved.from, resolved.to);
|
|
457
|
+
if (typeof direction === "string") {
|
|
458
|
+
console.error(chalk.red(`Error: ${direction}`));
|
|
459
|
+
process.exit(1);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
const registry = getRegistry();
|
|
463
|
+
const engine = getSharedEngine();
|
|
464
|
+
|
|
465
|
+
// Validate explicit format identifiers against direction
|
|
466
|
+
if (direction.sourceFormatId) {
|
|
467
|
+
const sourceRes = registry.resolve(direction.sourceFormatId, "source");
|
|
468
|
+
if (!sourceRes.ok) {
|
|
469
|
+
console.error(
|
|
470
|
+
chalk.red(
|
|
471
|
+
`Error: "${direction.sourceFormatId}" is not a valid source format.`,
|
|
472
|
+
),
|
|
473
|
+
);
|
|
474
|
+
for (const d of sourceRes.diagnostics) {
|
|
475
|
+
console.error(chalk.dim(` ${d.message}`));
|
|
476
|
+
}
|
|
477
|
+
process.exit(1);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
if (direction.targetFormatId) {
|
|
482
|
+
const targetRes = registry.resolve(direction.targetFormatId, "target");
|
|
483
|
+
if (!targetRes.ok) {
|
|
484
|
+
console.error(
|
|
485
|
+
chalk.red(
|
|
486
|
+
`Error: "${direction.targetFormatId}" is not a valid target format.`,
|
|
487
|
+
),
|
|
488
|
+
);
|
|
489
|
+
for (const d of targetRes.diagnostics) {
|
|
490
|
+
console.error(chalk.dim(` ${d.message}`));
|
|
491
|
+
}
|
|
492
|
+
process.exit(1);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
const documents = await loadSourceDocuments(sourcePath);
|
|
497
|
+
|
|
498
|
+
if (documents.length === 0) {
|
|
499
|
+
console.error(
|
|
500
|
+
chalk.red("Error: No documents found at the specified path."),
|
|
501
|
+
);
|
|
502
|
+
process.exit(1);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
const request = buildTranslationRequest(direction, documents, resolved);
|
|
506
|
+
|
|
507
|
+
if (options.dryRun) {
|
|
508
|
+
// Dry-run: produce inspection without writing
|
|
509
|
+
const report = engine.inspect(request);
|
|
510
|
+
|
|
511
|
+
if (options.json) {
|
|
512
|
+
const jsonOpts: JsonRenderOptions = {
|
|
513
|
+
generatedAt: new Date().toISOString(),
|
|
514
|
+
registryVersion: registry.version,
|
|
515
|
+
request,
|
|
516
|
+
defaults: [],
|
|
517
|
+
normalizations: [],
|
|
518
|
+
degradations: [],
|
|
519
|
+
};
|
|
520
|
+
console.log(renderJson(report, jsonOpts));
|
|
521
|
+
} else {
|
|
522
|
+
console.log(renderHuman(report));
|
|
523
|
+
}
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// Execute translation
|
|
528
|
+
const result = engine.translate(request);
|
|
529
|
+
|
|
530
|
+
if (options.json) {
|
|
531
|
+
const output = {
|
|
532
|
+
status: result.status,
|
|
533
|
+
diagnostics: result.diagnostics,
|
|
534
|
+
plan: result.plan
|
|
535
|
+
? {
|
|
536
|
+
operationCount: result.plan.outputFiles?.length ?? 0,
|
|
537
|
+
outputFiles:
|
|
538
|
+
result.plan.outputFiles?.map((f) => f.relativePath) ?? [],
|
|
539
|
+
}
|
|
540
|
+
: null,
|
|
541
|
+
};
|
|
542
|
+
console.log(JSON.stringify(output, null, 2));
|
|
543
|
+
} else {
|
|
544
|
+
// Human-readable result
|
|
545
|
+
if (result.status === "success") {
|
|
546
|
+
console.log(chalk.green("Translation successful."));
|
|
547
|
+
} else if (result.status === "partial") {
|
|
548
|
+
console.log(chalk.yellow("Translation completed with warnings."));
|
|
549
|
+
} else {
|
|
550
|
+
console.log(chalk.red("Translation failed."));
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
if (result.diagnostics.length > 0) {
|
|
554
|
+
console.log();
|
|
555
|
+
console.log(chalk.bold("Diagnostics:"));
|
|
556
|
+
for (const diag of result.diagnostics) {
|
|
557
|
+
const icon =
|
|
558
|
+
diag.severity === "error"
|
|
559
|
+
? chalk.red("✗")
|
|
560
|
+
: diag.severity === "warning"
|
|
561
|
+
? chalk.yellow("⚠")
|
|
562
|
+
: chalk.blue("ℹ");
|
|
563
|
+
console.log(` ${icon} [${diag.code}] ${diag.message}`);
|
|
564
|
+
if (diag.remediation) {
|
|
565
|
+
console.log(chalk.dim(` Remediation: ${diag.remediation}`));
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
if (result.plan?.outputFiles && result.plan.outputFiles.length > 0) {
|
|
571
|
+
console.log();
|
|
572
|
+
console.log(chalk.bold("Output files:"));
|
|
573
|
+
for (const file of result.plan.outputFiles) {
|
|
574
|
+
console.log(` ${file.relativePath}`);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
if (result.status === "failure") {
|
|
580
|
+
process.exit(1);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
585
|
+
// Translation Request Builder
|
|
586
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
587
|
+
|
|
588
|
+
function buildTranslationRequest(
|
|
589
|
+
direction: DirectionResult,
|
|
590
|
+
documents: readonly SourceDocument[],
|
|
591
|
+
resolved: ResolvedOptions,
|
|
592
|
+
): TranslationRequest {
|
|
593
|
+
// Documents are already in SourceDocument shape from the orchestrator
|
|
594
|
+
const sourceDocuments = documents.map((d) => ({
|
|
595
|
+
path: d.path,
|
|
596
|
+
content: typeof d.content === "string" ? d.content : d.content,
|
|
597
|
+
executable: d.executable,
|
|
598
|
+
...(d.mediaType ? { mediaType: d.mediaType } : {}),
|
|
599
|
+
}));
|
|
600
|
+
|
|
601
|
+
switch (direction.mode) {
|
|
602
|
+
case "inbound":
|
|
603
|
+
return {
|
|
604
|
+
mode: "inbound",
|
|
605
|
+
sourceDocuments,
|
|
606
|
+
source: {
|
|
607
|
+
formatId: direction.sourceFormatId,
|
|
608
|
+
variant: resolved.variant,
|
|
609
|
+
options: resolved.options as Record<string, never>,
|
|
610
|
+
},
|
|
611
|
+
canonical: {
|
|
612
|
+
emitEmptyAuxiliaryFiles: false,
|
|
613
|
+
},
|
|
614
|
+
canonicalSchemaVersion: "1.0.0",
|
|
615
|
+
strict: resolved.strict,
|
|
616
|
+
callerContext: {},
|
|
617
|
+
};
|
|
618
|
+
case "outbound":
|
|
619
|
+
// For outbound, the source documents represent a canonical artifact directory
|
|
620
|
+
// The engine handles parsing them into a KnowledgeArtifact
|
|
621
|
+
return {
|
|
622
|
+
mode: "transcode",
|
|
623
|
+
sourceDocuments,
|
|
624
|
+
source: {
|
|
625
|
+
formatId: "kanon-canonical",
|
|
626
|
+
options: {},
|
|
627
|
+
},
|
|
628
|
+
target: {
|
|
629
|
+
// biome-ignore lint/style/noNonNullAssertion: targetFormatId guaranteed for outbound
|
|
630
|
+
formatId: direction.targetFormatId!,
|
|
631
|
+
variant: resolved.variant,
|
|
632
|
+
options: resolved.options as Record<string, never>,
|
|
633
|
+
},
|
|
634
|
+
canonicalSchemaVersion: "1.0.0",
|
|
635
|
+
strict: resolved.strict,
|
|
636
|
+
callerContext: {},
|
|
637
|
+
};
|
|
638
|
+
case "transcode":
|
|
639
|
+
return {
|
|
640
|
+
mode: "transcode",
|
|
641
|
+
sourceDocuments,
|
|
642
|
+
source: {
|
|
643
|
+
formatId: direction.sourceFormatId,
|
|
644
|
+
variant: resolved.variant,
|
|
645
|
+
options: {},
|
|
646
|
+
},
|
|
647
|
+
target: {
|
|
648
|
+
// biome-ignore lint/style/noNonNullAssertion: targetFormatId guaranteed for transcode
|
|
649
|
+
formatId: direction.targetFormatId!,
|
|
650
|
+
variant: resolved.variant,
|
|
651
|
+
options: resolved.options as Record<string, never>,
|
|
652
|
+
},
|
|
653
|
+
canonicalSchemaVersion: "1.0.0",
|
|
654
|
+
strict: resolved.strict,
|
|
655
|
+
callerContext: {},
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
661
|
+
// Command Registration
|
|
662
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
663
|
+
|
|
664
|
+
/**
|
|
665
|
+
* Register the `kanon rosetta` command group on a Commander program.
|
|
666
|
+
*/
|
|
667
|
+
export function registerRosettaCommands(program: Command): void {
|
|
668
|
+
const rosettaCmd = program
|
|
669
|
+
.command("rosetta")
|
|
670
|
+
.description(
|
|
671
|
+
"Rosetta Stone — format discovery, detection, inspection, and translation",
|
|
672
|
+
);
|
|
673
|
+
|
|
674
|
+
// Register profiles subcommand group
|
|
675
|
+
registerProfilesCommands(rosettaCmd);
|
|
676
|
+
|
|
677
|
+
// Register the provenance backfill command
|
|
678
|
+
registerBackfillCommand(rosettaCmd);
|
|
679
|
+
|
|
680
|
+
rosettaCmd
|
|
681
|
+
.command("formats")
|
|
682
|
+
.description("List all registered format contracts")
|
|
683
|
+
.option("--json", "Output as JSON")
|
|
684
|
+
.action((opts: FormatsOptions) => formatsCommand(opts));
|
|
685
|
+
|
|
686
|
+
rosettaCmd
|
|
687
|
+
.command("detect <path>")
|
|
688
|
+
.description("Detect the source format of documents at a path")
|
|
689
|
+
.option("--format <id>", "Explicit format selection (validates only)")
|
|
690
|
+
.option("--json", "Output as JSON")
|
|
691
|
+
.action((path: string, opts: DetectOptions) => detectCommand(path, opts));
|
|
692
|
+
|
|
693
|
+
rosettaCmd
|
|
694
|
+
.command("inspect <path>")
|
|
695
|
+
.description("Inspect a translation without writing files")
|
|
696
|
+
.option("--from <id>", "Source format (inbound direction)")
|
|
697
|
+
.option("--to <id>", "Target format (outbound direction)")
|
|
698
|
+
.option("--variant <name>", "Explicit variant selection")
|
|
699
|
+
.option("--strict", "Promote compatibility diagnostics to errors")
|
|
700
|
+
.option("--json", "Output as versioned JSON")
|
|
701
|
+
.option("--profile <name>", "Named translation profile from config")
|
|
702
|
+
.action((path: string, opts: InspectOptions) => inspectCommand(path, opts));
|
|
703
|
+
|
|
704
|
+
rosettaCmd
|
|
705
|
+
.command("translate <path>")
|
|
706
|
+
.description("Translate artifacts between formats")
|
|
707
|
+
.option("--from <id>", "Source format (inbound direction)")
|
|
708
|
+
.option("--to <id>", "Target format (outbound direction)")
|
|
709
|
+
.option("--variant <name>", "Explicit variant selection")
|
|
710
|
+
.option("--dry-run", "Inspect without writing")
|
|
711
|
+
.option("--strict", "Promote compatibility diagnostics to errors")
|
|
712
|
+
.option("--json", "Output as JSON")
|
|
713
|
+
.option("--profile <name>", "Named translation profile from config")
|
|
714
|
+
.action((path: string, opts: TranslateOptions) =>
|
|
715
|
+
translateCommand(path, opts),
|
|
716
|
+
);
|
|
717
|
+
}
|