@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,367 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rosetta Stone — Profile Listing and Validation CLI
|
|
3
|
+
*
|
|
4
|
+
* Script-facing commands that return machine-readable acquisition and translation
|
|
5
|
+
* profile values/statuses without credentials or Git handles. Validates profiles
|
|
6
|
+
* against the registry and halts before acquisition on invalid configuration.
|
|
7
|
+
*
|
|
8
|
+
* Commands:
|
|
9
|
+
* kanon rosetta profiles [--json] — List all configured profiles
|
|
10
|
+
* kanon rosetta profiles validate [--json] — Validate profiles against registry
|
|
11
|
+
*
|
|
12
|
+
* Requirements: 10.4, 10.7, 11.2, 11.6, 11.7
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import chalk from "chalk";
|
|
16
|
+
import type { Command } from "commander";
|
|
17
|
+
import {
|
|
18
|
+
loadForgeConfig,
|
|
19
|
+
type ProfileDiagnostic,
|
|
20
|
+
type ProfileValidationResult,
|
|
21
|
+
validateProfiles,
|
|
22
|
+
} from "./config";
|
|
23
|
+
import { BUILTIN_FORMAT_CONTRACTS } from "./rosetta/index";
|
|
24
|
+
import type { AcquisitionProfile, TranslationProfile } from "./schemas";
|
|
25
|
+
|
|
26
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
27
|
+
// Registry Bootstrap (shared with rosetta-cli.ts)
|
|
28
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Extract the set of known format IDs from the built-in contract list.
|
|
32
|
+
* This avoids requiring a full registry with translators — we only need
|
|
33
|
+
* the format identifiers and their aliases for profile cross-validation.
|
|
34
|
+
*/
|
|
35
|
+
function getKnownFormatIds(): ReadonlySet<string> {
|
|
36
|
+
const ids = new Set<string>();
|
|
37
|
+
for (const contract of BUILTIN_FORMAT_CONTRACTS) {
|
|
38
|
+
ids.add(contract.id);
|
|
39
|
+
for (const alias of contract.aliases) {
|
|
40
|
+
ids.add(alias);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return ids;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
47
|
+
// Profile Sanitization — Strip credentials and Git handles
|
|
48
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
49
|
+
|
|
50
|
+
/** Sensitive fields that MUST NOT appear in output. */
|
|
51
|
+
const CREDENTIAL_FIELDS: ReadonlySet<string> = new Set([
|
|
52
|
+
"credentialReference",
|
|
53
|
+
"token",
|
|
54
|
+
"password",
|
|
55
|
+
"secret",
|
|
56
|
+
"apiKey",
|
|
57
|
+
]);
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Return a sanitized acquisition profile suitable for machine output.
|
|
61
|
+
* Strips credential references and Git handles.
|
|
62
|
+
*/
|
|
63
|
+
function sanitizeAcquisitionProfile(
|
|
64
|
+
profile: AcquisitionProfile,
|
|
65
|
+
): Record<string, unknown> {
|
|
66
|
+
return {
|
|
67
|
+
repo: profile.repo,
|
|
68
|
+
branch: profile.branch,
|
|
69
|
+
remote: profile.remote,
|
|
70
|
+
...(profile.checkoutPrefix
|
|
71
|
+
? { checkoutPrefix: profile.checkoutPrefix }
|
|
72
|
+
: {}),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Return a sanitized translation profile suitable for machine output.
|
|
78
|
+
* No credential fields exist in TranslationProfile but we still
|
|
79
|
+
* strip any that might appear via passthrough or future additions.
|
|
80
|
+
*/
|
|
81
|
+
function sanitizeTranslationProfile(
|
|
82
|
+
profile: TranslationProfile,
|
|
83
|
+
): Record<string, unknown> {
|
|
84
|
+
const result: Record<string, unknown> = {};
|
|
85
|
+
for (const [key, value] of Object.entries(profile)) {
|
|
86
|
+
if (CREDENTIAL_FIELDS.has(key)) continue;
|
|
87
|
+
result[key] = value;
|
|
88
|
+
}
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
93
|
+
// Profile Listing
|
|
94
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
95
|
+
|
|
96
|
+
interface ProfilesListOptions {
|
|
97
|
+
json?: boolean;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Machine-readable profile listing output shape.
|
|
102
|
+
*/
|
|
103
|
+
interface ProfileListingOutput {
|
|
104
|
+
acquisitions: Record<string, Record<string, unknown>>;
|
|
105
|
+
translations: Record<string, Record<string, unknown>>;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* List all configured acquisition and translation profiles.
|
|
110
|
+
* Returns profile metadata without credentials or Git handles.
|
|
111
|
+
*/
|
|
112
|
+
async function profilesListCommand(
|
|
113
|
+
options: ProfilesListOptions,
|
|
114
|
+
): Promise<void> {
|
|
115
|
+
const config = await loadForgeConfig();
|
|
116
|
+
|
|
117
|
+
const listing: ProfileListingOutput = {
|
|
118
|
+
acquisitions: {},
|
|
119
|
+
translations: {},
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// Sanitize acquisition profiles
|
|
123
|
+
if (config.acquisitions) {
|
|
124
|
+
for (const [name, profile] of Object.entries(config.acquisitions)) {
|
|
125
|
+
listing.acquisitions[name] = sanitizeAcquisitionProfile(profile);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Sanitize translation profiles
|
|
130
|
+
if (config.translations) {
|
|
131
|
+
for (const [name, profile] of Object.entries(config.translations)) {
|
|
132
|
+
listing.translations[name] = sanitizeTranslationProfile(profile);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (options.json) {
|
|
137
|
+
console.log(JSON.stringify(listing, null, 2));
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Human-readable output
|
|
142
|
+
const acqCount = Object.keys(listing.acquisitions).length;
|
|
143
|
+
const transCount = Object.keys(listing.translations).length;
|
|
144
|
+
|
|
145
|
+
if (acqCount === 0 && transCount === 0) {
|
|
146
|
+
console.log(
|
|
147
|
+
chalk.dim("No acquisition or translation profiles configured."),
|
|
148
|
+
);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (acqCount > 0) {
|
|
153
|
+
console.log(chalk.bold.cyan("Acquisition Profiles"));
|
|
154
|
+
console.log(chalk.dim("─".repeat(40)));
|
|
155
|
+
for (const [name, profile] of Object.entries(listing.acquisitions)) {
|
|
156
|
+
console.log(` ${chalk.bold(name)}`);
|
|
157
|
+
for (const [key, value] of Object.entries(
|
|
158
|
+
profile as Record<string, unknown>,
|
|
159
|
+
)) {
|
|
160
|
+
console.log(` ${key}: ${value}`);
|
|
161
|
+
}
|
|
162
|
+
console.log();
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (transCount > 0) {
|
|
167
|
+
console.log(chalk.bold.cyan("Translation Profiles"));
|
|
168
|
+
console.log(chalk.dim("─".repeat(40)));
|
|
169
|
+
for (const [name, profile] of Object.entries(listing.translations)) {
|
|
170
|
+
console.log(` ${chalk.bold(name)}`);
|
|
171
|
+
for (const [key, value] of Object.entries(
|
|
172
|
+
profile as Record<string, unknown>,
|
|
173
|
+
)) {
|
|
174
|
+
const display = Array.isArray(value)
|
|
175
|
+
? value.length > 0
|
|
176
|
+
? value.join(", ")
|
|
177
|
+
: "(none)"
|
|
178
|
+
: typeof value === "object" && value !== null
|
|
179
|
+
? JSON.stringify(value)
|
|
180
|
+
: String(value);
|
|
181
|
+
console.log(` ${key}: ${display}`);
|
|
182
|
+
}
|
|
183
|
+
console.log();
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
189
|
+
// Profile Validation
|
|
190
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
191
|
+
|
|
192
|
+
interface ProfilesValidateOptions {
|
|
193
|
+
json?: boolean;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Machine-readable validation output shape.
|
|
198
|
+
*/
|
|
199
|
+
interface ProfileValidationOutput {
|
|
200
|
+
valid: boolean;
|
|
201
|
+
acquisitions: Record<string, ProfileEntryStatus>;
|
|
202
|
+
translations: Record<string, ProfileEntryStatus>;
|
|
203
|
+
diagnostics: readonly ProfileDiagnosticOutput[];
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
interface ProfileEntryStatus {
|
|
207
|
+
valid: boolean;
|
|
208
|
+
diagnostics: readonly ProfileDiagnosticOutput[];
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
interface ProfileDiagnosticOutput {
|
|
212
|
+
path: string;
|
|
213
|
+
message: string;
|
|
214
|
+
severity: "error" | "warning";
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Validate all profiles against the registry.
|
|
219
|
+
* Exits nonzero with diagnostics if validation fails, BEFORE any
|
|
220
|
+
* Git/network operations would run (Req 10.7, 11.2).
|
|
221
|
+
*/
|
|
222
|
+
async function profilesValidateCommand(
|
|
223
|
+
options: ProfilesValidateOptions,
|
|
224
|
+
): Promise<void> {
|
|
225
|
+
const config = await loadForgeConfig();
|
|
226
|
+
const knownFormatIds = getKnownFormatIds();
|
|
227
|
+
|
|
228
|
+
const result: ProfileValidationResult = validateProfiles(config, {
|
|
229
|
+
knownFormatIds,
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
// Build per-profile status maps
|
|
233
|
+
const acqStatus: Record<string, ProfileEntryStatus> = {};
|
|
234
|
+
const transStatus: Record<string, ProfileEntryStatus> = {};
|
|
235
|
+
|
|
236
|
+
if (config.acquisitions) {
|
|
237
|
+
for (const name of Object.keys(config.acquisitions)) {
|
|
238
|
+
const profileDiags = result.diagnostics.filter(
|
|
239
|
+
(d) =>
|
|
240
|
+
d.path.startsWith(`acquisitions.${name}.`) ||
|
|
241
|
+
d.path === `acquisitions.${name}`,
|
|
242
|
+
);
|
|
243
|
+
acqStatus[name] = {
|
|
244
|
+
valid: profileDiags.every((d) => d.severity !== "error"),
|
|
245
|
+
diagnostics: profileDiags.map(diagToOutput),
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (config.translations) {
|
|
251
|
+
for (const name of Object.keys(config.translations)) {
|
|
252
|
+
const profileDiags = result.diagnostics.filter(
|
|
253
|
+
(d) =>
|
|
254
|
+
d.path.startsWith(`translations.${name}.`) ||
|
|
255
|
+
d.path === `translations.${name}`,
|
|
256
|
+
);
|
|
257
|
+
transStatus[name] = {
|
|
258
|
+
valid: profileDiags.every((d) => d.severity !== "error"),
|
|
259
|
+
diagnostics: profileDiags.map(diagToOutput),
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const output: ProfileValidationOutput = {
|
|
265
|
+
valid: result.valid,
|
|
266
|
+
acquisitions: acqStatus,
|
|
267
|
+
translations: transStatus,
|
|
268
|
+
diagnostics: result.diagnostics.map(diagToOutput),
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
if (options.json) {
|
|
272
|
+
console.log(JSON.stringify(output, null, 2));
|
|
273
|
+
if (!result.valid) {
|
|
274
|
+
process.exit(1);
|
|
275
|
+
}
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Human-readable output
|
|
280
|
+
if (result.valid) {
|
|
281
|
+
console.log(chalk.green("All profiles are valid."));
|
|
282
|
+
printProfileSummary(acqStatus, transStatus);
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Invalid — print diagnostics and exit nonzero
|
|
287
|
+
console.log(chalk.red.bold("Profile validation failed."));
|
|
288
|
+
console.log();
|
|
289
|
+
|
|
290
|
+
if (result.diagnostics.length > 0) {
|
|
291
|
+
console.log(chalk.bold("Diagnostics:"));
|
|
292
|
+
for (const diag of result.diagnostics) {
|
|
293
|
+
const icon =
|
|
294
|
+
diag.severity === "error" ? chalk.red("✗") : chalk.yellow("⚠");
|
|
295
|
+
console.log(` ${icon} [${diag.path}] ${diag.message}`);
|
|
296
|
+
}
|
|
297
|
+
console.log();
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
printProfileSummary(acqStatus, transStatus);
|
|
301
|
+
process.exit(1);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function diagToOutput(diag: ProfileDiagnostic): ProfileDiagnosticOutput {
|
|
305
|
+
return {
|
|
306
|
+
path: diag.path,
|
|
307
|
+
message: diag.message,
|
|
308
|
+
severity: diag.severity,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function printProfileSummary(
|
|
313
|
+
acqStatus: Record<string, ProfileEntryStatus>,
|
|
314
|
+
transStatus: Record<string, ProfileEntryStatus>,
|
|
315
|
+
): void {
|
|
316
|
+
const acqNames = Object.keys(acqStatus);
|
|
317
|
+
const transNames = Object.keys(transStatus);
|
|
318
|
+
|
|
319
|
+
if (acqNames.length > 0) {
|
|
320
|
+
console.log(chalk.bold("Acquisition profiles:"));
|
|
321
|
+
for (const name of acqNames) {
|
|
322
|
+
const status = acqStatus[name];
|
|
323
|
+
const icon = status.valid ? chalk.green("✓") : chalk.red("✗");
|
|
324
|
+
console.log(` ${icon} ${name}`);
|
|
325
|
+
}
|
|
326
|
+
console.log();
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (transNames.length > 0) {
|
|
330
|
+
console.log(chalk.bold("Translation profiles:"));
|
|
331
|
+
for (const name of transNames) {
|
|
332
|
+
const status = transStatus[name];
|
|
333
|
+
const icon = status.valid ? chalk.green("✓") : chalk.red("✗");
|
|
334
|
+
console.log(` ${icon} ${name}`);
|
|
335
|
+
}
|
|
336
|
+
console.log();
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
341
|
+
// Command Registration
|
|
342
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Register the `kanon rosetta profiles` subcommand group.
|
|
346
|
+
*/
|
|
347
|
+
export function registerProfilesCommands(rosettaCmd: Command): void {
|
|
348
|
+
const profilesCmd = rosettaCmd
|
|
349
|
+
.command("profiles")
|
|
350
|
+
.description(
|
|
351
|
+
"List and validate configured acquisition and translation profiles",
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
profilesCmd
|
|
355
|
+
.command("list")
|
|
356
|
+
.description("List all configured profiles with sanitized metadata")
|
|
357
|
+
.option("--json", "Output as JSON")
|
|
358
|
+
.action((opts: ProfilesListOptions) => profilesListCommand(opts));
|
|
359
|
+
|
|
360
|
+
profilesCmd
|
|
361
|
+
.command("validate")
|
|
362
|
+
.description(
|
|
363
|
+
"Validate all profiles against the registry (exits nonzero on failure)",
|
|
364
|
+
)
|
|
365
|
+
.option("--json", "Output as JSON")
|
|
366
|
+
.action((opts: ProfilesValidateOptions) => profilesValidateCommand(opts));
|
|
367
|
+
}
|