@thinkingsage/kanon 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. package/CHANGELOG.md +410 -0
  2. package/LICENSE +21 -0
  3. package/README.md +168 -0
  4. package/bridge/mcp-server.cjs +14171 -0
  5. package/package.json +98 -0
  6. package/src/adapters/capabilities.ts +178 -0
  7. package/src/adapters/claude-code.ts +110 -0
  8. package/src/adapters/cline.ts +98 -0
  9. package/src/adapters/codex.ts +173 -0
  10. package/src/adapters/copilot.ts +106 -0
  11. package/src/adapters/cursor.ts +97 -0
  12. package/src/adapters/degradation.ts +95 -0
  13. package/src/adapters/index.ts +324 -0
  14. package/src/adapters/kiro-frontmatter.ts +139 -0
  15. package/src/adapters/kiro-inclusion.ts +86 -0
  16. package/src/adapters/kiro.ts +412 -0
  17. package/src/adapters/qdeveloper.ts +115 -0
  18. package/src/adapters/types.ts +81 -0
  19. package/src/adapters/windsurf.ts +96 -0
  20. package/src/admin.ts +283 -0
  21. package/src/asset-conventions.ts +118 -0
  22. package/src/attribution-backfill.ts +319 -0
  23. package/src/attribution-report.ts +95 -0
  24. package/src/attribution.ts +239 -0
  25. package/src/backends/github.ts +194 -0
  26. package/src/backends/http.ts +122 -0
  27. package/src/backends/index.ts +39 -0
  28. package/src/backends/local.ts +47 -0
  29. package/src/backends/s3.ts +157 -0
  30. package/src/backends/types.ts +59 -0
  31. package/src/base-cache.ts +270 -0
  32. package/src/browse-ui.ts +3754 -0
  33. package/src/browse.ts +1038 -0
  34. package/src/build.ts +1108 -0
  35. package/src/catalog.ts +204 -0
  36. package/src/cli-deprecated.ts +29 -0
  37. package/src/cli.ts +773 -0
  38. package/src/collection-admin.ts +287 -0
  39. package/src/collection-builder.ts +464 -0
  40. package/src/collections.ts +116 -0
  41. package/src/compatibility.ts +105 -0
  42. package/src/config.ts +743 -0
  43. package/src/eval/rubrics/kiro-progressive-steering.ts +841 -0
  44. package/src/eval.ts +1169 -0
  45. package/src/file-writer.ts +61 -0
  46. package/src/format-registry.ts +141 -0
  47. package/src/guild/auto-updater.ts +163 -0
  48. package/src/guild/backend-resolver.ts +49 -0
  49. package/src/guild/cli.ts +592 -0
  50. package/src/guild/collection-expander.ts +47 -0
  51. package/src/guild/global-cache.ts +247 -0
  52. package/src/guild/hook-generator.ts +100 -0
  53. package/src/guild/manifest.ts +154 -0
  54. package/src/guild/path-utils.ts +12 -0
  55. package/src/guild/sync.ts +622 -0
  56. package/src/guild/version-resolver.ts +42 -0
  57. package/src/help/metadata.ts +445 -0
  58. package/src/help/renderer.ts +265 -0
  59. package/src/help/typo-suggester.ts +25 -0
  60. package/src/hooks/expression.ts +493 -0
  61. package/src/hooks/pipeline.ts +141 -0
  62. package/src/import.ts +773 -0
  63. package/src/importers/claude-code.ts +134 -0
  64. package/src/importers/cline.ts +103 -0
  65. package/src/importers/codex.ts +140 -0
  66. package/src/importers/copilot.ts +103 -0
  67. package/src/importers/cursor.ts +105 -0
  68. package/src/importers/index.ts +390 -0
  69. package/src/importers/kiro.ts +110 -0
  70. package/src/importers/qdeveloper.ts +103 -0
  71. package/src/importers/types.ts +54 -0
  72. package/src/importers/windsurf.ts +104 -0
  73. package/src/install.ts +1005 -0
  74. package/src/manifest-admin.ts +306 -0
  75. package/src/mcp-bridge.ts +240 -0
  76. package/src/mutation/delta.ts +50 -0
  77. package/src/mutation/history.ts +66 -0
  78. package/src/mutation/operators.ts +524 -0
  79. package/src/mutation/runner.ts +332 -0
  80. package/src/new.ts +106 -0
  81. package/src/outcomes/collision.ts +127 -0
  82. package/src/outcomes/normalize.ts +208 -0
  83. package/src/outcomes/registry.ts +173 -0
  84. package/src/parser.ts +446 -0
  85. package/src/provenance-backfill-cli.ts +319 -0
  86. package/src/provenance-backfill.ts +520 -0
  87. package/src/publish.ts +354 -0
  88. package/src/reconcile-orchestrator.ts +502 -0
  89. package/src/reconcile-report-renderer.ts +176 -0
  90. package/src/resolve-body.ts +15 -0
  91. package/src/rosetta/builtins/compatibility-profiles.ts +297 -0
  92. package/src/rosetta/builtins/contracts.ts +1033 -0
  93. package/src/rosetta/builtins/pretty-printers/claude-code-native.ts +122 -0
  94. package/src/rosetta/builtins/pretty-printers/cline-native.ts +50 -0
  95. package/src/rosetta/builtins/pretty-printers/codex-native.ts +127 -0
  96. package/src/rosetta/builtins/pretty-printers/copilot-native.ts +50 -0
  97. package/src/rosetta/builtins/pretty-printers/cursor-native.ts +50 -0
  98. package/src/rosetta/builtins/pretty-printers/index.ts +81 -0
  99. package/src/rosetta/builtins/pretty-printers/kiro-native.ts +166 -0
  100. package/src/rosetta/builtins/pretty-printers/kiro-power.ts +108 -0
  101. package/src/rosetta/builtins/pretty-printers/kiro-skill.ts +88 -0
  102. package/src/rosetta/builtins/pretty-printers/qdeveloper-native.ts +51 -0
  103. package/src/rosetta/builtins/pretty-printers/superpowers.ts +97 -0
  104. package/src/rosetta/builtins/pretty-printers/windsurf-native.ts +50 -0
  105. package/src/rosetta/builtins/sources/claude-code-native.ts +348 -0
  106. package/src/rosetta/builtins/sources/cline-native.ts +176 -0
  107. package/src/rosetta/builtins/sources/codex-native.ts +343 -0
  108. package/src/rosetta/builtins/sources/copilot-native.ts +178 -0
  109. package/src/rosetta/builtins/sources/cursor-native.ts +176 -0
  110. package/src/rosetta/builtins/sources/index.ts +95 -0
  111. package/src/rosetta/builtins/sources/kiro-native.ts +462 -0
  112. package/src/rosetta/builtins/sources/kiro-power.ts +285 -0
  113. package/src/rosetta/builtins/sources/kiro-skill.ts +230 -0
  114. package/src/rosetta/builtins/sources/qdeveloper-native.ts +181 -0
  115. package/src/rosetta/builtins/sources/superpowers.ts +240 -0
  116. package/src/rosetta/builtins/sources/windsurf-native.ts +176 -0
  117. package/src/rosetta/builtins/targets/claude-code.ts +181 -0
  118. package/src/rosetta/builtins/targets/cline.ts +87 -0
  119. package/src/rosetta/builtins/targets/codex.ts +226 -0
  120. package/src/rosetta/builtins/targets/copilot.ts +103 -0
  121. package/src/rosetta/builtins/targets/cursor.ts +87 -0
  122. package/src/rosetta/builtins/targets/index.ts +60 -0
  123. package/src/rosetta/builtins/targets/kiro.ts +278 -0
  124. package/src/rosetta/builtins/targets/qdeveloper.ts +103 -0
  125. package/src/rosetta/builtins/targets/windsurf.ts +87 -0
  126. package/src/rosetta/canonical.ts +729 -0
  127. package/src/rosetta/compatibility.ts +432 -0
  128. package/src/rosetta/contracts.ts +329 -0
  129. package/src/rosetta/detector.ts +724 -0
  130. package/src/rosetta/diagnostics.ts +630 -0
  131. package/src/rosetta/engine-bootstrap.ts +103 -0
  132. package/src/rosetta/engine.ts +744 -0
  133. package/src/rosetta/index.ts +381 -0
  134. package/src/rosetta/inspection.ts +530 -0
  135. package/src/rosetta/plan.ts +448 -0
  136. package/src/rosetta/provenance-digest.ts +369 -0
  137. package/src/rosetta/reconcile.ts +812 -0
  138. package/src/rosetta/redaction.ts +467 -0
  139. package/src/rosetta/registry.ts +712 -0
  140. package/src/rosetta/renderers.ts +571 -0
  141. package/src/rosetta/request-guard.ts +335 -0
  142. package/src/rosetta/resolution.ts +419 -0
  143. package/src/rosetta/source-accounting.ts +233 -0
  144. package/src/rosetta/templates.ts +129 -0
  145. package/src/rosetta-cli.ts +717 -0
  146. package/src/rosetta-docs-generator.ts +793 -0
  147. package/src/rosetta-profiles-cli.ts +367 -0
  148. package/src/schemas.ts +1712 -0
  149. package/src/spec-coordination.ts +1141 -0
  150. package/src/temper.ts +747 -0
  151. package/src/template-bundle-loader.ts +312 -0
  152. package/src/template-engine.ts +53 -0
  153. package/src/translation-application-policy.ts +496 -0
  154. package/src/translation-orchestrator.ts +1013 -0
  155. package/src/translation-plan-applier.ts +473 -0
  156. package/src/tutorial.ts +305 -0
  157. package/src/validate.ts +1093 -0
  158. package/src/versioning.ts +553 -0
  159. package/src/wizard.ts +660 -0
  160. package/src/workspace.ts +237 -0
  161. package/templates/eval-contexts/claude-code.md.njk +6 -0
  162. package/templates/eval-contexts/cline.md.njk +6 -0
  163. package/templates/eval-contexts/copilot.md.njk +6 -0
  164. package/templates/eval-contexts/cursor.md.njk +6 -0
  165. package/templates/eval-contexts/kiro.md.njk +10 -0
  166. package/templates/eval-contexts/qdeveloper.md.njk +6 -0
  167. package/templates/eval-contexts/windsurf.md.njk +6 -0
  168. package/templates/harness-adapters/_base/attribution-footer.md.njk +17 -0
  169. package/templates/harness-adapters/_base/base.md.njk +16 -0
  170. package/templates/harness-adapters/claude-code/claude.md.njk +1 -0
  171. package/templates/harness-adapters/claude-code/mcp.json.njk +1 -0
  172. package/templates/harness-adapters/claude-code/settings.json.njk +1 -0
  173. package/templates/harness-adapters/claude-code/skill-library-index.md.njk +13 -0
  174. package/templates/harness-adapters/claude-code/skill.md.njk +19 -0
  175. package/templates/harness-adapters/cline/hook.sh.njk +4 -0
  176. package/templates/harness-adapters/cline/mcp.json.njk +1 -0
  177. package/templates/harness-adapters/cline/rule.md.njk +1 -0
  178. package/templates/harness-adapters/codex/agents-md.md.njk +6 -0
  179. package/templates/harness-adapters/codex/agents-pointer.md.njk +16 -0
  180. package/templates/harness-adapters/codex/skill.md.njk +27 -0
  181. package/templates/harness-adapters/copilot/agents.md.njk +1 -0
  182. package/templates/harness-adapters/copilot/instructions.md.njk +1 -0
  183. package/templates/harness-adapters/copilot/scoped.md.njk +6 -0
  184. package/templates/harness-adapters/cursor/mcp.json.njk +1 -0
  185. package/templates/harness-adapters/cursor/rule.md.njk +6 -0
  186. package/templates/harness-adapters/kiro/hook.json.njk +1 -0
  187. package/templates/harness-adapters/kiro/mcp.json.njk +1 -0
  188. package/templates/harness-adapters/kiro/power-steering.md.njk +3 -0
  189. package/templates/harness-adapters/kiro/power.md.njk +12 -0
  190. package/templates/harness-adapters/kiro/steering.md.njk +16 -0
  191. package/templates/harness-adapters/qdeveloper/agent.md.njk +1 -0
  192. package/templates/harness-adapters/qdeveloper/mcp.json.njk +1 -0
  193. package/templates/harness-adapters/qdeveloper/rule.md.njk +1 -0
  194. package/templates/harness-adapters/windsurf/mcp.json.njk +1 -0
  195. package/templates/harness-adapters/windsurf/rule.md.njk +1 -0
  196. package/templates/harness-adapters/windsurf/workflow.md.njk +1 -0
  197. package/templates/knowledge/hooks.yaml.njk +4 -0
  198. package/templates/knowledge/knowledge.md.njk +53 -0
  199. package/templates/knowledge/mcp-servers.yaml.njk +2 -0
@@ -0,0 +1,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
+ }