@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,240 @@
1
+ /**
2
+ * Rosetta Stone — Superpowers Source Translator
3
+ *
4
+ * Pure translator for the `superpowers` format:
5
+ * - Consumes `SKILL.md` → extracts frontmatter (name, description) and body
6
+ * - Consumes companion `.md` files → maps to workflows
7
+ * - Sets reasonable defaults for missing fields
8
+ * - Sets `type: "skill"`, `harnesses: ["claude-code", "codex", "cursor"]`
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 superpowers document set into a canonical KnowledgeArtifact candidate.
39
+ *
40
+ * Expected documents:
41
+ * - `SKILL.md` (required): YAML frontmatter + Markdown body
42
+ * - Other `*.md` files (optional): mapped to workflows as companions
43
+ * - Non-md files: preserved
44
+ */
45
+ export function translateSuperpowers(
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: "superpowers",
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: "superpowers",
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
+
114
+ // Collect companion .md files as workflows (exclude SKILL.md itself)
115
+ const companionDocs = documents
116
+ .filter((d) => d.path !== "SKILL.md" && d.path.endsWith(".md"))
117
+ .sort((a, b) => codePointCompare(a.path, b.path));
118
+
119
+ const workflows = companionDocs.map((doc) => {
120
+ accountant.consume(doc.path);
121
+ const content =
122
+ typeof doc.content === "string"
123
+ ? doc.content
124
+ : new TextDecoder().decode(doc.content);
125
+ const name = doc.path.replace(/\.md$/, "");
126
+ return { name, filename: doc.path, content };
127
+ });
128
+
129
+ // Preserve non-md files
130
+ for (const doc of documents) {
131
+ if (doc.path === "SKILL.md") continue;
132
+ if (doc.path.endsWith(".md")) continue;
133
+ accountant.preserve(doc.path);
134
+ }
135
+
136
+ // Extract keywords from frontmatter
137
+ const keywords: string[] = [];
138
+ if (Array.isArray(sourceFm.keywords)) {
139
+ keywords.push(...sourceFm.keywords.map(String));
140
+ accountant.mapField("SKILL.md", "keywords", "keywords", false);
141
+ }
142
+
143
+ // Extract depends from requires
144
+ const depends: string[] = [];
145
+ if (Array.isArray(sourceFm.requires)) {
146
+ depends.push(...sourceFm.requires.map(String));
147
+ accountant.mapField("SKILL.md", "requires", "depends", true);
148
+ }
149
+
150
+ // Apply defaults
151
+ const appliedDefaults: Array<{
152
+ field: string;
153
+ value: unknown;
154
+ rule: string;
155
+ }> = [];
156
+
157
+ // DisplayName: derive from name if not provided
158
+ const displayName = String(
159
+ sourceFm.displayName ||
160
+ name.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()),
161
+ );
162
+ if (!sourceFm.displayName) {
163
+ appliedDefaults.push({
164
+ field: "displayName",
165
+ value: displayName,
166
+ rule: "superpowers-default-displayName",
167
+ });
168
+ }
169
+
170
+ const description = String(sourceFm.description || "");
171
+
172
+ const author = String(sourceFm.author || "obra");
173
+ if (!sourceFm.author) {
174
+ appliedDefaults.push({
175
+ field: "author",
176
+ value: author,
177
+ rule: "superpowers-default-author",
178
+ });
179
+ }
180
+
181
+ const version = "0.1.0";
182
+ appliedDefaults.push({
183
+ field: "version",
184
+ value: version,
185
+ rule: "superpowers-default-version",
186
+ });
187
+
188
+ // Emit diagnostics for applied defaults
189
+ for (const def of appliedDefaults) {
190
+ accountant.applyDefault(def.field, def.value, def.rule);
191
+ diagnostics.push(
192
+ createDiagnostic("RS_DEFAULT_APPLIED", {
193
+ formatId: "superpowers",
194
+ message: `Default applied for "${def.field}": ${JSON.stringify(def.value)}`,
195
+ source: { path: "SKILL.md" as NormalizedRelativePath },
196
+ }),
197
+ );
198
+ }
199
+
200
+ // Build canonical candidate
201
+ const candidate: KnowledgeArtifact = {
202
+ name,
203
+ frontmatter: {
204
+ name,
205
+ displayName,
206
+ description,
207
+ keywords,
208
+ author,
209
+ version,
210
+ harnesses: ["claude-code", "codex", "cursor"],
211
+ type: "skill",
212
+ inclusion: "manual",
213
+ categories: ["documentation"],
214
+ ecosystem: [],
215
+ depends,
216
+ enhances: [],
217
+ maturity: "stable",
218
+ trust: "community",
219
+ audience: "intermediate",
220
+ "model-assumptions": [],
221
+ collections: [],
222
+ "inherit-hooks": false,
223
+ outcomes: [],
224
+ },
225
+ body,
226
+ hooks: [],
227
+ mcpServers: [],
228
+ workflows,
229
+ sourcePath: "SKILL.md",
230
+ extraFields: {},
231
+ bodyOverrides: {},
232
+ };
233
+
234
+ return {
235
+ candidate,
236
+ diagnostics,
237
+ consumedPaths: accountant.getConsumedPaths(),
238
+ preservedPaths: accountant.getPreservedPaths(),
239
+ };
240
+ }
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Rosetta Stone — Windsurf Harness-Native Source Translator
3
+ *
4
+ * Translates Windsurf's native format (.windsurfrules or .windsurf/rules/*.md)
5
+ * into a canonical KnowledgeArtifact candidate.
6
+ *
7
+ * CONSTRAINTS:
8
+ * - NO filesystem, process, clock, random, Git, or network imports
9
+ * - Pure function only
10
+ *
11
+ * Requirements: 2.9, 4.1, 4.2, 4.3, 4.5, 4.6
12
+ */
13
+
14
+ import matter from "gray-matter";
15
+ import type { SourceDocument, TranslationDiagnostic } from "../../../schemas";
16
+ import { createDiagnostic } from "../../diagnostics";
17
+ import type {
18
+ SourceTranslationOutput,
19
+ SourceTranslatorContext,
20
+ } from "../../registry";
21
+ import {
22
+ normalizeDocumentOrder,
23
+ SourceAccountant,
24
+ } from "../../source-accounting";
25
+
26
+ // ═══════════════════════════════════════════════════════════════════════════════
27
+ // Internal helpers
28
+ // ═══════════════════════════════════════════════════════════════════════════════
29
+
30
+ /**
31
+ * Checks if a document is the root .windsurfrules file.
32
+ */
33
+ function isWindsurfRulesRoot(path: string): boolean {
34
+ return path === ".windsurfrules" || path.endsWith("/.windsurfrules");
35
+ }
36
+
37
+ /**
38
+ * Checks if a document is a Windsurf rule file under .windsurf/rules/.
39
+ */
40
+ function isWindsurfRuleFile(path: string): boolean {
41
+ return (
42
+ (path.includes(".windsurf/rules/") || path.includes(".windsurf/rules\\")) &&
43
+ path.endsWith(".md")
44
+ );
45
+ }
46
+
47
+ /**
48
+ * Checks if a document is any recognized Windsurf file.
49
+ */
50
+ function isWindsurfFile(path: string): boolean {
51
+ return isWindsurfRulesRoot(path) || isWindsurfRuleFile(path);
52
+ }
53
+
54
+ /**
55
+ * Derives a kebab-case artifact name from a document path.
56
+ */
57
+ function deriveArtifactName(path: string): string {
58
+ const segments = path.split("/");
59
+ const base = segments[segments.length - 1] ?? "";
60
+ const name = base.replace(/\.[^.]+$/, "");
61
+ return name
62
+ .toLowerCase()
63
+ .replace(/[^a-z0-9]+/g, "-")
64
+ .replace(/^-|-$/g, "");
65
+ }
66
+
67
+ // ═══════════════════════════════════════════════════════════════════════════════
68
+ // Exported Translator
69
+ // ═══════════════════════════════════════════════════════════════════════════════
70
+
71
+ /**
72
+ * Windsurf harness-native source translator.
73
+ *
74
+ * Consumes:
75
+ * - .windsurfrules → body content becomes the artifact body
76
+ * - .windsurf/rules/*.md → rule file body content
77
+ *
78
+ * Sets type: "rule" and harnesses: ["windsurf"]
79
+ */
80
+ export function translateWindsurfNative(
81
+ documents: readonly SourceDocument[],
82
+ context: SourceTranslatorContext,
83
+ ): SourceTranslationOutput {
84
+ const accountant = new SourceAccountant();
85
+ const diagnostics: TranslationDiagnostic[] = [];
86
+ const sorted = normalizeDocumentOrder(documents);
87
+
88
+ // Determine artifact name from caller context
89
+ const artifactNameHint = context.callerContext.artifactNameHint as
90
+ | string
91
+ | undefined;
92
+
93
+ // Classify documents — prefer .windsurfrules, fall back to rule dir files
94
+ const rootRuleDocs = sorted.filter((d) => isWindsurfRulesRoot(d.path));
95
+ const ruleDirDocs = sorted.filter((d) => isWindsurfRuleFile(d.path));
96
+
97
+ const primaryDoc = rootRuleDocs[0] ?? ruleDirDocs[0];
98
+
99
+ // Handle missing primary file
100
+ if (!primaryDoc) {
101
+ diagnostics.push(
102
+ createDiagnostic("RS_CANONICAL_MISSING_KNOWLEDGE_MD", {
103
+ formatId: context.format.id,
104
+ message:
105
+ "No .windsurfrules or .windsurf/rules/ file found in the document set.",
106
+ }),
107
+ );
108
+ return {
109
+ diagnostics,
110
+ consumedPaths: accountant.getConsumedPaths(),
111
+ preservedPaths: accountant.getPreservedPaths(),
112
+ };
113
+ }
114
+
115
+ // Parse primary file
116
+ const content =
117
+ typeof primaryDoc.content === "string"
118
+ ? primaryDoc.content
119
+ : new TextDecoder().decode(primaryDoc.content);
120
+
121
+ let frontmatterData: Record<string, unknown> = {};
122
+ let body = "";
123
+
124
+ try {
125
+ const parsed = matter(content);
126
+ frontmatterData = { ...parsed.data };
127
+ body = parsed.content.trim();
128
+ } catch {
129
+ diagnostics.push(
130
+ createDiagnostic("RS_CANONICAL_INVALID_FRONTMATTER", {
131
+ formatId: context.format.id,
132
+ message: `Failed to parse frontmatter in "${primaryDoc.path}".`,
133
+ source: { path: primaryDoc.path },
134
+ }),
135
+ );
136
+ body = content.trim();
137
+ }
138
+
139
+ accountant.consume(primaryDoc.path);
140
+ accountant.mapField(primaryDoc.path, "content", "body");
141
+
142
+ // Mark remaining files as preserved
143
+ for (const doc of sorted) {
144
+ if (doc.path !== primaryDoc.path && isWindsurfFile(doc.path)) {
145
+ accountant.preserve(doc.path);
146
+ }
147
+ }
148
+
149
+ // Derive artifact name
150
+ const name = artifactNameHint ?? deriveArtifactName(primaryDoc.path);
151
+
152
+ // Build the canonical candidate
153
+ const candidate: Record<string, unknown> = {
154
+ name,
155
+ frontmatter: {
156
+ name,
157
+ ...frontmatterData,
158
+ type: "rule",
159
+ harnesses: ["windsurf"],
160
+ },
161
+ body,
162
+ hooks: [],
163
+ mcpServers: [],
164
+ workflows: [],
165
+ sourcePath: primaryDoc.path,
166
+ extraFields: {},
167
+ bodyOverrides: {},
168
+ };
169
+
170
+ return {
171
+ candidate,
172
+ diagnostics,
173
+ consumedPaths: accountant.getConsumedPaths(),
174
+ preservedPaths: accountant.getPreservedPaths(),
175
+ };
176
+ }
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Rosetta Stone — Claude Code Target Translator
3
+ *
4
+ * Wraps the existing claude-code adapter logic with resolved variants, body
5
+ * overrides, template bundles, effective compatibility actions, structured
6
+ * diagnostics, and deterministic TranslationPlan output.
7
+ *
8
+ * CONSTRAINTS:
9
+ * - NO filesystem, process, clock, random, Git, or network imports
10
+ * - Pure function only
11
+ *
12
+ * Requirements: 6.1, 6.5, 6.6, 7.3, 7.5, 13.8
13
+ */
14
+
15
+ import type {
16
+ DegradationRecord,
17
+ KnowledgeArtifact,
18
+ OutputFile,
19
+ TranslationDiagnostic,
20
+ } from "../../../schemas";
21
+ import {
22
+ evaluateCompatibility,
23
+ identifyUsedCapabilities,
24
+ resolveEffectiveProfile,
25
+ } from "../../compatibility";
26
+ import { createDiagnostic } from "../../diagnostics";
27
+ import { createPlan } from "../../plan";
28
+ import type {
29
+ TargetTranslationOutput,
30
+ TargetTranslatorContext,
31
+ } from "../../registry";
32
+
33
+ // ═══════════════════════════════════════════════════════════════════════════════
34
+ // Constants
35
+ // ═══════════════════════════════════════════════════════════════════════════════
36
+
37
+ /** Claude Code supported hook events */
38
+ const SUPPORTED_CLAUDE_EVENTS = new Set(["agent_stop"]);
39
+
40
+ // ═══════════════════════════════════════════════════════════════════════════════
41
+ // Claude Code Target Translator
42
+ // ═══════════════════════════════════════════════════════════════════════════════
43
+
44
+ /**
45
+ * Translate a canonical KnowledgeArtifact into Claude Code harness-native output.
46
+ *
47
+ * Renders CLAUDE.md output, handles MCP server configuration (.claude/mcp.json),
48
+ * and translates agent_stop hooks to .claude/settings.json.
49
+ *
50
+ * Applies body overrides for "claude-code" harness when available.
51
+ */
52
+ export function translateClaudeCodeTarget(
53
+ artifact: Record<string, unknown>,
54
+ context: TargetTranslatorContext,
55
+ ): TargetTranslationOutput {
56
+ const diagnostics: TranslationDiagnostic[] = [];
57
+ const degradations: DegradationRecord[] = [];
58
+
59
+ const art = artifact as unknown as KnowledgeArtifact;
60
+ const { format, variant, canonicalSchemaVersion, templates } = context;
61
+
62
+ // Resolve body override for claude-code harness
63
+ const body = art.bodyOverrides?.["claude-code"] ?? art.body;
64
+
65
+ // Evaluate compatibility
66
+ const variantContract = format.variants[variant];
67
+ const effectiveProfile = resolveEffectiveProfile(format, variantContract);
68
+ const usedCapabilities = identifyUsedCapabilities(art);
69
+ const evaluation = evaluateCompatibility(
70
+ effectiveProfile,
71
+ usedCapabilities,
72
+ art,
73
+ );
74
+ diagnostics.push(...evaluation.diagnostics);
75
+ degradations.push(...evaluation.degradations);
76
+
77
+ // Render output files
78
+ const outputFiles: OutputFile[] = [];
79
+
80
+ // Generate CLAUDE.md via template
81
+ const claudeContent = templates.render("claude-code/claude.md.njk", {
82
+ artifact: art,
83
+ body,
84
+ });
85
+ outputFiles.push({
86
+ relativePath: "CLAUDE.md",
87
+ content: claudeContent,
88
+ executable: false,
89
+ });
90
+
91
+ // Translate agent_stop + run_command hooks to .claude/settings.json
92
+ const stopHooks: Array<{ command: string }> = [];
93
+ for (const hook of art.hooks) {
94
+ if (!SUPPORTED_CLAUDE_EVENTS.has(hook.event)) {
95
+ diagnostics.push(
96
+ createDiagnostic("RS_COMPATIBILITY_PARTIAL", {
97
+ formatId: format.id,
98
+ message: `Hook "${hook.name}" uses event "${hook.event}" which is not supported by Claude Code. Skipped.`,
99
+ remediation:
100
+ "Only agent_stop hooks with run_command actions are supported.",
101
+ canonical: {
102
+ artifactName: art.name,
103
+ fieldPath: "hooks",
104
+ },
105
+ }),
106
+ );
107
+ continue;
108
+ }
109
+ if (hook.action.type === "run_command") {
110
+ stopHooks.push({ command: hook.action.command });
111
+ }
112
+ }
113
+
114
+ if (stopHooks.length > 0) {
115
+ const settings = {
116
+ hooks: {
117
+ stop: stopHooks.map((h) => ({ type: "command", command: h.command })),
118
+ },
119
+ };
120
+ const settingsContent = templates.render("claude-code/settings.json.njk", {
121
+ settings,
122
+ });
123
+ outputFiles.push({
124
+ relativePath: ".claude/settings.json",
125
+ content: settingsContent,
126
+ executable: false,
127
+ });
128
+ }
129
+
130
+ // Generate .claude/mcp.json
131
+ if (art.mcpServers.length > 0) {
132
+ const mcpConfig = buildMcpConfigData(art.mcpServers);
133
+ const mcpContent = templates.render("claude-code/mcp.json.njk", {
134
+ mcpConfig,
135
+ });
136
+ outputFiles.push({
137
+ relativePath: ".claude/mcp.json",
138
+ content: mcpContent,
139
+ executable: false,
140
+ });
141
+ }
142
+
143
+ // Build plan deterministically
144
+ const plan = createPlan(format.id, canonicalSchemaVersion, outputFiles, {
145
+ variant,
146
+ });
147
+
148
+ return { plan, diagnostics, degradations };
149
+ }
150
+
151
+ // ═══════════════════════════════════════════════════════════════════════════════
152
+ // Internal Helpers
153
+ // ═══════════════════════════════════════════════════════════════════════════════
154
+
155
+ /**
156
+ * Build MCP config data structure for Claude Code template rendering.
157
+ */
158
+ function buildMcpConfigData(
159
+ servers: KnowledgeArtifact["mcpServers"],
160
+ ): Record<string, unknown> {
161
+ const mcpServers: Record<string, unknown> = {};
162
+ for (const server of servers) {
163
+ if ("command" in server) {
164
+ mcpServers[server.name] = {
165
+ command: server.command,
166
+ args: server.args,
167
+ env: server.env,
168
+ ...(server.timeout ? { timeout: server.timeout } : {}),
169
+ };
170
+ } else {
171
+ mcpServers[server.name] = {
172
+ url: server.url,
173
+ ...(server.env && Object.keys(server.env).length > 0
174
+ ? { env: server.env }
175
+ : {}),
176
+ ...(server.timeout ? { timeout: server.timeout } : {}),
177
+ };
178
+ }
179
+ }
180
+ return { mcpServers };
181
+ }
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Rosetta Stone — Cline Target Translator
3
+ *
4
+ * Translates a canonical KnowledgeArtifact into Cline's native output
5
+ * format (.cline/rules/<name>.md).
6
+ *
7
+ * CONSTRAINTS:
8
+ * - NO filesystem, process, clock, random, Git, or network imports
9
+ * - Pure function only
10
+ *
11
+ * Requirements: 6.1, 6.5, 6.6, 12.2, 12.5, 13.8
12
+ */
13
+
14
+ import type {
15
+ DegradationRecord,
16
+ KnowledgeArtifact,
17
+ OutputFile,
18
+ TranslationDiagnostic,
19
+ } from "../../../schemas";
20
+ import {
21
+ evaluateCompatibility,
22
+ identifyUsedCapabilities,
23
+ resolveEffectiveProfile,
24
+ } from "../../compatibility";
25
+ import { codePointCompare } from "../../contracts";
26
+ import { createPlan } from "../../plan";
27
+ import type {
28
+ TargetTranslationOutput,
29
+ TargetTranslatorContext,
30
+ } from "../../registry";
31
+
32
+ // ═══════════════════════════════════════════════════════════════════════════════
33
+ // Cline Target Translator
34
+ // ═══════════════════════════════════════════════════════════════════════════════
35
+
36
+ /**
37
+ * Translate a canonical artifact into Cline-native output files.
38
+ *
39
+ * Produces .cline/rules/<artifact-name>.md
40
+ */
41
+ export function translateClineTarget(
42
+ artifact: Record<string, unknown>,
43
+ context: TargetTranslatorContext,
44
+ ): TargetTranslationOutput {
45
+ const diagnostics: TranslationDiagnostic[] = [];
46
+ const degradations: DegradationRecord[] = [];
47
+
48
+ const art = artifact as unknown as KnowledgeArtifact;
49
+ const { format, variant, canonicalSchemaVersion, templates } = context;
50
+
51
+ // Resolve body override for cline harness
52
+ const body = art.bodyOverrides?.cline ?? art.body;
53
+
54
+ // Evaluate compatibility
55
+ const variantContract = format.variants[variant];
56
+ const effectiveProfile = resolveEffectiveProfile(format, variantContract);
57
+ const usedCapabilities = identifyUsedCapabilities(art);
58
+ const evaluation = evaluateCompatibility(
59
+ effectiveProfile,
60
+ usedCapabilities,
61
+ art,
62
+ );
63
+ diagnostics.push(...evaluation.diagnostics);
64
+ degradations.push(...evaluation.degradations);
65
+
66
+ // Render output files
67
+ const outputFiles: OutputFile[] = [];
68
+
69
+ const content = templates.render("cline/rule.md.njk", {
70
+ artifact: art,
71
+ body,
72
+ });
73
+ outputFiles.push({
74
+ relativePath: `.cline/rules/${art.name}.md`,
75
+ content,
76
+ executable: false,
77
+ });
78
+
79
+ // Sort output files deterministically by path
80
+ outputFiles.sort((a, b) => codePointCompare(a.relativePath, b.relativePath));
81
+
82
+ const plan = createPlan(format.id, canonicalSchemaVersion, outputFiles, {
83
+ variant,
84
+ });
85
+
86
+ return { plan, diagnostics, degradations };
87
+ }