@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,95 @@
1
+ /**
2
+ * Rosetta Stone — Built-in Source Translator Registry
3
+ *
4
+ * Exports all source translators (path-based and harness-native) and lookup
5
+ * maps from format identifiers to their translator implementations.
6
+ *
7
+ * CONSTRAINTS:
8
+ * - NO filesystem, process, clock, random, Git, or network imports
9
+ * - Pure re-exports only
10
+ *
11
+ * Requirements: 2.9, 4.1, 4.2, 4.3, 4.5, 4.6
12
+ */
13
+
14
+ import type { FormatIdentifier } from "../../../schemas";
15
+ import type { SourceTranslator } from "../../registry";
16
+ import { translateClaudeCodeNative } from "./claude-code-native";
17
+ import { translateClineNative } from "./cline-native";
18
+ import { translateCodexNative } from "./codex-native";
19
+ import { translateCopilotNative } from "./copilot-native";
20
+ import { translateCursorNative } from "./cursor-native";
21
+ import { translateKiroNative } from "./kiro-native";
22
+ import { translateKiroPower } from "./kiro-power";
23
+ import { translateKiroSkill } from "./kiro-skill";
24
+ import { translateQDeveloperNative } from "./qdeveloper-native";
25
+ import { translateSuperpowers } from "./superpowers";
26
+ import { translateWindsurfNative } from "./windsurf-native";
27
+
28
+ // ═══════════════════════════════════════════════════════════════════════════════
29
+ // Re-exports — Path-based source translators (task 6.2)
30
+ // ═══════════════════════════════════════════════════════════════════════════════
31
+
32
+ export { translateKiroPower } from "./kiro-power";
33
+ export { translateKiroSkill } from "./kiro-skill";
34
+ export { translateSuperpowers } from "./superpowers";
35
+
36
+ // ═══════════════════════════════════════════════════════════════════════════════
37
+ // Re-exports — Harness-native source translators (task 6.3)
38
+ // ═══════════════════════════════════════════════════════════════════════════════
39
+
40
+ export { translateClaudeCodeNative } from "./claude-code-native";
41
+ export { translateCodexNative } from "./codex-native";
42
+ export { translateKiroNative } from "./kiro-native";
43
+
44
+ // ═══════════════════════════════════════════════════════════════════════════════
45
+ // Re-exports — Harness-native source translators (task 6.4)
46
+ // ═══════════════════════════════════════════════════════════════════════════════
47
+
48
+ export { translateClineNative } from "./cline-native";
49
+ export { translateCopilotNative } from "./copilot-native";
50
+ export { translateCursorNative } from "./cursor-native";
51
+ export { translateQDeveloperNative } from "./qdeveloper-native";
52
+ export { translateWindsurfNative } from "./windsurf-native";
53
+
54
+ // ═══════════════════════════════════════════════════════════════════════════════
55
+ // Path-Based Source Translator Map
56
+ // ═══════════════════════════════════════════════════════════════════════════════
57
+
58
+ /**
59
+ * Maps format identifiers to their path-based source translators.
60
+ *
61
+ * These handle known directory structures (e.g., kiro-power format expects
62
+ * POWER.md + steering/ files).
63
+ */
64
+ export const PATH_BASED_SOURCE_TRANSLATORS: ReadonlyMap<
65
+ FormatIdentifier,
66
+ SourceTranslator
67
+ > = new Map<FormatIdentifier, SourceTranslator>([
68
+ ["kiro-power" as FormatIdentifier, translateKiroPower],
69
+ ["kiro-skill" as FormatIdentifier, translateKiroSkill],
70
+ ["superpowers" as FormatIdentifier, translateSuperpowers],
71
+ ]);
72
+
73
+ // ═══════════════════════════════════════════════════════════════════════════════
74
+ // Harness-Native Source Translator Map
75
+ // ═══════════════════════════════════════════════════════════════════════════════
76
+
77
+ /**
78
+ * Maps format identifiers to their harness-native source translators.
79
+ *
80
+ * These handle individual harness-native files and convert them to canonical
81
+ * artifacts (e.g., CLAUDE.md, AGENTS.md, .kiro/steering/*.md).
82
+ */
83
+ export const HARNESS_NATIVE_SOURCE_TRANSLATORS: ReadonlyMap<
84
+ FormatIdentifier,
85
+ SourceTranslator
86
+ > = new Map<FormatIdentifier, SourceTranslator>([
87
+ ["kiro" as FormatIdentifier, translateKiroNative],
88
+ ["claude-code" as FormatIdentifier, translateClaudeCodeNative],
89
+ ["codex" as FormatIdentifier, translateCodexNative],
90
+ ["copilot" as FormatIdentifier, translateCopilotNative],
91
+ ["cursor" as FormatIdentifier, translateCursorNative],
92
+ ["windsurf" as FormatIdentifier, translateWindsurfNative],
93
+ ["cline" as FormatIdentifier, translateClineNative],
94
+ ["qdeveloper" as FormatIdentifier, translateQDeveloperNative],
95
+ ]);
@@ -0,0 +1,462 @@
1
+ /**
2
+ * Rosetta Stone — Kiro Harness-Native Source Translator
3
+ *
4
+ * Translates Kiro's native format (.kiro/steering/*.md, hooks, MCP servers)
5
+ * into a canonical KnowledgeArtifact candidate. Handles both "steering" and
6
+ * "power" variants.
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 {
17
+ CanonicalEvent,
18
+ CanonicalHook,
19
+ McpServerDefinition,
20
+ SourceDocument,
21
+ TranslationDiagnostic,
22
+ } from "../../../schemas";
23
+ import { createDiagnostic } from "../../diagnostics";
24
+ import type {
25
+ SourceTranslationOutput,
26
+ SourceTranslatorContext,
27
+ } from "../../registry";
28
+ import {
29
+ namespacedExtraField,
30
+ normalizeDocumentOrder,
31
+ SourceAccountant,
32
+ } from "../../source-accounting";
33
+
34
+ // ═══════════════════════════════════════════════════════════════════════════════
35
+ // Internal helpers
36
+ // ═══════════════════════════════════════════════════════════════════════════════
37
+
38
+ const KIRO_EVENT_MAP: Record<string, CanonicalEvent> = {
39
+ fileEdited: "file_edited",
40
+ fileCreated: "file_created",
41
+ fileDeleted: "file_deleted",
42
+ agentStop: "agent_stop",
43
+ promptSubmit: "prompt_submit",
44
+ preToolUse: "pre_tool_use",
45
+ postToolUse: "post_tool_use",
46
+ preTaskExecution: "pre_task",
47
+ postTaskExecution: "post_task",
48
+ userTriggered: "user_triggered",
49
+ file_edited: "file_edited",
50
+ file_created: "file_created",
51
+ file_deleted: "file_deleted",
52
+ agent_stop: "agent_stop",
53
+ prompt_submit: "prompt_submit",
54
+ pre_tool_use: "pre_tool_use",
55
+ post_tool_use: "post_tool_use",
56
+ pre_task: "pre_task",
57
+ post_task: "post_task",
58
+ user_triggered: "user_triggered",
59
+ };
60
+
61
+ function mapKiroEvent(rawEvent: string): CanonicalEvent {
62
+ return (KIRO_EVENT_MAP[rawEvent] ?? rawEvent) as CanonicalEvent;
63
+ }
64
+
65
+ /**
66
+ * Derives a kebab-case artifact name from a document path.
67
+ */
68
+ function deriveArtifactName(path: string): string {
69
+ const segments = path.split("/");
70
+ const base = segments[segments.length - 1] ?? "";
71
+ const name =
72
+ base.replace(/\.kiro\.hook$/, "") || base.replace(/\.[^.]+$/, "");
73
+ return name
74
+ .toLowerCase()
75
+ .replace(/[^a-z0-9]+/g, "-")
76
+ .replace(/^-|-$/g, "");
77
+ }
78
+
79
+ /**
80
+ * Checks if a document is a primary steering/power markdown file.
81
+ */
82
+ function isPrimaryMd(path: string): boolean {
83
+ return (
84
+ path.endsWith(".md") &&
85
+ !path.endsWith(".kiro.hook") &&
86
+ !path.includes("hooks/") &&
87
+ !path.includes("mcp-servers")
88
+ );
89
+ }
90
+
91
+ /**
92
+ * Checks if a document is a hook file.
93
+ */
94
+ function isHookFile(path: string): boolean {
95
+ return path.endsWith(".kiro.hook") || path.includes("hooks/");
96
+ }
97
+
98
+ /**
99
+ * Checks if a document is an MCP server definition file.
100
+ */
101
+ function isMcpFile(path: string): boolean {
102
+ return path.includes("mcp-servers") || path.endsWith("mcp.json");
103
+ }
104
+
105
+ /**
106
+ * Parse a hook from a JSON document.
107
+ */
108
+ function parseHookDocument(
109
+ doc: SourceDocument,
110
+ diagnostics: TranslationDiagnostic[],
111
+ ): CanonicalHook[] {
112
+ const content =
113
+ typeof doc.content === "string"
114
+ ? doc.content
115
+ : new TextDecoder().decode(doc.content);
116
+
117
+ let data: Record<string, unknown>;
118
+ try {
119
+ data = JSON.parse(content);
120
+ } catch {
121
+ diagnostics.push(
122
+ createDiagnostic("RS_CANONICAL_INVALID_YAML", {
123
+ message: `Hook file "${doc.path}" contains invalid JSON.`,
124
+ source: { path: doc.path },
125
+ }),
126
+ );
127
+ return [];
128
+ }
129
+
130
+ if (!data || typeof data !== "object") return [];
131
+
132
+ const hooks: CanonicalHook[] = [];
133
+ const hook: Partial<CanonicalHook> = {};
134
+
135
+ // Map event
136
+ const rawEvent =
137
+ (data.event as string) ??
138
+ ((data.when as Record<string, unknown>)?.event as string);
139
+ if (rawEvent) {
140
+ hook.event = mapKiroEvent(rawEvent);
141
+ }
142
+
143
+ // Map action
144
+ const rawAction =
145
+ (data.action as Record<string, unknown>) ??
146
+ (data.then as Record<string, unknown>);
147
+ if (rawAction) {
148
+ if (
149
+ rawAction.type === "run_command" ||
150
+ (rawAction.command && !rawAction.prompt)
151
+ ) {
152
+ hook.action = {
153
+ type: "run_command",
154
+ command:
155
+ (rawAction.command as string) ?? (rawAction.prompt as string) ?? "",
156
+ };
157
+ } else if (rawAction.type === "ask_agent" || rawAction.prompt) {
158
+ hook.action = {
159
+ type: "ask_agent",
160
+ prompt:
161
+ (rawAction.prompt as string) ?? (rawAction.command as string) ?? "",
162
+ };
163
+ }
164
+ }
165
+
166
+ // Map name and description
167
+ hook.name =
168
+ (data.name as string) ??
169
+ (data.id as string) ??
170
+ deriveArtifactName(doc.path);
171
+ if (data.description) hook.description = data.description as string;
172
+
173
+ // Map condition
174
+ const whenObj = data.when as Record<string, unknown> | undefined;
175
+ const conditionObj = data.condition as Record<string, unknown> | undefined;
176
+ const filePatterns =
177
+ (conditionObj?.file_patterns as string[]) ??
178
+ (whenObj?.filePatterns as string[]);
179
+ const toolTypes =
180
+ (conditionObj?.tool_types as string[]) ?? (whenObj?.toolTypes as string[]);
181
+
182
+ if (filePatterns || toolTypes) {
183
+ hook.condition = {};
184
+ if (filePatterns) {
185
+ hook.condition.file_patterns = Array.isArray(filePatterns)
186
+ ? filePatterns
187
+ : [filePatterns as unknown as string];
188
+ }
189
+ if (toolTypes) {
190
+ hook.condition.tool_types = Array.isArray(toolTypes)
191
+ ? toolTypes
192
+ : [toolTypes as unknown as string];
193
+ }
194
+ }
195
+
196
+ if (hook.event && hook.action && hook.name) {
197
+ hooks.push(hook as CanonicalHook);
198
+ }
199
+
200
+ return hooks;
201
+ }
202
+
203
+ /**
204
+ * Parse MCP server definitions from a JSON document.
205
+ */
206
+ function parseMcpDocument(
207
+ doc: SourceDocument,
208
+ diagnostics: TranslationDiagnostic[],
209
+ ): McpServerDefinition[] {
210
+ const content =
211
+ typeof doc.content === "string"
212
+ ? doc.content
213
+ : new TextDecoder().decode(doc.content);
214
+
215
+ let data: unknown;
216
+ try {
217
+ data = JSON.parse(content);
218
+ } catch {
219
+ diagnostics.push(
220
+ createDiagnostic("RS_CANONICAL_INVALID_YAML", {
221
+ message: `MCP file "${doc.path}" contains invalid JSON.`,
222
+ source: { path: doc.path },
223
+ }),
224
+ );
225
+ return [];
226
+ }
227
+
228
+ const servers: McpServerDefinition[] = [];
229
+
230
+ if (data && typeof data === "object" && !Array.isArray(data)) {
231
+ const obj = data as Record<string, unknown>;
232
+ // Handle { mcpServers: { name: config } } shape
233
+ const mcpServers = (obj.mcpServers ?? obj) as Record<string, unknown>;
234
+ for (const [name, config] of Object.entries(mcpServers)) {
235
+ if (!config || typeof config !== "object") continue;
236
+ const cfg = config as Record<string, unknown>;
237
+ if (cfg.url) {
238
+ const transport =
239
+ cfg.type === "http" ? ("http" as const) : ("sse" as const);
240
+ servers.push({
241
+ name,
242
+ transport,
243
+ url: cfg.url as string,
244
+ env: (cfg.env as Record<string, string>) ?? {},
245
+ ...(cfg.timeout ? { timeout: cfg.timeout as number } : {}),
246
+ });
247
+ } else if (cfg.command) {
248
+ servers.push({
249
+ name,
250
+ transport: "stdio" as const,
251
+ command: cfg.command as string,
252
+ args: (cfg.args as string[]) ?? [],
253
+ env: (cfg.env as Record<string, string>) ?? {},
254
+ ...(cfg.timeout ? { timeout: cfg.timeout as number } : {}),
255
+ });
256
+ }
257
+ }
258
+ } else if (Array.isArray(data)) {
259
+ for (const entry of data) {
260
+ if (!entry || typeof entry !== "object") continue;
261
+ const cfg = entry as Record<string, unknown>;
262
+ if (!cfg.name) continue;
263
+ if (cfg.url) {
264
+ servers.push({
265
+ name: cfg.name as string,
266
+ transport:
267
+ cfg.transport === "http" ? ("http" as const) : ("sse" as const),
268
+ url: cfg.url as string,
269
+ env: (cfg.env as Record<string, string>) ?? {},
270
+ });
271
+ } else if (cfg.command) {
272
+ servers.push({
273
+ name: cfg.name as string,
274
+ transport: "stdio" as const,
275
+ command: cfg.command as string,
276
+ args: (cfg.args as string[]) ?? [],
277
+ env: (cfg.env as Record<string, string>) ?? {},
278
+ });
279
+ }
280
+ }
281
+ }
282
+
283
+ return servers;
284
+ }
285
+
286
+ // ═══════════════════════════════════════════════════════════════════════════════
287
+ // Exported Translator
288
+ // ═══════════════════════════════════════════════════════════════════════════════
289
+
290
+ /**
291
+ * Kiro harness-native source translator.
292
+ *
293
+ * Consumes:
294
+ * - Primary steering/power markdown file (frontmatter + body)
295
+ * - Hook files (.kiro.hook JSON → CanonicalHook[])
296
+ * - MCP server definitions (JSON → McpServerDefinition[])
297
+ *
298
+ * Handles both "steering" and "power" variants:
299
+ * - Power: looks for POWER.md/SKILL.md frontmatter structure
300
+ * - Steering: standard steering markdown format
301
+ */
302
+ export function translateKiroNative(
303
+ documents: readonly SourceDocument[],
304
+ context: SourceTranslatorContext,
305
+ ): SourceTranslationOutput {
306
+ const accountant = new SourceAccountant();
307
+ const diagnostics: TranslationDiagnostic[] = [];
308
+ const sorted = normalizeDocumentOrder(documents);
309
+
310
+ // Determine artifact name from caller context
311
+ const artifactNameHint = context.callerContext.artifactNameHint as
312
+ | string
313
+ | undefined;
314
+
315
+ // Classify documents
316
+ const primaryDocs = sorted.filter((d) => isPrimaryMd(d.path));
317
+ const hookDocs = sorted.filter((d) => isHookFile(d.path));
318
+ const mcpDocs = sorted.filter((d) => isMcpFile(d.path));
319
+
320
+ // Handle missing primary file
321
+ if (primaryDocs.length === 0) {
322
+ diagnostics.push(
323
+ createDiagnostic("RS_CANONICAL_MISSING_KNOWLEDGE_MD", {
324
+ formatId: context.format.id,
325
+ message:
326
+ "No primary Kiro steering or power markdown file found in the document set.",
327
+ }),
328
+ );
329
+ return {
330
+ diagnostics,
331
+ consumedPaths: accountant.getConsumedPaths(),
332
+ preservedPaths: accountant.getPreservedPaths(),
333
+ };
334
+ }
335
+
336
+ // Parse primary markdown (use first matching file)
337
+ const primaryDoc = primaryDocs[0];
338
+ const content =
339
+ typeof primaryDoc.content === "string"
340
+ ? primaryDoc.content
341
+ : new TextDecoder().decode(primaryDoc.content);
342
+
343
+ let frontmatterData: Record<string, unknown> = {};
344
+ let body = "";
345
+
346
+ try {
347
+ const parsed = matter(content);
348
+ frontmatterData = { ...parsed.data };
349
+ body = parsed.content.trim();
350
+ } catch {
351
+ diagnostics.push(
352
+ createDiagnostic("RS_CANONICAL_INVALID_FRONTMATTER", {
353
+ formatId: context.format.id,
354
+ message: `Failed to parse frontmatter in "${primaryDoc.path}".`,
355
+ source: { path: primaryDoc.path },
356
+ }),
357
+ );
358
+ // Continue with empty data — body still available as raw content
359
+ body = content.trim();
360
+ }
361
+
362
+ accountant.consume(primaryDoc.path);
363
+ accountant.mapField(primaryDoc.path, "frontmatter", "frontmatter");
364
+ accountant.mapField(primaryDoc.path, "content", "body");
365
+
366
+ // Mark additional primary docs as preserved (multi-file sets)
367
+ for (let i = 1; i < primaryDocs.length; i++) {
368
+ accountant.preserve(primaryDocs[i].path);
369
+ }
370
+
371
+ // Parse hooks
372
+ const allHooks: CanonicalHook[] = [];
373
+ for (const hookDoc of hookDocs) {
374
+ const hooks = parseHookDocument(hookDoc, diagnostics);
375
+ allHooks.push(...hooks);
376
+ accountant.consume(hookDoc.path);
377
+ accountant.mapField(hookDoc.path, "hooks", "hooks");
378
+ }
379
+
380
+ // Parse MCP servers
381
+ const allMcpServers: McpServerDefinition[] = [];
382
+ for (const mcpDoc of mcpDocs) {
383
+ const servers = parseMcpDocument(mcpDoc, diagnostics);
384
+ allMcpServers.push(...servers);
385
+ accountant.consume(mcpDoc.path);
386
+ accountant.mapField(mcpDoc.path, "mcpServers", "mcpServers");
387
+ }
388
+
389
+ // Derive artifact name
390
+ const name =
391
+ artifactNameHint ??
392
+ (frontmatterData.name as string) ??
393
+ deriveArtifactName(primaryDoc.path);
394
+
395
+ // Build extra fields from source-specific frontmatter keys
396
+ const extraFields: Record<string, unknown> = {};
397
+ const knownFrontmatterKeys = new Set([
398
+ "name",
399
+ "displayName",
400
+ "description",
401
+ "keywords",
402
+ "author",
403
+ "version",
404
+ "harnesses",
405
+ "type",
406
+ "inclusion",
407
+ "file_patterns",
408
+ "categories",
409
+ "ecosystem",
410
+ "depends",
411
+ "enhances",
412
+ "id",
413
+ "license",
414
+ "maturity",
415
+ "trust",
416
+ "risk-level",
417
+ "audience",
418
+ "model-assumptions",
419
+ "successor",
420
+ "replaces",
421
+ "collections",
422
+ "inherit-hooks",
423
+ "visibility",
424
+ "priority",
425
+ "outcomes",
426
+ "harness-config",
427
+ "migrations",
428
+ ]);
429
+
430
+ for (const [key, value] of Object.entries(frontmatterData)) {
431
+ if (!knownFrontmatterKeys.has(key)) {
432
+ extraFields[
433
+ namespacedExtraField(context.format.id, primaryDoc.path, key)
434
+ ] = value;
435
+ }
436
+ }
437
+
438
+ // Build the canonical candidate
439
+ const candidate: Record<string, unknown> = {
440
+ name,
441
+ frontmatter: {
442
+ name,
443
+ ...frontmatterData,
444
+ type: (frontmatterData.type as string) ?? "skill",
445
+ harnesses: (frontmatterData.harnesses as string[]) ?? ["kiro"],
446
+ },
447
+ body,
448
+ hooks: allHooks,
449
+ mcpServers: allMcpServers,
450
+ workflows: [],
451
+ sourcePath: primaryDoc.path,
452
+ extraFields,
453
+ bodyOverrides: {},
454
+ };
455
+
456
+ return {
457
+ candidate,
458
+ diagnostics,
459
+ consumedPaths: accountant.getConsumedPaths(),
460
+ preservedPaths: accountant.getPreservedPaths(),
461
+ };
462
+ }