@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,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
+ }