@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,319 @@
1
+ /**
2
+ * Provenance Backfill — CLI Command Handler
3
+ *
4
+ * `kanon rosetta backfill [upstream]` is the one-shot command that records
5
+ * `ProvenanceRecord`s for existing distilled artifacts (ADR-0049). It matches
6
+ * each distilled artifact to the current upstream by NAME, records the current
7
+ * `baseDigest`, writes the machine-managed provenance block into `knowledge.md`,
8
+ * and seeds the git-ignored base cache. Supports `--dry-run` to report what it
9
+ * would backfill without writing.
10
+ *
11
+ * This handler is the orchestration shell: it resolves config, locates the
12
+ * upstream source root and distilled knowledge directory, resolves the source
13
+ * revision, then delegates the per-artifact work to `backfillUpstream`
14
+ * (src/provenance-backfill.ts), which composes the pure Rosetta Stone helpers.
15
+ *
16
+ * Requirements: 18.1, 18.9
17
+ */
18
+
19
+ import { exists } from "node:fs/promises";
20
+ import { isAbsolute, resolve } from "node:path";
21
+ import chalk from "chalk";
22
+ import type { Command } from "commander";
23
+ import { loadForgeConfig, type UpstreamConfig } from "./config";
24
+ import { loadKnowledgeArtifact } from "./parser";
25
+ import {
26
+ type BackfillResult,
27
+ type BackfillSourceFormat,
28
+ type BackfillUpstreamOptions,
29
+ backfillUpstream,
30
+ type DistilledArtifactLoader,
31
+ } from "./provenance-backfill";
32
+ import type { KnowledgeArtifact } from "./schemas";
33
+
34
+ // ═══════════════════════════════════════════════════════════════════════════════
35
+ // Options
36
+ // ═══════════════════════════════════════════════════════════════════════════════
37
+
38
+ interface BackfillCliOptions {
39
+ dryRun?: boolean;
40
+ json?: boolean;
41
+ revision?: string;
42
+ }
43
+
44
+ /** Source formats a path-based backfill can distill from. */
45
+ const BACKFILL_FORMATS: ReadonlySet<string> = new Set([
46
+ "kiro-power",
47
+ "kiro-skill",
48
+ "superpowers",
49
+ ]);
50
+
51
+ // ═══════════════════════════════════════════════════════════════════════════════
52
+ // Filesystem-backed distilled artifact loader
53
+ // ═══════════════════════════════════════════════════════════════════════════════
54
+
55
+ /**
56
+ * Default loader: parse a distilled artifact directory via the canonical parser.
57
+ * Returns undefined when the directory does not parse into a valid artifact so
58
+ * the backfill classifies it as a translation failure rather than throwing.
59
+ */
60
+ const loadDistilledFromDisk: DistilledArtifactLoader = async (artifactDir) => {
61
+ const result = await loadKnowledgeArtifact(artifactDir);
62
+ if ("errors" in result) {
63
+ return undefined;
64
+ }
65
+ return result.data as KnowledgeArtifact;
66
+ };
67
+
68
+ // ═══════════════════════════════════════════════════════════════════════════════
69
+ // Source-revision resolution
70
+ // ═══════════════════════════════════════════════════════════════════════════════
71
+
72
+ /**
73
+ * Resolve the upstream source revision to record in provenance. Prefers an
74
+ * explicit `--revision`; otherwise attempts to read the current subtree HEAD via
75
+ * git; otherwise falls back to a stable backfill marker so the (non-empty)
76
+ * schema field is always satisfied without failing the command on machines
77
+ * where git is unavailable.
78
+ */
79
+ async function resolveSourceRevision(
80
+ explicit: string | undefined,
81
+ repoRoot: string,
82
+ ): Promise<string> {
83
+ if (explicit && explicit.trim().length > 0) {
84
+ return explicit.trim();
85
+ }
86
+
87
+ try {
88
+ const proc = Bun.spawn(["git", "rev-parse", "HEAD"], {
89
+ cwd: repoRoot,
90
+ stdout: "pipe",
91
+ stderr: "ignore",
92
+ });
93
+ const out = (await new Response(proc.stdout).text()).trim();
94
+ const code = await proc.exited;
95
+ if (code === 0 && out.length > 0) {
96
+ return `backfill:${out}`;
97
+ }
98
+ } catch {
99
+ // git unavailable — fall through to the marker below.
100
+ }
101
+
102
+ return "backfill:unknown";
103
+ }
104
+
105
+ // ═══════════════════════════════════════════════════════════════════════════════
106
+ // Path resolution for one upstream
107
+ // ═══════════════════════════════════════════════════════════════════════════════
108
+
109
+ interface ResolvedUpstreamPaths {
110
+ readonly sourceFormat: BackfillSourceFormat;
111
+ readonly knowledgeDir: string;
112
+ readonly upstreamRoot: string;
113
+ }
114
+
115
+ /**
116
+ * Resolve the distilled knowledge directory and upstream source root for an
117
+ * upstream config entry, or return a diagnostic string explaining why it cannot
118
+ * be backfilled. `prefix` is relative to the repository root; `knowledgeDir` is
119
+ * relative to the kanon working directory.
120
+ */
121
+ async function resolveUpstreamPaths(
122
+ upstream: UpstreamConfig,
123
+ repoRoot: string,
124
+ workingDir: string,
125
+ ): Promise<ResolvedUpstreamPaths | string> {
126
+ const format = upstream.format ?? "auto";
127
+ if (!BACKFILL_FORMATS.has(format)) {
128
+ return `format "${format}" is not a path-based source format (expected kiro-power, kiro-skill, or superpowers)`;
129
+ }
130
+
131
+ if (!upstream.knowledgeDir) {
132
+ return "no knowledgeDir configured for this upstream";
133
+ }
134
+ if (!upstream.prefix) {
135
+ return "no prefix (subtree path) configured for this upstream";
136
+ }
137
+
138
+ const knowledgeDir = isAbsolute(upstream.knowledgeDir)
139
+ ? upstream.knowledgeDir
140
+ : resolve(workingDir, upstream.knowledgeDir);
141
+ const upstreamRoot = isAbsolute(upstream.prefix)
142
+ ? upstream.prefix
143
+ : resolve(repoRoot, upstream.prefix);
144
+
145
+ if (!(await exists(knowledgeDir))) {
146
+ return `knowledge directory not found: ${knowledgeDir}`;
147
+ }
148
+ if (!(await exists(upstreamRoot))) {
149
+ return `upstream source root not found: ${upstreamRoot} (run the sync script first)`;
150
+ }
151
+
152
+ return {
153
+ sourceFormat: format as BackfillSourceFormat,
154
+ knowledgeDir,
155
+ upstreamRoot,
156
+ };
157
+ }
158
+
159
+ // ═══════════════════════════════════════════════════════════════════════════════
160
+ // Human-readable rendering
161
+ // ═══════════════════════════════════════════════════════════════════════════════
162
+
163
+ function renderResult(result: BackfillResult, dryRun: boolean): void {
164
+ const verb = dryRun ? "would backfill" : "backfilled";
165
+ console.log(
166
+ chalk.bold.cyan(`Provenance backfill — ${result.upstream}`) +
167
+ (dryRun ? chalk.dim(" (dry run)") : ""),
168
+ );
169
+ console.log(chalk.dim("─".repeat(50)));
170
+
171
+ for (const entry of result.entries) {
172
+ switch (entry.outcome) {
173
+ case "backfilled": {
174
+ const digest = entry.baseDigest
175
+ ? chalk.dim(` ${entry.baseDigest.slice(0, 19)}…`)
176
+ : "";
177
+ console.log(` ${chalk.green("✓")} ${chalk.bold(entry.name)}${digest}`);
178
+ break;
179
+ }
180
+ case "skipped-has-provenance":
181
+ console.log(
182
+ ` ${chalk.dim("•")} ${entry.name} ${chalk.dim("(already has provenance)")}`,
183
+ );
184
+ break;
185
+ case "unmatched":
186
+ console.log(
187
+ ` ${chalk.yellow("⚠")} ${entry.name} ${chalk.dim("(no upstream match)")}`,
188
+ );
189
+ break;
190
+ case "translation-failed":
191
+ console.log(
192
+ ` ${chalk.red("✗")} ${entry.name} ${chalk.dim(`(${entry.detail ?? "translation failed"})`)}`,
193
+ );
194
+ break;
195
+ }
196
+ }
197
+
198
+ console.log();
199
+ console.log(
200
+ chalk.green(` ${result.backfilledCount} ${verb}`) +
201
+ chalk.dim(
202
+ `, ${result.skippedCount} skipped, ${result.unmatchedCount} unmatched, ${result.failedCount} failed`,
203
+ ),
204
+ );
205
+ console.log();
206
+ }
207
+
208
+ // ═══════════════════════════════════════════════════════════════════════════════
209
+ // Command handler
210
+ // ═══════════════════════════════════════════════════════════════════════════════
211
+
212
+ /**
213
+ * Handler for `kanon rosetta backfill [upstream]`. Backfills one named upstream
214
+ * or every configured `upstreams` entry when no name is given.
215
+ */
216
+ async function backfillCommand(
217
+ upstreamName: string | undefined,
218
+ options: BackfillCliOptions,
219
+ ): Promise<void> {
220
+ const config = await loadForgeConfig();
221
+ const upstreams = config.upstreams ?? {};
222
+
223
+ const names = upstreamName ? [upstreamName] : Object.keys(upstreams).sort();
224
+
225
+ if (upstreamName && !upstreams[upstreamName]) {
226
+ console.error(
227
+ chalk.red(`Error: Upstream "${upstreamName}" not found in config.`),
228
+ );
229
+ process.exit(1);
230
+ }
231
+
232
+ if (names.length === 0) {
233
+ console.error(
234
+ chalk.yellow("No upstreams configured in kanon.config.yaml."),
235
+ );
236
+ return;
237
+ }
238
+
239
+ const workingDir = process.cwd();
240
+ const repoRoot = resolve(workingDir, "..");
241
+ const dryRun = Boolean(options.dryRun);
242
+
243
+ const results: BackfillResult[] = [];
244
+ let hadError = false;
245
+
246
+ for (const name of names) {
247
+ const upstream = upstreams[name];
248
+ const resolved = await resolveUpstreamPaths(upstream, repoRoot, workingDir);
249
+
250
+ if (typeof resolved === "string") {
251
+ if (!options.json) {
252
+ console.error(chalk.yellow(` ⚠ ${name} — skipped: ${resolved}`));
253
+ }
254
+ hadError = true;
255
+ continue;
256
+ }
257
+
258
+ const sourceRevision = await resolveSourceRevision(
259
+ options.revision,
260
+ repoRoot,
261
+ );
262
+
263
+ const opts: BackfillUpstreamOptions = {
264
+ upstream: name,
265
+ knowledgeDir: resolved.knowledgeDir,
266
+ upstreamRoot: resolved.upstreamRoot,
267
+ sourceFormat: resolved.sourceFormat,
268
+ sourceRevision,
269
+ dryRun,
270
+ workspaceRoot: workingDir,
271
+ };
272
+
273
+ const result = await backfillUpstream(opts, loadDistilledFromDisk);
274
+ results.push(result);
275
+
276
+ if (result.failedCount > 0) {
277
+ hadError = true;
278
+ }
279
+ }
280
+
281
+ if (options.json) {
282
+ console.log(JSON.stringify({ dryRun, upstreams: results }, null, 2));
283
+ } else {
284
+ for (const result of results) {
285
+ renderResult(result, dryRun);
286
+ }
287
+ }
288
+
289
+ if (hadError) {
290
+ process.exit(1);
291
+ }
292
+ }
293
+
294
+ // ═══════════════════════════════════════════════════════════════════════════════
295
+ // Registration
296
+ // ═══════════════════════════════════════════════════════════════════════════════
297
+
298
+ /**
299
+ * Register the `kanon rosetta backfill` subcommand on the rosetta command group.
300
+ */
301
+ export function registerBackfillCommand(rosettaCmd: Command): void {
302
+ rosettaCmd
303
+ .command("backfill [upstream]")
304
+ .description(
305
+ "Backfill provenance for existing distilled artifacts by matching to current upstream by name",
306
+ )
307
+ .option(
308
+ "--dry-run",
309
+ "Report what would be backfilled without writing files",
310
+ )
311
+ .option("--json", "Output as JSON")
312
+ .option(
313
+ "--revision <rev>",
314
+ "Upstream revision to record in provenance (default: current git HEAD)",
315
+ )
316
+ .action((upstream: string | undefined, opts: BackfillCliOptions) =>
317
+ backfillCommand(upstream, opts),
318
+ );
319
+ }