@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,390 @@
|
|
|
1
|
+
import { exists, mkdir, readdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { join, relative } from "node:path";
|
|
3
|
+
import * as p from "@clack/prompts";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import * as yaml from "js-yaml";
|
|
6
|
+
import type { HarnessName } from "../schemas";
|
|
7
|
+
import { SUPPORTED_HARNESSES } from "../schemas";
|
|
8
|
+
import { parseClaudeCode } from "./claude-code";
|
|
9
|
+
import { parseCline } from "./cline";
|
|
10
|
+
import { parseCodex } from "./codex";
|
|
11
|
+
import { parseCopilot } from "./copilot";
|
|
12
|
+
import { parseCursor } from "./cursor";
|
|
13
|
+
import { parseKiro } from "./kiro";
|
|
14
|
+
import { parseQDeveloper } from "./qdeveloper";
|
|
15
|
+
import type { ImportedFile, ImporterRegistry } from "./types";
|
|
16
|
+
import { parseWindsurf } from "./windsurf";
|
|
17
|
+
|
|
18
|
+
// ── Harness-native file path mappings ─────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Maps each harness to its known native file path globs (relative to cwd).
|
|
22
|
+
* Used by detectHarnessFiles() to scan for importable content.
|
|
23
|
+
*/
|
|
24
|
+
export const HARNESS_NATIVE_PATHS: Record<HarnessName, string[]> = {
|
|
25
|
+
kiro: [".kiro/steering/*.md", ".kiro/skills/*/SKILL.md"],
|
|
26
|
+
"claude-code": ["CLAUDE.md", ".claude/settings.json"],
|
|
27
|
+
codex: [
|
|
28
|
+
"AGENTS.md",
|
|
29
|
+
".codex/skills/*/SKILL.md",
|
|
30
|
+
".agents/skills/*/SKILL.md",
|
|
31
|
+
".codex/config.toml",
|
|
32
|
+
],
|
|
33
|
+
copilot: [
|
|
34
|
+
".github/copilot-instructions.md",
|
|
35
|
+
".github/instructions/*.instructions.md",
|
|
36
|
+
],
|
|
37
|
+
cursor: [".cursor/rules/*.md", ".cursorrules"],
|
|
38
|
+
windsurf: [".windsurfrules", ".windsurf/rules/*.md"],
|
|
39
|
+
cline: [".clinerules/*.md"],
|
|
40
|
+
qdeveloper: [".q/rules/*.md", ".amazonq/rules/*.md"],
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// ── Importer Registry ─────────────────────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Registry mapping each harness to its native file paths and parser function.
|
|
47
|
+
*/
|
|
48
|
+
export const importerRegistry: ImporterRegistry = {
|
|
49
|
+
kiro: {
|
|
50
|
+
nativePaths: HARNESS_NATIVE_PATHS.kiro,
|
|
51
|
+
parse: parseKiro,
|
|
52
|
+
},
|
|
53
|
+
"claude-code": {
|
|
54
|
+
nativePaths: HARNESS_NATIVE_PATHS["claude-code"],
|
|
55
|
+
parse: parseClaudeCode,
|
|
56
|
+
},
|
|
57
|
+
codex: {
|
|
58
|
+
nativePaths: HARNESS_NATIVE_PATHS.codex,
|
|
59
|
+
parse: parseCodex,
|
|
60
|
+
},
|
|
61
|
+
copilot: {
|
|
62
|
+
nativePaths: HARNESS_NATIVE_PATHS.copilot,
|
|
63
|
+
parse: parseCopilot,
|
|
64
|
+
},
|
|
65
|
+
cursor: {
|
|
66
|
+
nativePaths: HARNESS_NATIVE_PATHS.cursor,
|
|
67
|
+
parse: parseCursor,
|
|
68
|
+
},
|
|
69
|
+
windsurf: {
|
|
70
|
+
nativePaths: HARNESS_NATIVE_PATHS.windsurf,
|
|
71
|
+
parse: parseWindsurf,
|
|
72
|
+
},
|
|
73
|
+
cline: {
|
|
74
|
+
nativePaths: HARNESS_NATIVE_PATHS.cline,
|
|
75
|
+
parse: parseCline,
|
|
76
|
+
},
|
|
77
|
+
qdeveloper: {
|
|
78
|
+
nativePaths: HARNESS_NATIVE_PATHS.qdeveloper,
|
|
79
|
+
parse: parseQDeveloper,
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// ── Glob matching utility ─────────────────────────────────────────────────────
|
|
84
|
+
|
|
85
|
+
function escapeRegexLiteral(input: string): string {
|
|
86
|
+
return input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Matches a relative file path against a simple glob pattern.
|
|
91
|
+
* Supports `*` (single directory segment wildcard) in path segments.
|
|
92
|
+
*/
|
|
93
|
+
function matchGlob(pattern: string, filePath: string): boolean {
|
|
94
|
+
const patternParts = pattern.split("/");
|
|
95
|
+
const pathParts = filePath.split("/");
|
|
96
|
+
|
|
97
|
+
if (patternParts.length !== pathParts.length) return false;
|
|
98
|
+
|
|
99
|
+
for (let i = 0; i < patternParts.length; i++) {
|
|
100
|
+
const pat = patternParts[i];
|
|
101
|
+
const seg = pathParts[i];
|
|
102
|
+
|
|
103
|
+
if (pat === "*") continue;
|
|
104
|
+
|
|
105
|
+
// Handle patterns like "*.md" or "*.instructions.md"
|
|
106
|
+
if (pat.includes("*")) {
|
|
107
|
+
const safePattern = escapeRegexLiteral(pat).replace(/\\\*/g, ".*");
|
|
108
|
+
const regex = new RegExp(`^${safePattern}$`);
|
|
109
|
+
if (!regex.test(seg)) return false;
|
|
110
|
+
} else {
|
|
111
|
+
if (pat !== seg) return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Recursively collects all file paths relative to `root`, up to a max depth.
|
|
120
|
+
*/
|
|
121
|
+
async function collectRelativePaths(
|
|
122
|
+
root: string,
|
|
123
|
+
maxDepth = 4,
|
|
124
|
+
): Promise<string[]> {
|
|
125
|
+
const results: string[] = [];
|
|
126
|
+
|
|
127
|
+
async function walk(dir: string, depth: number): Promise<void> {
|
|
128
|
+
if (depth > maxDepth) return;
|
|
129
|
+
|
|
130
|
+
let entries: Array<{ name: string; isDirectory(): boolean }>;
|
|
131
|
+
try {
|
|
132
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
133
|
+
} catch {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
for (const entry of entries) {
|
|
138
|
+
const fullPath = join(dir, entry.name);
|
|
139
|
+
const relPath = relative(root, fullPath);
|
|
140
|
+
|
|
141
|
+
if (entry.isDirectory()) {
|
|
142
|
+
await walk(fullPath, depth + 1);
|
|
143
|
+
} else {
|
|
144
|
+
results.push(relPath);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
await walk(root, 0);
|
|
150
|
+
return results;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ── Auto-detection ────────────────────────────────────────────────────────────
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Scans the given directory for files matching known harness-native file paths.
|
|
157
|
+
* Returns detected files grouped by harness name.
|
|
158
|
+
*/
|
|
159
|
+
export async function detectHarnessFiles(
|
|
160
|
+
cwd: string,
|
|
161
|
+
): Promise<Record<HarnessName, string[]>> {
|
|
162
|
+
const result: Record<HarnessName, string[]> = {
|
|
163
|
+
kiro: [],
|
|
164
|
+
"claude-code": [],
|
|
165
|
+
codex: [],
|
|
166
|
+
copilot: [],
|
|
167
|
+
cursor: [],
|
|
168
|
+
windsurf: [],
|
|
169
|
+
cline: [],
|
|
170
|
+
qdeveloper: [],
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const allFiles = await collectRelativePaths(cwd);
|
|
174
|
+
|
|
175
|
+
for (const harness of SUPPORTED_HARNESSES) {
|
|
176
|
+
const patterns = HARNESS_NATIVE_PATHS[harness];
|
|
177
|
+
for (const pattern of patterns) {
|
|
178
|
+
for (const filePath of allFiles) {
|
|
179
|
+
if (matchGlob(pattern, filePath)) {
|
|
180
|
+
result[harness].push(filePath);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return result;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// ── Import command options ────────────────────────────────────────────────────
|
|
190
|
+
|
|
191
|
+
export interface ImportCommandOptions {
|
|
192
|
+
/** Only scan/import for this specific harness. */
|
|
193
|
+
harness?: HarnessName;
|
|
194
|
+
/** Overwrite existing artifacts without confirmation. */
|
|
195
|
+
force?: boolean;
|
|
196
|
+
/** Show what would happen without writing files. */
|
|
197
|
+
dryRun?: boolean;
|
|
198
|
+
/** Target knowledge directory (default: "knowledge"). */
|
|
199
|
+
knowledgeDir?: string;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ── Import command orchestrator ───────────────────────────────────────────────
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Orchestrates the multi-harness import flow:
|
|
206
|
+
* - If --harness provided, only scan for that harness
|
|
207
|
+
* - If no --harness, scan all and present summary for confirmation
|
|
208
|
+
* - Supports --force (overwrite without confirmation) and --dry-run
|
|
209
|
+
* - If no files detected, suggests `kanon new`
|
|
210
|
+
*/
|
|
211
|
+
export async function importCommand(
|
|
212
|
+
options: ImportCommandOptions,
|
|
213
|
+
): Promise<void> {
|
|
214
|
+
const cwd = process.cwd();
|
|
215
|
+
const knowledgeDir = options.knowledgeDir ?? "knowledge";
|
|
216
|
+
const dryRun = options.dryRun ?? false;
|
|
217
|
+
const force = options.force ?? false;
|
|
218
|
+
|
|
219
|
+
if (dryRun) {
|
|
220
|
+
console.error(chalk.dim(" Dry run — no files will be written\n"));
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Scan for harness-native files
|
|
224
|
+
const detected = await detectHarnessFiles(cwd);
|
|
225
|
+
|
|
226
|
+
// Filter to specified harness if --harness flag provided
|
|
227
|
+
let harnessesToProcess: HarnessName[];
|
|
228
|
+
if (options.harness) {
|
|
229
|
+
harnessesToProcess = [options.harness];
|
|
230
|
+
} else {
|
|
231
|
+
harnessesToProcess = SUPPORTED_HARNESSES.filter(
|
|
232
|
+
(h) => detected[h].length > 0,
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Check if any files were detected
|
|
237
|
+
const totalFiles = harnessesToProcess.reduce(
|
|
238
|
+
(sum, h) => sum + detected[h].length,
|
|
239
|
+
0,
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
if (totalFiles === 0) {
|
|
243
|
+
console.error(
|
|
244
|
+
chalk.yellow(
|
|
245
|
+
"\n No harness-native files detected in the current directory.\n",
|
|
246
|
+
),
|
|
247
|
+
);
|
|
248
|
+
console.error(
|
|
249
|
+
chalk.dim(
|
|
250
|
+
" Checked paths for: " +
|
|
251
|
+
(options.harness ? options.harness : "all 7 harnesses"),
|
|
252
|
+
),
|
|
253
|
+
);
|
|
254
|
+
console.error(
|
|
255
|
+
chalk.dim(" Run `kanon new` to create a new knowledge artifact.\n"),
|
|
256
|
+
);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Present summary
|
|
261
|
+
console.error("");
|
|
262
|
+
console.error(chalk.bold(" Detected harness-native files:\n"));
|
|
263
|
+
|
|
264
|
+
for (const harness of harnessesToProcess) {
|
|
265
|
+
const files = detected[harness];
|
|
266
|
+
if (files.length === 0) continue;
|
|
267
|
+
console.error(
|
|
268
|
+
` ${chalk.cyan(harness)} ${chalk.dim(`(${files.length} file${files.length !== 1 ? "s" : ""})`)}`,
|
|
269
|
+
);
|
|
270
|
+
for (const file of files) {
|
|
271
|
+
console.error(` ${chalk.dim("•")} ${file}`);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
console.error("");
|
|
275
|
+
|
|
276
|
+
// If no --harness flag and not --force, prompt for confirmation
|
|
277
|
+
if (!options.harness && !force) {
|
|
278
|
+
const confirmed = await p.confirm({
|
|
279
|
+
message: `Import ${totalFiles} file${totalFiles !== 1 ? "s" : ""} from ${harnessesToProcess.length} harness${harnessesToProcess.length !== 1 ? "es" : ""}?`,
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
if (p.isCancel(confirmed) || !confirmed) {
|
|
283
|
+
console.error(chalk.dim(" Import cancelled.\n"));
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Process each harness's files
|
|
289
|
+
let imported = 0;
|
|
290
|
+
let skipped = 0;
|
|
291
|
+
const warnings: string[] = [];
|
|
292
|
+
|
|
293
|
+
for (const harness of harnessesToProcess) {
|
|
294
|
+
const files = detected[harness];
|
|
295
|
+
if (files.length === 0) continue;
|
|
296
|
+
|
|
297
|
+
const parser = importerRegistry[harness].parse;
|
|
298
|
+
|
|
299
|
+
for (const filePath of files) {
|
|
300
|
+
const fullPath = join(cwd, filePath);
|
|
301
|
+
|
|
302
|
+
let parsed: ImportedFile;
|
|
303
|
+
try {
|
|
304
|
+
parsed = await parser(fullPath);
|
|
305
|
+
} catch (err) {
|
|
306
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
307
|
+
warnings.push(`${filePath}: ${msg}`);
|
|
308
|
+
skipped++;
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const targetDir = join(knowledgeDir, parsed.artifactName);
|
|
313
|
+
const targetPath = join(targetDir, "knowledge.md");
|
|
314
|
+
|
|
315
|
+
// Check for collision
|
|
316
|
+
if (await exists(targetDir)) {
|
|
317
|
+
if (!force) {
|
|
318
|
+
console.error(
|
|
319
|
+
chalk.yellow(
|
|
320
|
+
` ⚠ ${parsed.artifactName} — already exists (use --force to overwrite)`,
|
|
321
|
+
),
|
|
322
|
+
);
|
|
323
|
+
skipped++;
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (!dryRun) {
|
|
329
|
+
await mkdir(join(targetDir, "workflows"), { recursive: true });
|
|
330
|
+
|
|
331
|
+
// Build knowledge.md with frontmatter
|
|
332
|
+
const fm = {
|
|
333
|
+
name: parsed.artifactName,
|
|
334
|
+
...parsed.frontmatter,
|
|
335
|
+
harnesses: [harness],
|
|
336
|
+
};
|
|
337
|
+
const frontmatterYaml = yaml.dump(fm, { lineWidth: -1 });
|
|
338
|
+
const knowledgeMd = `---\n${frontmatterYaml}---\n${parsed.body}\n`;
|
|
339
|
+
await writeFile(targetPath, knowledgeMd, "utf-8");
|
|
340
|
+
|
|
341
|
+
// Write hooks.yaml if hooks present
|
|
342
|
+
if (parsed.hooks.length > 0) {
|
|
343
|
+
const hooksYaml = yaml.dump(parsed.hooks, { lineWidth: -1 });
|
|
344
|
+
await writeFile(join(targetDir, "hooks.yaml"), hooksYaml, "utf-8");
|
|
345
|
+
} else {
|
|
346
|
+
await writeFile(join(targetDir, "hooks.yaml"), "[]\n", "utf-8");
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Write mcp-servers.yaml if MCP servers present
|
|
350
|
+
if (parsed.mcpServers.length > 0) {
|
|
351
|
+
const mcpYaml = yaml.dump(parsed.mcpServers, { lineWidth: -1 });
|
|
352
|
+
await writeFile(
|
|
353
|
+
join(targetDir, "mcp-servers.yaml"),
|
|
354
|
+
mcpYaml,
|
|
355
|
+
"utf-8",
|
|
356
|
+
);
|
|
357
|
+
} else {
|
|
358
|
+
await writeFile(join(targetDir, "mcp-servers.yaml"), "[]\n", "utf-8");
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const prefix = dryRun ? chalk.dim(" → ") : chalk.green(" ✓ ");
|
|
363
|
+
console.error(
|
|
364
|
+
`${prefix}${chalk.bold(parsed.artifactName)} ${chalk.dim(`← ${filePath}`)}`,
|
|
365
|
+
);
|
|
366
|
+
imported++;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Print warnings
|
|
371
|
+
for (const warning of warnings) {
|
|
372
|
+
console.error(chalk.yellow(` ⚠ ${warning}`));
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Print summary
|
|
376
|
+
console.error("");
|
|
377
|
+
const verb = dryRun ? "would import" : "imported";
|
|
378
|
+
console.error(
|
|
379
|
+
chalk.green(` ${imported} artifact${imported !== 1 ? "s" : ""} ${verb}`) +
|
|
380
|
+
(skipped > 0 ? chalk.yellow(`, ${skipped} skipped`) : ""),
|
|
381
|
+
);
|
|
382
|
+
|
|
383
|
+
if (!dryRun && imported > 0) {
|
|
384
|
+
console.error(
|
|
385
|
+
chalk.dim(" Run `kanon validate` to check the imported artifacts."),
|
|
386
|
+
);
|
|
387
|
+
console.error(chalk.dim(" Run `kanon build` to compile them."));
|
|
388
|
+
}
|
|
389
|
+
console.error("");
|
|
390
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kiro Importer — Compatibility Facade
|
|
3
|
+
*
|
|
4
|
+
* Preserves the public `parseKiro` interface while delegating pure parsing
|
|
5
|
+
* to the Rosetta Stone kiro-native source translator. Handles multi-file
|
|
6
|
+
* grouping (steering markdown + hook files) deterministically.
|
|
7
|
+
*
|
|
8
|
+
* Requirements: 14.2, 14.10, 14.11
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { readFile } from "node:fs/promises";
|
|
12
|
+
import { basename } from "node:path";
|
|
13
|
+
import { translateKiroNative } from "../rosetta/builtins/sources/kiro-native";
|
|
14
|
+
import type { SourceTranslatorContext } from "../rosetta/registry";
|
|
15
|
+
import type {
|
|
16
|
+
FormatIdentifier,
|
|
17
|
+
NormalizedRelativePath,
|
|
18
|
+
SourceDocument,
|
|
19
|
+
} from "../schemas";
|
|
20
|
+
import type { ImportedFile, ImportParser } from "./types";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Derives a kebab-case artifact name from a file path.
|
|
24
|
+
* Uses the filename without extension, already expected to be kebab-case.
|
|
25
|
+
*/
|
|
26
|
+
function deriveArtifactName(filePath: string): string {
|
|
27
|
+
const base = basename(filePath);
|
|
28
|
+
const name =
|
|
29
|
+
base.replace(/\.kiro\.hook$/, "") || base.replace(/\.[^.]+$/, "");
|
|
30
|
+
return name
|
|
31
|
+
.toLowerCase()
|
|
32
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
33
|
+
.replace(/^-|-$/g, "");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Kiro import parser.
|
|
38
|
+
* Handles .kiro/steering/*.md (frontmatter + body) and .kiro/hooks/*.kiro.hook (JSON → CanonicalHook).
|
|
39
|
+
* Delegates pure parsing to the Rosetta Stone kiro-native source translator.
|
|
40
|
+
*/
|
|
41
|
+
export const parseKiro: ImportParser = async (
|
|
42
|
+
filePath: string,
|
|
43
|
+
): Promise<ImportedFile> => {
|
|
44
|
+
const raw = await readFile(filePath, "utf-8");
|
|
45
|
+
const artifactName = deriveArtifactName(filePath);
|
|
46
|
+
|
|
47
|
+
// Determine the relative path for the document based on file type
|
|
48
|
+
const base = basename(filePath);
|
|
49
|
+
const relativePath = base as NormalizedRelativePath;
|
|
50
|
+
|
|
51
|
+
// Build SourceDocument for Rosetta Stone
|
|
52
|
+
const documents: SourceDocument[] = [
|
|
53
|
+
{
|
|
54
|
+
path: relativePath,
|
|
55
|
+
content: raw,
|
|
56
|
+
executable: false,
|
|
57
|
+
},
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
// Build translator context
|
|
61
|
+
const context: SourceTranslatorContext = {
|
|
62
|
+
format: {
|
|
63
|
+
id: "kiro" as FormatIdentifier,
|
|
64
|
+
} as SourceTranslatorContext["format"],
|
|
65
|
+
canonicalSchemaVersion: "1.0.0",
|
|
66
|
+
options: {},
|
|
67
|
+
callerContext: { artifactNameHint: artifactName },
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// Delegate to Rosetta Stone
|
|
71
|
+
const output = translateKiroNative(documents, context);
|
|
72
|
+
|
|
73
|
+
// Map SourceTranslationOutput back to ImportedFile shape
|
|
74
|
+
if (output.candidate) {
|
|
75
|
+
const candidate = output.candidate as Record<string, unknown>;
|
|
76
|
+
const frontmatter = (candidate.frontmatter ?? {}) as Record<
|
|
77
|
+
string,
|
|
78
|
+
unknown
|
|
79
|
+
>;
|
|
80
|
+
const {
|
|
81
|
+
name: _n,
|
|
82
|
+
type: _t,
|
|
83
|
+
harnesses: _h,
|
|
84
|
+
...restFrontmatter
|
|
85
|
+
} = frontmatter;
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
sourcePath: filePath,
|
|
89
|
+
artifactName,
|
|
90
|
+
body: (candidate.body as string) ?? "",
|
|
91
|
+
frontmatter: restFrontmatter,
|
|
92
|
+
hooks: (candidate.hooks as ImportedFile["hooks"]) ?? [],
|
|
93
|
+
mcpServers: (candidate.mcpServers as ImportedFile["mcpServers"]) ?? [],
|
|
94
|
+
extraFields: (candidate.extraFields as Record<string, unknown>) ?? {},
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Fallback: if translation produced no candidate, return minimal result
|
|
99
|
+
return {
|
|
100
|
+
sourcePath: filePath,
|
|
101
|
+
artifactName,
|
|
102
|
+
body: raw.trim(),
|
|
103
|
+
frontmatter: {},
|
|
104
|
+
hooks: [],
|
|
105
|
+
mcpServers: [],
|
|
106
|
+
extraFields: {},
|
|
107
|
+
};
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export default parseKiro;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Q Developer Importer — Compatibility Facade
|
|
3
|
+
*
|
|
4
|
+
* Preserves the public `parseQDeveloper` interface while delegating pure parsing
|
|
5
|
+
* to the Rosetta Stone qdeveloper-native source translator.
|
|
6
|
+
*
|
|
7
|
+
* Requirements: 14.2, 14.10, 14.11
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { readFile } from "node:fs/promises";
|
|
11
|
+
import { basename } from "node:path";
|
|
12
|
+
import { translateQDeveloperNative } from "../rosetta/builtins/sources/qdeveloper-native";
|
|
13
|
+
import type { SourceTranslatorContext } from "../rosetta/registry";
|
|
14
|
+
import type {
|
|
15
|
+
FormatIdentifier,
|
|
16
|
+
NormalizedRelativePath,
|
|
17
|
+
SourceDocument,
|
|
18
|
+
} from "../schemas";
|
|
19
|
+
import type { ImportedFile, ImportParser } from "./types";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Derives a kebab-case artifact name from a file path.
|
|
23
|
+
*/
|
|
24
|
+
function deriveArtifactName(filePath: string): string {
|
|
25
|
+
const base = basename(filePath);
|
|
26
|
+
const name = base.replace(/\.md$/, "");
|
|
27
|
+
return name
|
|
28
|
+
.toLowerCase()
|
|
29
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
30
|
+
.replace(/^-|-$/g, "");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Q Developer import parser.
|
|
35
|
+
* Handles .q/rules/*.md and .amazonq/rules/*.md files.
|
|
36
|
+
* Delegates pure parsing to the Rosetta Stone qdeveloper-native source translator.
|
|
37
|
+
*/
|
|
38
|
+
export const parseQDeveloper: ImportParser = async (
|
|
39
|
+
filePath: string,
|
|
40
|
+
): Promise<ImportedFile> => {
|
|
41
|
+
const raw = await readFile(filePath, "utf-8");
|
|
42
|
+
const artifactName = deriveArtifactName(filePath);
|
|
43
|
+
|
|
44
|
+
// Build SourceDocument for Rosetta Stone
|
|
45
|
+
const documents: SourceDocument[] = [
|
|
46
|
+
{
|
|
47
|
+
path: basename(filePath) as NormalizedRelativePath,
|
|
48
|
+
content: raw,
|
|
49
|
+
executable: false,
|
|
50
|
+
},
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
// Build translator context
|
|
54
|
+
const context: SourceTranslatorContext = {
|
|
55
|
+
format: {
|
|
56
|
+
id: "qdeveloper" as FormatIdentifier,
|
|
57
|
+
} as SourceTranslatorContext["format"],
|
|
58
|
+
canonicalSchemaVersion: "1.0.0",
|
|
59
|
+
options: {},
|
|
60
|
+
callerContext: { artifactNameHint: artifactName },
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// Delegate to Rosetta Stone
|
|
64
|
+
const output = translateQDeveloperNative(documents, context);
|
|
65
|
+
|
|
66
|
+
// Map SourceTranslationOutput back to ImportedFile shape
|
|
67
|
+
if (output.candidate) {
|
|
68
|
+
const candidate = output.candidate as Record<string, unknown>;
|
|
69
|
+
const frontmatter = (candidate.frontmatter ?? {}) as Record<
|
|
70
|
+
string,
|
|
71
|
+
unknown
|
|
72
|
+
>;
|
|
73
|
+
const {
|
|
74
|
+
name: _n,
|
|
75
|
+
type: _t,
|
|
76
|
+
harnesses: _h,
|
|
77
|
+
...restFrontmatter
|
|
78
|
+
} = frontmatter;
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
sourcePath: filePath,
|
|
82
|
+
artifactName,
|
|
83
|
+
body: (candidate.body as string) ?? "",
|
|
84
|
+
frontmatter: restFrontmatter,
|
|
85
|
+
hooks: (candidate.hooks as ImportedFile["hooks"]) ?? [],
|
|
86
|
+
mcpServers: (candidate.mcpServers as ImportedFile["mcpServers"]) ?? [],
|
|
87
|
+
extraFields: (candidate.extraFields as Record<string, unknown>) ?? {},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Fallback: if translation produced no candidate, return minimal result
|
|
92
|
+
return {
|
|
93
|
+
sourcePath: filePath,
|
|
94
|
+
artifactName,
|
|
95
|
+
body: raw.trim(),
|
|
96
|
+
frontmatter: {},
|
|
97
|
+
hooks: [],
|
|
98
|
+
mcpServers: [],
|
|
99
|
+
extraFields: {},
|
|
100
|
+
};
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export default parseQDeveloper;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CanonicalHook,
|
|
3
|
+
HarnessName,
|
|
4
|
+
McpServerDefinition,
|
|
5
|
+
} from "../schemas";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Represents a single file parsed from a harness-native format.
|
|
9
|
+
* This is the intermediate representation before writing to a Knowledge_Artifact directory.
|
|
10
|
+
*/
|
|
11
|
+
export interface ImportedFile {
|
|
12
|
+
/** Original file path that was parsed. */
|
|
13
|
+
sourcePath: string;
|
|
14
|
+
/** Derived artifact name (kebab-cased, no extension). */
|
|
15
|
+
artifactName: string;
|
|
16
|
+
/** Markdown body content extracted from the source file. */
|
|
17
|
+
body: string;
|
|
18
|
+
/** Frontmatter fields extracted from the source (YAML or inferred). */
|
|
19
|
+
frontmatter: Record<string, unknown>;
|
|
20
|
+
/** Canonical hooks extracted from the source file. */
|
|
21
|
+
hooks: CanonicalHook[];
|
|
22
|
+
/** MCP server definitions extracted from the source file. */
|
|
23
|
+
mcpServers: McpServerDefinition[];
|
|
24
|
+
/** Fields that could not be mapped to the canonical schema. */
|
|
25
|
+
extraFields: Record<string, unknown>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Result of running an import parser on one or more harness-native files.
|
|
30
|
+
*/
|
|
31
|
+
export interface ImportResult {
|
|
32
|
+
/** Successfully parsed files ready for artifact creation. */
|
|
33
|
+
files: ImportedFile[];
|
|
34
|
+
/** Warnings emitted during parsing (unmapped fields, ambiguous content, etc.). */
|
|
35
|
+
warnings: string[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* A function that parses a single harness-native file into an ImportedFile.
|
|
40
|
+
*/
|
|
41
|
+
export type ImportParser = (filePath: string) => Promise<ImportedFile>;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Registry mapping each harness to its native file path globs and parser function.
|
|
45
|
+
*/
|
|
46
|
+
export type ImporterRegistry = Record<
|
|
47
|
+
HarnessName,
|
|
48
|
+
{
|
|
49
|
+
/** Glob patterns for harness-native files (relative to cwd). */
|
|
50
|
+
nativePaths: string[];
|
|
51
|
+
/** Parser function for this harness's native format. */
|
|
52
|
+
parse: ImportParser;
|
|
53
|
+
}
|
|
54
|
+
>;
|