@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,285 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rosetta Stone — Kiro Power Source Translator
|
|
3
|
+
*
|
|
4
|
+
* Pure translator for the `kiro-power` format:
|
|
5
|
+
* - Consumes `POWER.md` → extracts frontmatter (name, description, keywords) and body
|
|
6
|
+
* - Consumes `steering/*.md` files → maps to workflows
|
|
7
|
+
* - Preserves any other files found
|
|
8
|
+
* - Maps power-specific fields: `globs` → `file_patterns`, `alwaysApply` → `inclusion`
|
|
9
|
+
* - Sets `type: "skill"`, `harnesses: ["kiro"]`, `harness-config: { kiro: { format: "power" } }`
|
|
10
|
+
*
|
|
11
|
+
* CONSTRAINTS:
|
|
12
|
+
* - NO filesystem, process, clock, random, Git, or network imports
|
|
13
|
+
* - Pure function only
|
|
14
|
+
*
|
|
15
|
+
* Requirements: 2.9, 4.1, 4.2, 4.3, 4.5, 13.7, 14.3
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import matter from "gray-matter";
|
|
19
|
+
|
|
20
|
+
import type {
|
|
21
|
+
KnowledgeArtifact,
|
|
22
|
+
NormalizedRelativePath,
|
|
23
|
+
SourceDocument,
|
|
24
|
+
TranslationDiagnostic,
|
|
25
|
+
} from "../../../schemas";
|
|
26
|
+
import { codePointCompare } from "../../contracts";
|
|
27
|
+
import { createDiagnostic } from "../../diagnostics";
|
|
28
|
+
import type {
|
|
29
|
+
SourceTranslationOutput,
|
|
30
|
+
SourceTranslatorContext,
|
|
31
|
+
} from "../../registry";
|
|
32
|
+
import { SourceAccountant } from "../../source-accounting";
|
|
33
|
+
|
|
34
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
35
|
+
// Translator
|
|
36
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Translate a kiro-power document set into a canonical KnowledgeArtifact candidate.
|
|
40
|
+
*
|
|
41
|
+
* Expected documents:
|
|
42
|
+
* - `POWER.md` (required): YAML frontmatter + Markdown body
|
|
43
|
+
* - `steering/*.md` (optional): mapped to workflows
|
|
44
|
+
* - Other files: preserved
|
|
45
|
+
*/
|
|
46
|
+
export function translateKiroPower(
|
|
47
|
+
documents: readonly SourceDocument[],
|
|
48
|
+
context: SourceTranslatorContext,
|
|
49
|
+
): SourceTranslationOutput {
|
|
50
|
+
const accountant = new SourceAccountant();
|
|
51
|
+
const diagnostics: TranslationDiagnostic[] = [];
|
|
52
|
+
|
|
53
|
+
// Find POWER.md
|
|
54
|
+
const powerDoc = documents.find((d) => d.path === "POWER.md");
|
|
55
|
+
if (!powerDoc) {
|
|
56
|
+
diagnostics.push(
|
|
57
|
+
createDiagnostic("RS_CANONICAL_MISSING_KNOWLEDGE_MD", {
|
|
58
|
+
formatId: "kiro-power",
|
|
59
|
+
message: "POWER.md not found in the provided document set.",
|
|
60
|
+
source: { path: "POWER.md" as NormalizedRelativePath },
|
|
61
|
+
}),
|
|
62
|
+
);
|
|
63
|
+
// Preserve all documents since we cannot parse
|
|
64
|
+
for (const doc of documents) {
|
|
65
|
+
accountant.preserve(doc.path);
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
diagnostics,
|
|
69
|
+
consumedPaths: accountant.getConsumedPaths(),
|
|
70
|
+
preservedPaths: accountant.getPreservedPaths(),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Parse POWER.md frontmatter
|
|
75
|
+
const rawContent =
|
|
76
|
+
typeof powerDoc.content === "string"
|
|
77
|
+
? powerDoc.content
|
|
78
|
+
: new TextDecoder().decode(powerDoc.content);
|
|
79
|
+
|
|
80
|
+
let parsed: matter.GrayMatterFile<string>;
|
|
81
|
+
try {
|
|
82
|
+
parsed = matter(rawContent);
|
|
83
|
+
} catch {
|
|
84
|
+
diagnostics.push(
|
|
85
|
+
createDiagnostic("RS_CANONICAL_INVALID_FRONTMATTER", {
|
|
86
|
+
formatId: "kiro-power",
|
|
87
|
+
message: "The frontmatter YAML in POWER.md could not be parsed.",
|
|
88
|
+
source: { path: "POWER.md" as NormalizedRelativePath },
|
|
89
|
+
}),
|
|
90
|
+
);
|
|
91
|
+
for (const doc of documents) {
|
|
92
|
+
accountant.preserve(doc.path);
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
diagnostics,
|
|
96
|
+
consumedPaths: accountant.getConsumedPaths(),
|
|
97
|
+
preservedPaths: accountant.getPreservedPaths(),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
accountant.consume("POWER.md");
|
|
102
|
+
|
|
103
|
+
const sourceFm = parsed.data as Record<string, unknown>;
|
|
104
|
+
const body = parsed.content.trim();
|
|
105
|
+
|
|
106
|
+
// Resolve artifact name from caller context or frontmatter
|
|
107
|
+
const artifactNameHint = context.callerContext?.artifactNameHint as
|
|
108
|
+
| string
|
|
109
|
+
| undefined;
|
|
110
|
+
const name = String(sourceFm.name || artifactNameHint || "unnamed-power");
|
|
111
|
+
|
|
112
|
+
// Map fields from source frontmatter to canonical
|
|
113
|
+
accountant.mapField("POWER.md", "name", "name", false);
|
|
114
|
+
accountant.mapField("POWER.md", "description", "description", false);
|
|
115
|
+
accountant.mapField("POWER.md", "keywords", "keywords", false);
|
|
116
|
+
|
|
117
|
+
// Map power-specific fields
|
|
118
|
+
const filePatterns = mapGlobsToFilePatterns(sourceFm, accountant);
|
|
119
|
+
const inclusion = mapAlwaysApplyToInclusion(sourceFm, accountant);
|
|
120
|
+
|
|
121
|
+
// Collect steering/ files as workflows
|
|
122
|
+
const steeringDocs = documents
|
|
123
|
+
.filter((d) => d.path.startsWith("steering/") && d.path.endsWith(".md"))
|
|
124
|
+
.sort((a, b) => codePointCompare(a.path, b.path));
|
|
125
|
+
|
|
126
|
+
const workflows = steeringDocs.map((doc) => {
|
|
127
|
+
accountant.consume(doc.path);
|
|
128
|
+
const content =
|
|
129
|
+
typeof doc.content === "string"
|
|
130
|
+
? doc.content
|
|
131
|
+
: new TextDecoder().decode(doc.content);
|
|
132
|
+
// Strip the steering/ prefix for the workflow filename
|
|
133
|
+
const filename = doc.path.slice("steering/".length);
|
|
134
|
+
// Name derived from filename without extension
|
|
135
|
+
const name = filename.replace(/\.md$/, "");
|
|
136
|
+
return { name, filename, content };
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// Preserve any files that are neither POWER.md nor in steering/
|
|
140
|
+
for (const doc of documents) {
|
|
141
|
+
if (doc.path === "POWER.md") continue;
|
|
142
|
+
if (doc.path.startsWith("steering/") && doc.path.endsWith(".md")) continue;
|
|
143
|
+
accountant.preserve(doc.path);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Apply defaults for fields not in source
|
|
147
|
+
const appliedDefaults: Array<{
|
|
148
|
+
field: string;
|
|
149
|
+
value: unknown;
|
|
150
|
+
rule: string;
|
|
151
|
+
}> = [];
|
|
152
|
+
|
|
153
|
+
const displayName = String(sourceFm.displayName || name);
|
|
154
|
+
if (!sourceFm.displayName) {
|
|
155
|
+
appliedDefaults.push({
|
|
156
|
+
field: "displayName",
|
|
157
|
+
value: displayName,
|
|
158
|
+
rule: "kiro-power-default-displayName",
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const description = String(sourceFm.description || "");
|
|
163
|
+
const keywords = Array.isArray(sourceFm.keywords)
|
|
164
|
+
? sourceFm.keywords.map(String)
|
|
165
|
+
: [];
|
|
166
|
+
|
|
167
|
+
const author = String(sourceFm.author || "");
|
|
168
|
+
if (!sourceFm.author) {
|
|
169
|
+
appliedDefaults.push({
|
|
170
|
+
field: "author",
|
|
171
|
+
value: "",
|
|
172
|
+
rule: "kiro-power-default-author",
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const version = "0.1.0";
|
|
177
|
+
appliedDefaults.push({
|
|
178
|
+
field: "version",
|
|
179
|
+
value: version,
|
|
180
|
+
rule: "kiro-power-default-version",
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// Emit diagnostics for applied defaults
|
|
184
|
+
for (const def of appliedDefaults) {
|
|
185
|
+
accountant.applyDefault(def.field, def.value, def.rule);
|
|
186
|
+
diagnostics.push(
|
|
187
|
+
createDiagnostic("RS_DEFAULT_APPLIED", {
|
|
188
|
+
formatId: "kiro-power",
|
|
189
|
+
message: `Default applied for "${def.field}": ${JSON.stringify(def.value)}`,
|
|
190
|
+
source: { path: "POWER.md" as NormalizedRelativePath },
|
|
191
|
+
}),
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Build canonical candidate
|
|
196
|
+
const candidate: KnowledgeArtifact = {
|
|
197
|
+
name,
|
|
198
|
+
frontmatter: {
|
|
199
|
+
name,
|
|
200
|
+
displayName,
|
|
201
|
+
description,
|
|
202
|
+
keywords,
|
|
203
|
+
author,
|
|
204
|
+
version,
|
|
205
|
+
harnesses: ["kiro"],
|
|
206
|
+
type: "skill",
|
|
207
|
+
inclusion,
|
|
208
|
+
categories: ["documentation"],
|
|
209
|
+
ecosystem: [],
|
|
210
|
+
depends: [],
|
|
211
|
+
enhances: [],
|
|
212
|
+
maturity: "stable",
|
|
213
|
+
trust: "community",
|
|
214
|
+
audience: "intermediate",
|
|
215
|
+
"model-assumptions": [],
|
|
216
|
+
collections: [],
|
|
217
|
+
"inherit-hooks": false,
|
|
218
|
+
outcomes: [],
|
|
219
|
+
"harness-config": { kiro: { format: "power" } },
|
|
220
|
+
...(filePatterns.length > 0 ? { file_patterns: filePatterns } : {}),
|
|
221
|
+
},
|
|
222
|
+
body,
|
|
223
|
+
hooks: [],
|
|
224
|
+
mcpServers: [],
|
|
225
|
+
workflows,
|
|
226
|
+
sourcePath: "POWER.md",
|
|
227
|
+
extraFields: {},
|
|
228
|
+
bodyOverrides: {},
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
candidate,
|
|
233
|
+
diagnostics,
|
|
234
|
+
consumedPaths: accountant.getConsumedPaths(),
|
|
235
|
+
preservedPaths: accountant.getPreservedPaths(),
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
240
|
+
// Field Mappers
|
|
241
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Map `globs` source field to `file_patterns` canonical field.
|
|
245
|
+
*/
|
|
246
|
+
function mapGlobsToFilePatterns(
|
|
247
|
+
sourceFm: Record<string, unknown>,
|
|
248
|
+
accountant: SourceAccountant,
|
|
249
|
+
): string[] {
|
|
250
|
+
if (!sourceFm.globs) return [];
|
|
251
|
+
accountant.mapField("POWER.md", "globs", "file_patterns", true);
|
|
252
|
+
if (Array.isArray(sourceFm.globs)) {
|
|
253
|
+
return sourceFm.globs.map(String);
|
|
254
|
+
}
|
|
255
|
+
if (typeof sourceFm.globs === "string") {
|
|
256
|
+
return [sourceFm.globs];
|
|
257
|
+
}
|
|
258
|
+
return [];
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Map `alwaysApply` source field to `inclusion` canonical field.
|
|
263
|
+
*/
|
|
264
|
+
function mapAlwaysApplyToInclusion(
|
|
265
|
+
sourceFm: Record<string, unknown>,
|
|
266
|
+
accountant: SourceAccountant,
|
|
267
|
+
): "always" | "auto" | "fileMatch" | "manual" {
|
|
268
|
+
if (sourceFm.alwaysApply !== undefined) {
|
|
269
|
+
accountant.mapField("POWER.md", "alwaysApply", "inclusion", true);
|
|
270
|
+
return sourceFm.alwaysApply === true ? "always" : "manual";
|
|
271
|
+
}
|
|
272
|
+
if (sourceFm.inclusion !== undefined) {
|
|
273
|
+
accountant.mapField("POWER.md", "inclusion", "inclusion", false);
|
|
274
|
+
const val = String(sourceFm.inclusion);
|
|
275
|
+
if (
|
|
276
|
+
val === "always" ||
|
|
277
|
+
val === "auto" ||
|
|
278
|
+
val === "fileMatch" ||
|
|
279
|
+
val === "manual"
|
|
280
|
+
) {
|
|
281
|
+
return val;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return "manual";
|
|
285
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rosetta Stone — Kiro Skill Source Translator
|
|
3
|
+
*
|
|
4
|
+
* Pure translator for the `kiro-skill` format:
|
|
5
|
+
* - Consumes `SKILL.md` → extracts frontmatter (name, description, keywords) and body
|
|
6
|
+
* - Consumes `references/*.md` files → maps to workflows
|
|
7
|
+
* - Preserves any other files found
|
|
8
|
+
* - Sets `type: "skill"`, `harnesses: ["kiro"]`
|
|
9
|
+
*
|
|
10
|
+
* CONSTRAINTS:
|
|
11
|
+
* - NO filesystem, process, clock, random, Git, or network imports
|
|
12
|
+
* - Pure function only
|
|
13
|
+
*
|
|
14
|
+
* Requirements: 2.9, 4.1, 4.2, 4.3, 4.5, 13.7, 14.3
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import matter from "gray-matter";
|
|
18
|
+
|
|
19
|
+
import type {
|
|
20
|
+
KnowledgeArtifact,
|
|
21
|
+
NormalizedRelativePath,
|
|
22
|
+
SourceDocument,
|
|
23
|
+
TranslationDiagnostic,
|
|
24
|
+
} from "../../../schemas";
|
|
25
|
+
import { codePointCompare } from "../../contracts";
|
|
26
|
+
import { createDiagnostic } from "../../diagnostics";
|
|
27
|
+
import type {
|
|
28
|
+
SourceTranslationOutput,
|
|
29
|
+
SourceTranslatorContext,
|
|
30
|
+
} from "../../registry";
|
|
31
|
+
import { SourceAccountant } from "../../source-accounting";
|
|
32
|
+
|
|
33
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
34
|
+
// Translator
|
|
35
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Translate a kiro-skill document set into a canonical KnowledgeArtifact candidate.
|
|
39
|
+
*
|
|
40
|
+
* Expected documents:
|
|
41
|
+
* - `SKILL.md` (required): YAML frontmatter + Markdown body
|
|
42
|
+
* - `references/*.md` (optional): mapped to workflows
|
|
43
|
+
* - Other files: preserved
|
|
44
|
+
*/
|
|
45
|
+
export function translateKiroSkill(
|
|
46
|
+
documents: readonly SourceDocument[],
|
|
47
|
+
context: SourceTranslatorContext,
|
|
48
|
+
): SourceTranslationOutput {
|
|
49
|
+
const accountant = new SourceAccountant();
|
|
50
|
+
const diagnostics: TranslationDiagnostic[] = [];
|
|
51
|
+
|
|
52
|
+
// Find SKILL.md
|
|
53
|
+
const skillDoc = documents.find((d) => d.path === "SKILL.md");
|
|
54
|
+
if (!skillDoc) {
|
|
55
|
+
diagnostics.push(
|
|
56
|
+
createDiagnostic("RS_CANONICAL_MISSING_KNOWLEDGE_MD", {
|
|
57
|
+
formatId: "kiro-skill",
|
|
58
|
+
message: "SKILL.md not found in the provided document set.",
|
|
59
|
+
source: { path: "SKILL.md" as NormalizedRelativePath },
|
|
60
|
+
}),
|
|
61
|
+
);
|
|
62
|
+
for (const doc of documents) {
|
|
63
|
+
accountant.preserve(doc.path);
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
diagnostics,
|
|
67
|
+
consumedPaths: accountant.getConsumedPaths(),
|
|
68
|
+
preservedPaths: accountant.getPreservedPaths(),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Parse SKILL.md frontmatter
|
|
73
|
+
const rawContent =
|
|
74
|
+
typeof skillDoc.content === "string"
|
|
75
|
+
? skillDoc.content
|
|
76
|
+
: new TextDecoder().decode(skillDoc.content);
|
|
77
|
+
|
|
78
|
+
let parsed: matter.GrayMatterFile<string>;
|
|
79
|
+
try {
|
|
80
|
+
parsed = matter(rawContent);
|
|
81
|
+
} catch {
|
|
82
|
+
diagnostics.push(
|
|
83
|
+
createDiagnostic("RS_CANONICAL_INVALID_FRONTMATTER", {
|
|
84
|
+
formatId: "kiro-skill",
|
|
85
|
+
message: "The frontmatter YAML in SKILL.md could not be parsed.",
|
|
86
|
+
source: { path: "SKILL.md" as NormalizedRelativePath },
|
|
87
|
+
}),
|
|
88
|
+
);
|
|
89
|
+
for (const doc of documents) {
|
|
90
|
+
accountant.preserve(doc.path);
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
diagnostics,
|
|
94
|
+
consumedPaths: accountant.getConsumedPaths(),
|
|
95
|
+
preservedPaths: accountant.getPreservedPaths(),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
accountant.consume("SKILL.md");
|
|
100
|
+
|
|
101
|
+
const sourceFm = parsed.data as Record<string, unknown>;
|
|
102
|
+
const body = parsed.content.trim();
|
|
103
|
+
|
|
104
|
+
// Resolve artifact name from caller context or frontmatter
|
|
105
|
+
const artifactNameHint = context.callerContext?.artifactNameHint as
|
|
106
|
+
| string
|
|
107
|
+
| undefined;
|
|
108
|
+
const name = String(sourceFm.name || artifactNameHint || "unnamed-skill");
|
|
109
|
+
|
|
110
|
+
// Map fields
|
|
111
|
+
accountant.mapField("SKILL.md", "name", "name", false);
|
|
112
|
+
accountant.mapField("SKILL.md", "description", "description", false);
|
|
113
|
+
accountant.mapField("SKILL.md", "keywords", "keywords", false);
|
|
114
|
+
|
|
115
|
+
// Collect references/ files as workflows
|
|
116
|
+
const refDocs = documents
|
|
117
|
+
.filter((d) => d.path.startsWith("references/") && d.path.endsWith(".md"))
|
|
118
|
+
.sort((a, b) => codePointCompare(a.path, b.path));
|
|
119
|
+
|
|
120
|
+
const workflows = refDocs.map((doc) => {
|
|
121
|
+
accountant.consume(doc.path);
|
|
122
|
+
const content =
|
|
123
|
+
typeof doc.content === "string"
|
|
124
|
+
? doc.content
|
|
125
|
+
: new TextDecoder().decode(doc.content);
|
|
126
|
+
const filename = doc.path.slice("references/".length);
|
|
127
|
+
const name = filename.replace(/\.md$/, "");
|
|
128
|
+
return { name, filename, content };
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Preserve any files that are neither SKILL.md nor in references/
|
|
132
|
+
for (const doc of documents) {
|
|
133
|
+
if (doc.path === "SKILL.md") continue;
|
|
134
|
+
if (doc.path.startsWith("references/") && doc.path.endsWith(".md"))
|
|
135
|
+
continue;
|
|
136
|
+
accountant.preserve(doc.path);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Apply defaults
|
|
140
|
+
const appliedDefaults: Array<{
|
|
141
|
+
field: string;
|
|
142
|
+
value: unknown;
|
|
143
|
+
rule: string;
|
|
144
|
+
}> = [];
|
|
145
|
+
|
|
146
|
+
const displayName = String(sourceFm.displayName || name);
|
|
147
|
+
if (!sourceFm.displayName) {
|
|
148
|
+
appliedDefaults.push({
|
|
149
|
+
field: "displayName",
|
|
150
|
+
value: displayName,
|
|
151
|
+
rule: "kiro-skill-default-displayName",
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const description = String(sourceFm.description || "");
|
|
156
|
+
const keywords = Array.isArray(sourceFm.keywords)
|
|
157
|
+
? sourceFm.keywords.map(String)
|
|
158
|
+
: [];
|
|
159
|
+
|
|
160
|
+
const author = String(sourceFm.author || "");
|
|
161
|
+
if (!sourceFm.author) {
|
|
162
|
+
appliedDefaults.push({
|
|
163
|
+
field: "author",
|
|
164
|
+
value: "",
|
|
165
|
+
rule: "kiro-skill-default-author",
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const version = String(sourceFm.version || "0.1.0");
|
|
170
|
+
if (!sourceFm.version) {
|
|
171
|
+
appliedDefaults.push({
|
|
172
|
+
field: "version",
|
|
173
|
+
value: version,
|
|
174
|
+
rule: "kiro-skill-default-version",
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Emit diagnostics for applied defaults
|
|
179
|
+
for (const def of appliedDefaults) {
|
|
180
|
+
accountant.applyDefault(def.field, def.value, def.rule);
|
|
181
|
+
diagnostics.push(
|
|
182
|
+
createDiagnostic("RS_DEFAULT_APPLIED", {
|
|
183
|
+
formatId: "kiro-skill",
|
|
184
|
+
message: `Default applied for "${def.field}": ${JSON.stringify(def.value)}`,
|
|
185
|
+
source: { path: "SKILL.md" as NormalizedRelativePath },
|
|
186
|
+
}),
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Build canonical candidate
|
|
191
|
+
const candidate: KnowledgeArtifact = {
|
|
192
|
+
name,
|
|
193
|
+
frontmatter: {
|
|
194
|
+
name,
|
|
195
|
+
displayName,
|
|
196
|
+
description,
|
|
197
|
+
keywords,
|
|
198
|
+
author,
|
|
199
|
+
version,
|
|
200
|
+
harnesses: ["kiro"],
|
|
201
|
+
type: "skill",
|
|
202
|
+
inclusion: "manual",
|
|
203
|
+
categories: ["documentation"],
|
|
204
|
+
ecosystem: [],
|
|
205
|
+
depends: [],
|
|
206
|
+
enhances: [],
|
|
207
|
+
maturity: "stable",
|
|
208
|
+
trust: "community",
|
|
209
|
+
audience: "intermediate",
|
|
210
|
+
"model-assumptions": [],
|
|
211
|
+
collections: [],
|
|
212
|
+
"inherit-hooks": false,
|
|
213
|
+
outcomes: [],
|
|
214
|
+
},
|
|
215
|
+
body,
|
|
216
|
+
hooks: [],
|
|
217
|
+
mcpServers: [],
|
|
218
|
+
workflows,
|
|
219
|
+
sourcePath: "SKILL.md",
|
|
220
|
+
extraFields: {},
|
|
221
|
+
bodyOverrides: {},
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
candidate,
|
|
226
|
+
diagnostics,
|
|
227
|
+
consumedPaths: accountant.getConsumedPaths(),
|
|
228
|
+
preservedPaths: accountant.getPreservedPaths(),
|
|
229
|
+
};
|
|
230
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rosetta Stone — Q Developer Harness-Native Source Translator
|
|
3
|
+
*
|
|
4
|
+
* Translates Amazon Q Developer's native format
|
|
5
|
+
* (.qdeveloper/rules/*.md or .amazonq/rules/*.md)
|
|
6
|
+
* into a canonical KnowledgeArtifact candidate.
|
|
7
|
+
*
|
|
8
|
+
* CONSTRAINTS:
|
|
9
|
+
* - NO filesystem, process, clock, random, Git, or network imports
|
|
10
|
+
* - Pure function only
|
|
11
|
+
*
|
|
12
|
+
* Requirements: 2.9, 4.1, 4.2, 4.3, 4.5, 4.6
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import matter from "gray-matter";
|
|
16
|
+
import type { SourceDocument, TranslationDiagnostic } from "../../../schemas";
|
|
17
|
+
import { createDiagnostic } from "../../diagnostics";
|
|
18
|
+
import type {
|
|
19
|
+
SourceTranslationOutput,
|
|
20
|
+
SourceTranslatorContext,
|
|
21
|
+
} from "../../registry";
|
|
22
|
+
import {
|
|
23
|
+
normalizeDocumentOrder,
|
|
24
|
+
SourceAccountant,
|
|
25
|
+
} from "../../source-accounting";
|
|
26
|
+
|
|
27
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
28
|
+
// Internal helpers
|
|
29
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Checks if a document is a Q Developer rule file under .qdeveloper/rules/.
|
|
33
|
+
*/
|
|
34
|
+
function isQDeveloperRuleFile(path: string): boolean {
|
|
35
|
+
return (
|
|
36
|
+
(path.includes(".qdeveloper/rules/") ||
|
|
37
|
+
path.includes(".qdeveloper/rules\\")) &&
|
|
38
|
+
path.endsWith(".md")
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Checks if a document is a Q Developer rule file under .amazonq/rules/.
|
|
44
|
+
*/
|
|
45
|
+
function isAmazonQRuleFile(path: string): boolean {
|
|
46
|
+
return (
|
|
47
|
+
(path.includes(".amazonq/rules/") || path.includes(".amazonq/rules\\")) &&
|
|
48
|
+
path.endsWith(".md")
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Checks if a document is any recognized Q Developer file.
|
|
54
|
+
*/
|
|
55
|
+
function isQDeveloperFile(path: string): boolean {
|
|
56
|
+
return isQDeveloperRuleFile(path) || isAmazonQRuleFile(path);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Derives a kebab-case artifact name from a document path.
|
|
61
|
+
*/
|
|
62
|
+
function deriveArtifactName(path: string): string {
|
|
63
|
+
const segments = path.split("/");
|
|
64
|
+
const base = segments[segments.length - 1] ?? "";
|
|
65
|
+
const name = base.replace(/\.[^.]+$/, "");
|
|
66
|
+
return name
|
|
67
|
+
.toLowerCase()
|
|
68
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
69
|
+
.replace(/^-|-$/g, "");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
73
|
+
// Exported Translator
|
|
74
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Q Developer harness-native source translator.
|
|
78
|
+
*
|
|
79
|
+
* Consumes:
|
|
80
|
+
* - .qdeveloper/rules/*.md → rule file body content
|
|
81
|
+
* - .amazonq/rules/*.md → rule file body content (legacy path)
|
|
82
|
+
*
|
|
83
|
+
* Sets type: "rule" and harnesses: ["qdeveloper"]
|
|
84
|
+
*/
|
|
85
|
+
export function translateQDeveloperNative(
|
|
86
|
+
documents: readonly SourceDocument[],
|
|
87
|
+
context: SourceTranslatorContext,
|
|
88
|
+
): SourceTranslationOutput {
|
|
89
|
+
const accountant = new SourceAccountant();
|
|
90
|
+
const diagnostics: TranslationDiagnostic[] = [];
|
|
91
|
+
const sorted = normalizeDocumentOrder(documents);
|
|
92
|
+
|
|
93
|
+
// Determine artifact name from caller context
|
|
94
|
+
const artifactNameHint = context.callerContext.artifactNameHint as
|
|
95
|
+
| string
|
|
96
|
+
| undefined;
|
|
97
|
+
|
|
98
|
+
// Classify documents — prefer .qdeveloper/rules/, fall back to .amazonq/rules/
|
|
99
|
+
const qdeveloperDocs = sorted.filter((d) => isQDeveloperRuleFile(d.path));
|
|
100
|
+
const amazonqDocs = sorted.filter((d) => isAmazonQRuleFile(d.path));
|
|
101
|
+
|
|
102
|
+
const primaryDoc = qdeveloperDocs[0] ?? amazonqDocs[0];
|
|
103
|
+
|
|
104
|
+
// Handle missing primary file
|
|
105
|
+
if (!primaryDoc) {
|
|
106
|
+
diagnostics.push(
|
|
107
|
+
createDiagnostic("RS_CANONICAL_MISSING_KNOWLEDGE_MD", {
|
|
108
|
+
formatId: context.format.id,
|
|
109
|
+
message:
|
|
110
|
+
"No .qdeveloper/rules/ or .amazonq/rules/ file found in the document set.",
|
|
111
|
+
}),
|
|
112
|
+
);
|
|
113
|
+
return {
|
|
114
|
+
diagnostics,
|
|
115
|
+
consumedPaths: accountant.getConsumedPaths(),
|
|
116
|
+
preservedPaths: accountant.getPreservedPaths(),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Parse primary file
|
|
121
|
+
const content =
|
|
122
|
+
typeof primaryDoc.content === "string"
|
|
123
|
+
? primaryDoc.content
|
|
124
|
+
: new TextDecoder().decode(primaryDoc.content);
|
|
125
|
+
|
|
126
|
+
let frontmatterData: Record<string, unknown> = {};
|
|
127
|
+
let body = "";
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
const parsed = matter(content);
|
|
131
|
+
frontmatterData = { ...parsed.data };
|
|
132
|
+
body = parsed.content.trim();
|
|
133
|
+
} catch {
|
|
134
|
+
diagnostics.push(
|
|
135
|
+
createDiagnostic("RS_CANONICAL_INVALID_FRONTMATTER", {
|
|
136
|
+
formatId: context.format.id,
|
|
137
|
+
message: `Failed to parse frontmatter in "${primaryDoc.path}".`,
|
|
138
|
+
source: { path: primaryDoc.path },
|
|
139
|
+
}),
|
|
140
|
+
);
|
|
141
|
+
body = content.trim();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
accountant.consume(primaryDoc.path);
|
|
145
|
+
accountant.mapField(primaryDoc.path, "content", "body");
|
|
146
|
+
|
|
147
|
+
// Mark remaining files as preserved
|
|
148
|
+
for (const doc of sorted) {
|
|
149
|
+
if (doc.path !== primaryDoc.path && isQDeveloperFile(doc.path)) {
|
|
150
|
+
accountant.preserve(doc.path);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Derive artifact name
|
|
155
|
+
const name = artifactNameHint ?? deriveArtifactName(primaryDoc.path);
|
|
156
|
+
|
|
157
|
+
// Build the canonical candidate
|
|
158
|
+
const candidate: Record<string, unknown> = {
|
|
159
|
+
name,
|
|
160
|
+
frontmatter: {
|
|
161
|
+
name,
|
|
162
|
+
...frontmatterData,
|
|
163
|
+
type: "rule",
|
|
164
|
+
harnesses: ["qdeveloper"],
|
|
165
|
+
},
|
|
166
|
+
body,
|
|
167
|
+
hooks: [],
|
|
168
|
+
mcpServers: [],
|
|
169
|
+
workflows: [],
|
|
170
|
+
sourcePath: primaryDoc.path,
|
|
171
|
+
extraFields: {},
|
|
172
|
+
bodyOverrides: {},
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
return {
|
|
176
|
+
candidate,
|
|
177
|
+
diagnostics,
|
|
178
|
+
consumedPaths: accountant.getConsumedPaths(),
|
|
179
|
+
preservedPaths: accountant.getPreservedPaths(),
|
|
180
|
+
};
|
|
181
|
+
}
|