@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
package/src/cli.ts ADDED
@@ -0,0 +1,773 @@
1
+ #!/usr/bin/env bun
2
+
3
+ if (typeof globalThis.Bun === "undefined") {
4
+ console.error(
5
+ "Error: Kanon requires Bun (https://bun.sh) to run.\n" +
6
+ "Install it with: curl -fsSL https://bun.sh/install | bash\n" +
7
+ "Then run: bunx @thinkingsage/kanon <command>",
8
+ );
9
+ process.exit(1);
10
+ }
11
+
12
+ import { writeFile } from "node:fs/promises";
13
+ import chalk from "chalk";
14
+ import { Command } from "commander";
15
+ import { runAttributionBackfill } from "./attribution-backfill";
16
+ import { renderAttributionReport } from "./attribution-report";
17
+ import { browseCommand, exportCommand } from "./browse";
18
+ import { buildCommand } from "./build";
19
+ import { catalogCommand, generateCatalog, SOURCE_DIRS } from "./catalog";
20
+ import {
21
+ collectionBuildCommand,
22
+ collectionNewCommand,
23
+ collectionStatusCommand,
24
+ } from "./collection-builder";
25
+ import { evalCommand } from "./eval";
26
+ import { registerGuildCommands } from "./guild/cli";
27
+ import { commandMetaRegistry } from "./help/metadata";
28
+ import {
29
+ type RootCommand,
30
+ renderCommandHelp,
31
+ renderRootHelp,
32
+ renderVersion,
33
+ } from "./help/renderer";
34
+ import { suggestCommand } from "./help/typo-suggester";
35
+ import { importCommand as kiroImportCommand } from "./import";
36
+ import { importCommand as multiHarnessImportCommand } from "./importers/index";
37
+ import { installCommand } from "./install";
38
+ import { newCommand } from "./new";
39
+ import { publishCommand } from "./publish";
40
+ import { registerRosettaCommands } from "./rosetta-cli";
41
+ import type { HarnessName } from "./schemas";
42
+ import { SUPPORTED_HARNESSES } from "./schemas";
43
+ import {
44
+ specClaimCommand,
45
+ specDoneCommand,
46
+ specHandoffCommand,
47
+ specListCommand,
48
+ specNextCommand,
49
+ specReconcileCommand,
50
+ specReleaseCommand,
51
+ specStatusCommand,
52
+ } from "./spec-coordination";
53
+ import {
54
+ formatComparisonOutput,
55
+ formatJsonOutput,
56
+ formatTerminalOutput,
57
+ renderComparison,
58
+ renderTemper,
59
+ startTemperServer,
60
+ } from "./temper";
61
+ import { tutorialCommand } from "./tutorial";
62
+ import { validateCommand } from "./validate";
63
+ import { upgradeCommand } from "./versioning";
64
+
65
+ // Banner lines — stored without trailing padding; printBanner normalises widths.
66
+ const bannerLines = [
67
+ "",
68
+ " ____ _ _ _ _ _____",
69
+ " / ___|| | _(_) | | | ___|__ _ __ __ _ ___",
70
+ " \\___ \\| |/ / | | | | |_ / _ \\| '__/ _` |/ _ \\",
71
+ " ___) | <| | | | | _| (_) | | | (_| | __/",
72
+ " |____/|_|\\_\\_|_|_| |_| \\___/|_| \\__, |\\___|",
73
+ " |___/",
74
+ " ⚡ author → catalog → harness ⚡",
75
+ "",
76
+ ];
77
+
78
+ // Orange → Cyan gradient mapped across the banner lines
79
+ const gradientSteps = [
80
+ [255, 140, 0], // orange
81
+ [255, 180, 40], // gold
82
+ [100, 220, 180], // teal
83
+ [0, 210, 255], // bright cyan
84
+ [0, 180, 255], // azure
85
+ [0, 160, 240], // mid blue
86
+ [0, 200, 255], // cyan
87
+ [80, 220, 240], // light cyan
88
+ [0, 210, 255], // bright cyan
89
+ ] as const;
90
+
91
+ /**
92
+ * Visual display width of a string — counts wide characters (emoji, CJK)
93
+ * as 2 columns. Handles the ⚡ in the tagline correctly.
94
+ */
95
+ function visualWidth(s: string): number {
96
+ let w = 0;
97
+ for (const cp of s) {
98
+ const code = cp.codePointAt(0) ?? 0;
99
+ // Wide: emoji, CJK, fullwidth ranges
100
+ const wide =
101
+ (code >= 0x1100 && code <= 0x115f) || // Hangul Jamo
102
+ (code >= 0x2600 && code <= 0x27bf) || // Misc symbols, Dingbats (⚡ is U+26A1)
103
+ (code >= 0x2e80 && code <= 0x303e) || // CJK radicals
104
+ (code >= 0x3040 && code <= 0xa4cf) || // CJK unified
105
+ (code >= 0xac00 && code <= 0xd7a3) || // Hangul syllables
106
+ (code >= 0xf900 && code <= 0xfaff) || // CJK compatibility
107
+ (code >= 0xfe10 && code <= 0xfe1f) || // Vertical forms
108
+ (code >= 0xfe30 && code <= 0xfe4f) || // CJK compatibility forms
109
+ (code >= 0xff00 && code <= 0xff60) || // Fullwidth forms
110
+ (code >= 0xffe0 && code <= 0xffe6) || // Fullwidth signs
111
+ (code >= 0x1f300 && code <= 0x1faff); // Emoji block
112
+ w += wide ? 2 : 1;
113
+ }
114
+ return w;
115
+ }
116
+
117
+ function printBanner() {
118
+ const bg = chalk.bgRgb(15, 20, 35);
119
+ const leadingMargin = 2; // left padding
120
+ const trailingMargin = 4; // right padding (more to visually balance the logo's left indent)
121
+ const maxContent = Math.max(...bannerLines.map(visualWidth));
122
+
123
+ for (let i = 0; i < bannerLines.length; i++) {
124
+ const [r, g, b] = gradientSteps[i % gradientSteps.length];
125
+ const line = bannerLines[i];
126
+ const trailing = " ".repeat(
127
+ maxContent - visualWidth(line) + trailingMargin,
128
+ );
129
+ console.log(
130
+ bg(
131
+ chalk
132
+ .rgb(r, g, b)
133
+ .bold(`${" ".repeat(leadingMargin)}${line}${trailing}`),
134
+ ),
135
+ );
136
+ }
137
+ console.log();
138
+ }
139
+
140
+ /** Compact plain-text banner for log files — no ANSI, no emoji. */
141
+ export function logBanner(): string {
142
+ const ts = new Date().toISOString();
143
+ return ["--- kanon v0.4.1 ---", `started: ${ts}`, "-".repeat(26)].join("\n");
144
+ }
145
+
146
+ // Detect --no-color early and disable chalk styling
147
+ const useColor = !process.argv.includes("--no-color");
148
+ if (!useColor) {
149
+ chalk.level = 0;
150
+ }
151
+
152
+ // Rewrite trailing "help" to "--help" so `kanon build help` shows the help screen
153
+ // instead of erroring with "too many arguments".
154
+ if (
155
+ process.argv.length >= 3 &&
156
+ process.argv[process.argv.length - 1] === "help"
157
+ ) {
158
+ // Only rewrite when "help" isn't the registered subcommand itself (argv[2])
159
+ if (process.argv[2] !== "help") {
160
+ process.argv[process.argv.length - 1] = "--help";
161
+ }
162
+ }
163
+
164
+ const hasHelpFlag =
165
+ process.argv.includes("--help") || process.argv.includes("-h");
166
+ const hasHelpCommand = process.argv[2] === "help";
167
+ if (process.argv.length <= 2 && !hasHelpFlag && !hasHelpCommand) {
168
+ printBanner();
169
+ }
170
+
171
+ // Only parse CLI when run directly (not when imported for logBanner)
172
+ if (import.meta.main !== false) {
173
+ const program = new Command()
174
+ .name("kanon")
175
+ .description("Kanon — write knowledge once, compile to every harness");
176
+
177
+ program
178
+ .command("build")
179
+ .description("Compile knowledge artifacts to harness-native formats")
180
+ .option("--harness <name>", "Build for a single harness only")
181
+ .option("--strict", "Treat compatibility warnings as errors")
182
+ .action(buildCommand);
183
+
184
+ program
185
+ .command("install [artifact]")
186
+ .description("Install compiled artifacts into the current project")
187
+ .option("--harness <name>", "Install for a specific harness")
188
+ .option("--all", "Install for all harnesses")
189
+ .option("--force", "Overwrite without confirmation")
190
+ .option("--dry-run", "Show what would be installed without writing files")
191
+ .option("--source <path>", "Path to kanon repository")
192
+ .option("--from-release <tag>", "Download from GitHub release")
193
+ .option("--backend <name>", "Named backend from kanon.config.yaml")
194
+ .option("--global", "Install artifact into the global cache")
195
+ .option("--project <name>", "Install into a specific workspace project")
196
+ .option(
197
+ "--max-always <N>",
198
+ "Max number of always-mode Kiro steering files to install (-1 = no limit)",
199
+ (val: string) => parseInt(val, 10),
200
+ )
201
+ .action(installCommand);
202
+
203
+ program
204
+ .command("new <artifact-name>")
205
+ .description("Scaffold a new knowledge artifact")
206
+ .option("--yes", "Skip interactive wizard, use template defaults")
207
+ .option(
208
+ "--type <type>",
209
+ "Asset type: skill, power, rule, workflow, agent, prompt, template, reference-pack",
210
+ )
211
+ .action(newCommand);
212
+
213
+ program
214
+ .command("tutorial")
215
+ .description("Guided walkthrough for first-time artifact authors")
216
+ .action(tutorialCommand);
217
+
218
+ program
219
+ .command("validate [artifact-path]")
220
+ .description("Validate knowledge artifacts")
221
+ .option(
222
+ "--security",
223
+ "Run additional security checks (prompt injection, dangerous hooks, obfuscation)",
224
+ )
225
+ .action((artifactPath, options) => validateCommand(artifactPath, options));
226
+
227
+ const catalogCmd = program
228
+ .command("catalog")
229
+ .description("Manage the artifact catalog");
230
+
231
+ catalogCmd
232
+ .command("generate")
233
+ .description("Generate catalog.json")
234
+ .action(catalogCommand);
235
+
236
+ catalogCmd
237
+ .command("browse")
238
+ .description("Browse the artifact catalog in your browser")
239
+ .option("--port <number>", "Port to serve on", "3131")
240
+ .option(
241
+ "--all",
242
+ "Include unlisted artifacts in the listing (private remain hidden)",
243
+ )
244
+ .action(browseCommand);
245
+
246
+ catalogCmd
247
+ .command("export")
248
+ .description(
249
+ "Export a self-contained static catalog site for GitHub Pages or any static host",
250
+ )
251
+ .option(
252
+ "--output <dir>",
253
+ "Output directory for index.html and catalog.json",
254
+ "dist/web",
255
+ )
256
+ .action(exportCommand);
257
+
258
+ const collectionCmd = program
259
+ .command("collection")
260
+ .description("Manage knowledge collections")
261
+ .allowExcessArguments(true)
262
+ .action(collectionStatusCommand);
263
+
264
+ collectionCmd
265
+ .command("new [name]")
266
+ .description("Scaffold a new collection manifest")
267
+ .action(collectionNewCommand);
268
+
269
+ collectionCmd
270
+ .command("build")
271
+ .description("Build collection bundles from dist artifacts")
272
+ .option("--harness <name>", "Build for a single harness only")
273
+ .action(collectionBuildCommand);
274
+
275
+ program
276
+ .command("import [path]")
277
+ .description(
278
+ "Import knowledge artifacts from an external source (Kiro powers, skills, or harness-native files)",
279
+ )
280
+ .option("--all", "Import all artifact subdirectories within <path>")
281
+ .option(
282
+ "--format <format>",
283
+ "Source format: kiro-power, kiro-skill, superpowers (default: auto-detect)",
284
+ )
285
+ .option("--harness <name>", "Scan for and import harness-native files")
286
+ .option("--force", "Overwrite existing artifacts without confirmation")
287
+ .option("--dry-run", "Show what would be imported without writing files")
288
+ .option(
289
+ "--collections <names>",
290
+ "Comma-separated collection names to assign to imported artifacts",
291
+ )
292
+ .option(
293
+ "--knowledge-dir <dir>",
294
+ "Target knowledge directory (default: knowledge)",
295
+ )
296
+ .option(
297
+ "--attribution-defaults",
298
+ "Accept derived upstream attribution without prompting (relationship=verbatim)",
299
+ )
300
+ .option(
301
+ "--no-attribution",
302
+ "Skip attribution capture; write no attribution block",
303
+ )
304
+ .action(async (path, options) => {
305
+ // If --harness is provided or no path argument, use multi-harness import
306
+ if (options.harness || !path) {
307
+ await multiHarnessImportCommand({
308
+ harness: options.harness as HarnessName | undefined,
309
+ force: options.force,
310
+ dryRun: options.dryRun,
311
+ knowledgeDir: options.knowledgeDir,
312
+ });
313
+ } else {
314
+ // Delegate to existing Kiro import (path-based)
315
+ await kiroImportCommand(path, options);
316
+ }
317
+ });
318
+
319
+ const attributeCmd = program
320
+ .command("attribute")
321
+ .description(
322
+ "Generate a NOTICES report of upstream attribution, grouped by license",
323
+ )
324
+ .option(
325
+ "--output <file>",
326
+ "Write the report to a file instead of stdout (e.g. NOTICES)",
327
+ )
328
+ .action(async (options) => {
329
+ const entries = await generateCatalog([...SOURCE_DIRS]);
330
+ const report = renderAttributionReport(entries);
331
+ if (options.output) {
332
+ await writeFile(String(options.output), report, "utf-8");
333
+ console.error(
334
+ chalk.green(`✓ Wrote attribution report to ${options.output}`),
335
+ );
336
+ } else {
337
+ console.log(report);
338
+ }
339
+ });
340
+
341
+ attributeCmd
342
+ .command("backfill")
343
+ .description(
344
+ "Backfill attribution blocks for imported artifacts (author untouched)",
345
+ )
346
+ .option("--dry-run", "Classify and report without writing any file")
347
+ .action(async (options) => {
348
+ const summary = await runAttributionBackfill({
349
+ knowledgeDirs: [...SOURCE_DIRS],
350
+ dryRun: Boolean(options.dryRun),
351
+ });
352
+ const verb = options.dryRun ? "would backfill" : "backfilled";
353
+ console.error("");
354
+ console.error(
355
+ ` ${chalk.green(`${summary.clean} ${verb} clean`)}, ` +
356
+ `${chalk.yellow(`${summary.manualReview} need manual review`)}, ` +
357
+ `${chalk.dim(`${summary.skipInHouse} in-house`)}, ` +
358
+ `${chalk.dim(`${summary.skipHasAttribution} already attributed`)}`,
359
+ );
360
+ for (const plan of summary.plans) {
361
+ if (plan.classification === "manual-review") {
362
+ console.error(
363
+ chalk.yellow(
364
+ ` ⚠ ${plan.name} — ${plan.reason ?? "manual review"}`,
365
+ ),
366
+ );
367
+ }
368
+ }
369
+ console.error("");
370
+ });
371
+
372
+ program
373
+ .command("publish")
374
+ .description(
375
+ "Publish compiled artifacts to a release backend (GitHub, S3, or HTTP)",
376
+ )
377
+ .option(
378
+ "--backend <name>",
379
+ "Named backend from kanon.config.yaml (default: github)",
380
+ )
381
+ .option(
382
+ "--tag <version>",
383
+ "Release tag, e.g. v1.2.0 (default: package.json version)",
384
+ )
385
+ .option("--dry-run", "Validate and package without uploading")
386
+ .option("--notes <file>", "Markdown file to use as release notes")
387
+ .action(publishCommand);
388
+
389
+ program
390
+ .command("eval [artifact]")
391
+ .description("Run eval tests against compiled artifacts")
392
+ .option("--harness <name>", "Run evals for a specific harness only")
393
+ .option(
394
+ "--rubric <name>",
395
+ "Run a named rubric (default: progressive-steering for kiro harness)",
396
+ )
397
+ .option(
398
+ "--build <dir>",
399
+ "Point the rubric grader at an already-compiled build directory",
400
+ )
401
+ .option(
402
+ "--workload <path>",
403
+ "Workload JSON for the rubric grader (default: the scenario matching --build)",
404
+ )
405
+ .option("--json", "Output rubric result as canonical JSON")
406
+ .option("--threshold <score>", "Minimum passing score (0.0–1.0)", "0.7")
407
+ .option("--output <path>", "Write detailed results as JSON")
408
+ .option("--ci", "Machine-readable output for CI pipelines")
409
+ .option("--provider <name>", "Run against a single provider")
410
+ .option("--no-context", "Skip harness context wrapping")
411
+ .option("--init <artifact>", "Scaffold eval suite for an artifact")
412
+ .option(
413
+ "--record",
414
+ "Append results to evals/history.jsonl for trend tracking",
415
+ )
416
+ .option("--trend", "Show score progression from evals/history.jsonl")
417
+ .option("--mutation", "Run mutation testing on adapter source files")
418
+ .option(
419
+ "--delta",
420
+ "Only mutate files changed since last mutation run (nightly-delta strategy)",
421
+ )
422
+ .action(evalCommand);
423
+
424
+ program
425
+ .command("upgrade")
426
+ .description("Upgrade installed artifacts to their latest versions")
427
+ .option("--force", "Upgrade without confirmation prompts")
428
+ .option("--dry-run", "Show what would be upgraded without modifying files")
429
+ .option(
430
+ "--project <name>",
431
+ "Upgrade only within a specific workspace project",
432
+ )
433
+ .action(async (options) => {
434
+ try {
435
+ await upgradeCommand({
436
+ force: options.force,
437
+ dryRun: options.dryRun,
438
+ project: options.project,
439
+ });
440
+ } catch (err: unknown) {
441
+ const msg = err instanceof Error ? err.message : String(err);
442
+ console.error(chalk.red(`Error: ${msg}`));
443
+ console.error(
444
+ chalk.dim(
445
+ " Run `kanon install` to install artifacts first, then retry.",
446
+ ),
447
+ );
448
+ process.exit(1);
449
+ }
450
+ });
451
+
452
+ program
453
+ .command("temper <artifact>")
454
+ .description(
455
+ "Preview the compiled AI experience for an artifact-harness pair",
456
+ )
457
+ .option("--harness <name>", "Target harness (default: kiro)")
458
+ .option("--compare", "Compare artifact across all targeted harnesses")
459
+ .option("--web", "Open interactive web preview in browser")
460
+ .option("--json", "Output as JSON conforming to TemperOutputSchema")
461
+ .option("--no-color", "Disable color output for deterministic results")
462
+ .action(async (artifact, options) => {
463
+ const harness = (options.harness ?? "kiro") as HarnessName;
464
+
465
+ if (!SUPPORTED_HARNESSES.includes(harness)) {
466
+ console.error(chalk.red(`Error: Unknown harness "${harness}".`));
467
+ console.error(
468
+ chalk.dim(` Supported harnesses: ${SUPPORTED_HARNESSES.join(", ")}`),
469
+ );
470
+ process.exit(1);
471
+ }
472
+
473
+ try {
474
+ if (options.compare) {
475
+ const result = await renderComparison({
476
+ artifactName: artifact,
477
+ harnesses: [...SUPPORTED_HARNESSES],
478
+ });
479
+ console.log(formatComparisonOutput(result, !options.color));
480
+ } else if (options.web) {
481
+ const output = await renderTemper({
482
+ artifactName: artifact,
483
+ harness,
484
+ });
485
+ await startTemperServer(output);
486
+ } else {
487
+ const output = await renderTemper({
488
+ artifactName: artifact,
489
+ harness,
490
+ noColor: !options.color,
491
+ });
492
+ if (options.json) {
493
+ console.log(formatJsonOutput(output));
494
+ } else {
495
+ console.log(formatTerminalOutput(output, !options.color));
496
+ }
497
+ }
498
+ } catch (err: unknown) {
499
+ const msg = err instanceof Error ? err.message : String(err);
500
+ console.error(chalk.red(`Error: ${msg}`));
501
+ console.error(
502
+ chalk.dim(
503
+ " Run `kanon catalog generate` to see available artifacts.",
504
+ ),
505
+ );
506
+ process.exit(1);
507
+ }
508
+ });
509
+
510
+ // Kiro Spec coordination — read/write COORDINATION.md + tasks.md across agents
511
+ const specCmd = program
512
+ .command("spec")
513
+ .description(
514
+ "Coordinate multi-agent work on Kiro Specs (.kiro/specs/) via COORDINATION.md",
515
+ )
516
+ .action(() => specListCommand());
517
+
518
+ specCmd
519
+ .command("list")
520
+ .description("List specs with type, workflow, and task progress")
521
+ .option("--json", "Output as JSON")
522
+ .action((options) => specListCommand(options));
523
+
524
+ specCmd
525
+ .command("status [spec]")
526
+ .description("Show task ownership, progress, deps, leases, and handoffs")
527
+ .option("--json", "Output as JSON")
528
+ .action((spec, options) => specStatusCommand(spec, options));
529
+
530
+ specCmd
531
+ .command("next [spec]")
532
+ .alias("channel")
533
+ .description(
534
+ "Select and claim the next actionable task (deps satisfied, unclaimed) for an agent",
535
+ )
536
+ .option("--agent <name>", "Agent requesting work")
537
+ .option("--lease <minutes>", "Lease duration in minutes")
538
+ .option("--dry-run", "Report the next task without claiming it")
539
+ .option("--json", "Output as JSON")
540
+ .action((spec, options) => specNextCommand(spec, options));
541
+
542
+ specCmd
543
+ .command("claim [spec] [taskId]")
544
+ .description(
545
+ "Claim a task for an agent (fails if already owned or blocked)",
546
+ )
547
+ .option("--agent <name>", "Agent claiming the task")
548
+ .option("--force", "Take over a task already owned by another agent")
549
+ .option("--lease <minutes>", "Lease duration in minutes")
550
+ .option("--ignore-deps", "Claim even if dependencies are unmet")
551
+ .action((spec, taskId, options) => specClaimCommand(spec, taskId, options));
552
+
553
+ specCmd
554
+ .command("release [spec] [taskId]")
555
+ .description("Release a claimed task back to open")
556
+ .action((spec, taskId) => specReleaseCommand(spec, taskId));
557
+
558
+ specCmd
559
+ .command("done [spec] [taskId]")
560
+ .description(
561
+ "Mark a task complete: check the box in tasks.md and update coordination",
562
+ )
563
+ .option("--agent <name>", "Agent completing the task")
564
+ .action((spec, taskId, options) => specDoneCommand(spec, taskId, options));
565
+
566
+ specCmd
567
+ .command("reconcile [spec]")
568
+ .description(
569
+ "Sync coordination rows to tasks.md checkboxes (adds new, marks done)",
570
+ )
571
+ .action(specReconcileCommand);
572
+
573
+ specCmd
574
+ .command("handoff [spec] [message]")
575
+ .description("Record a handoff note from one agent to another")
576
+ .option("--from <agent>", "Agent handing off")
577
+ .option("--to <agent>", "Agent receiving")
578
+ .action((spec, message, options) =>
579
+ specHandoffCommand(spec, message, options),
580
+ );
581
+
582
+ // Register guild commands
583
+ registerGuildCommands(program);
584
+
585
+ // Register rosetta commands
586
+ registerRosettaCommands(program);
587
+
588
+ // Register `kanon help [command]` subcommand
589
+ program
590
+ .command("help [command]")
591
+ .description("Show help for a command")
592
+ .action((cmdName?: string) => {
593
+ if (!cmdName) {
594
+ // No argument — show root help
595
+ const commands = program.commands
596
+ .filter((c) => c.name() !== "help")
597
+ .map((cmd) => ({
598
+ name: cmd.name() + (cmd.usage() ? ` ${cmd.usage()}` : ""),
599
+ description: cmd.description(),
600
+ }));
601
+ commands.push({
602
+ name: "help [command]",
603
+ description: "Show help for a command",
604
+ });
605
+ console.log(renderRootHelp(commands, { useColor }));
606
+ return;
607
+ }
608
+
609
+ // Find matching command
610
+ const targetCmd = program.commands.find((c) => c.name() === cmdName);
611
+ if (targetCmd) {
612
+ console.log(targetCmd.helpInformation());
613
+ return;
614
+ }
615
+
616
+ // Unknown command — suggest typo fix
617
+ const validNames = program.commands
618
+ .filter((c) => c.name() !== "help")
619
+ .map((c) => c.name());
620
+ const suggestion = suggestCommand(cmdName, validNames);
621
+
622
+ console.error(`error: unknown command '${cmdName}'`);
623
+ if (suggestion) {
624
+ console.error(`Did you mean "${suggestion}"?`);
625
+ }
626
+ console.error("");
627
+ console.error("Available commands:");
628
+ for (const name of validNames) {
629
+ console.error(` ${name}`);
630
+ }
631
+ process.exit(1);
632
+ });
633
+
634
+ // Custom version option using renderVersion
635
+ program.option("-V, --version", "Output version information");
636
+ program.on("option:version", () => {
637
+ console.log(renderVersion("0.2.0", { useColor }));
638
+ process.exit(0);
639
+ });
640
+
641
+ // Override helpInformation() on the root program
642
+ program.helpInformation = () => {
643
+ const commands: RootCommand[] = program.commands
644
+ .filter((cmd) => cmd.name() !== "help")
645
+ .map((cmd) => {
646
+ // Strip [options] from usage — it's noise at the overview level
647
+ const rawUsage = cmd.usage() ?? "";
648
+ const cleanUsage = rawUsage
649
+ .replace(/\[options\]\s*/g, "")
650
+ .replace(/\[options\]$/g, "")
651
+ .trim();
652
+ const name = cleanUsage ? `${cmd.name()} ${cleanUsage}` : cmd.name();
653
+
654
+ const subcommands =
655
+ cmd.commands.length > 0
656
+ ? cmd.commands.map((sub) => ({
657
+ name: sub.name(),
658
+ description: sub.description(),
659
+ }))
660
+ : undefined;
661
+
662
+ return { name, description: cmd.description(), subcommands };
663
+ });
664
+ // Add help at the end
665
+ commands.push({
666
+ name: "help [command]",
667
+ description: "Show help for a command",
668
+ });
669
+ return renderRootHelp(commands, { useColor });
670
+ };
671
+
672
+ // Override helpInformation() on each subcommand
673
+ for (const cmd of program.commands) {
674
+ const cmdName = cmd.name();
675
+ cmd.helpInformation = () => {
676
+ const opts = cmd.options.map((o) => ({
677
+ flags: o.flags,
678
+ description: o.description,
679
+ }));
680
+ const meta = commandMetaRegistry[cmdName];
681
+ const subs =
682
+ cmd.commands.length > 0
683
+ ? cmd.commands.map((sub) => ({
684
+ name: sub.name(),
685
+ description: sub.description(),
686
+ }))
687
+ : undefined;
688
+ return renderCommandHelp(
689
+ cmdName,
690
+ cmd.description(),
691
+ `kanon ${cmdName} ${cmd.usage()}`.trim(),
692
+ opts,
693
+ meta,
694
+ { useColor },
695
+ subs,
696
+ );
697
+ };
698
+
699
+ // Handle subcommands (e.g., catalog generate, catalog browse)
700
+ if (cmd.commands && cmd.commands.length > 0) {
701
+ for (const sub of cmd.commands) {
702
+ const subName = `${cmdName} ${sub.name()}`;
703
+ sub.helpInformation = () => {
704
+ const opts = sub.options.map((o) => ({
705
+ flags: o.flags,
706
+ description: o.description,
707
+ }));
708
+ const meta = commandMetaRegistry[subName];
709
+ const nestedSubs =
710
+ sub.commands.length > 0
711
+ ? sub.commands.map((s) => ({
712
+ name: s.name(),
713
+ description: s.description(),
714
+ }))
715
+ : undefined;
716
+ return renderCommandHelp(
717
+ subName,
718
+ sub.description(),
719
+ `kanon ${subName} ${sub.usage()}`.trim(),
720
+ opts,
721
+ meta,
722
+ { useColor },
723
+ nestedSubs,
724
+ );
725
+ };
726
+
727
+ // Handle third-level subcommands (e.g., guild hook install)
728
+ if (sub.commands && sub.commands.length > 0) {
729
+ for (const nested of sub.commands) {
730
+ const nestedName = `${subName} ${nested.name()}`;
731
+ nested.helpInformation = () => {
732
+ const opts = nested.options.map((o) => ({
733
+ flags: o.flags,
734
+ description: o.description,
735
+ }));
736
+ const meta = commandMetaRegistry[nestedName];
737
+ return renderCommandHelp(
738
+ nestedName,
739
+ nested.description(),
740
+ `kanon ${nestedName} ${nested.usage()}`.trim(),
741
+ opts,
742
+ meta,
743
+ { useColor },
744
+ );
745
+ };
746
+ }
747
+ }
748
+ }
749
+ }
750
+ }
751
+
752
+ // Handle unknown commands with typo suggestions
753
+ program.on("command:*", (operands: string[]) => {
754
+ const unknown = operands[0];
755
+ const validNames = program.commands
756
+ .filter((c) => c.name() !== "help")
757
+ .map((c) => c.name());
758
+ const suggestion = suggestCommand(unknown, validNames);
759
+
760
+ console.error(`error: unknown command '${unknown}'`);
761
+ if (suggestion) {
762
+ console.error(`Did you mean "${suggestion}"?`);
763
+ }
764
+ console.error("");
765
+ console.error("Available commands:");
766
+ for (const name of validNames) {
767
+ console.error(` ${name}`);
768
+ }
769
+ process.exit(1);
770
+ });
771
+
772
+ program.parse();
773
+ }