@thinkingsage/kanon 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +410 -0
- package/LICENSE +21 -0
- package/README.md +168 -0
- package/bridge/mcp-server.cjs +14171 -0
- package/package.json +98 -0
- package/src/adapters/capabilities.ts +178 -0
- package/src/adapters/claude-code.ts +110 -0
- package/src/adapters/cline.ts +98 -0
- package/src/adapters/codex.ts +173 -0
- package/src/adapters/copilot.ts +106 -0
- package/src/adapters/cursor.ts +97 -0
- package/src/adapters/degradation.ts +95 -0
- package/src/adapters/index.ts +324 -0
- package/src/adapters/kiro-frontmatter.ts +139 -0
- package/src/adapters/kiro-inclusion.ts +86 -0
- package/src/adapters/kiro.ts +412 -0
- package/src/adapters/qdeveloper.ts +115 -0
- package/src/adapters/types.ts +81 -0
- package/src/adapters/windsurf.ts +96 -0
- package/src/admin.ts +283 -0
- package/src/asset-conventions.ts +118 -0
- package/src/attribution-backfill.ts +319 -0
- package/src/attribution-report.ts +95 -0
- package/src/attribution.ts +239 -0
- package/src/backends/github.ts +194 -0
- package/src/backends/http.ts +122 -0
- package/src/backends/index.ts +39 -0
- package/src/backends/local.ts +47 -0
- package/src/backends/s3.ts +157 -0
- package/src/backends/types.ts +59 -0
- package/src/base-cache.ts +270 -0
- package/src/browse-ui.ts +3754 -0
- package/src/browse.ts +1038 -0
- package/src/build.ts +1108 -0
- package/src/catalog.ts +204 -0
- package/src/cli-deprecated.ts +29 -0
- package/src/cli.ts +773 -0
- package/src/collection-admin.ts +287 -0
- package/src/collection-builder.ts +464 -0
- package/src/collections.ts +116 -0
- package/src/compatibility.ts +105 -0
- package/src/config.ts +743 -0
- package/src/eval/rubrics/kiro-progressive-steering.ts +841 -0
- package/src/eval.ts +1169 -0
- package/src/file-writer.ts +61 -0
- package/src/format-registry.ts +141 -0
- package/src/guild/auto-updater.ts +163 -0
- package/src/guild/backend-resolver.ts +49 -0
- package/src/guild/cli.ts +592 -0
- package/src/guild/collection-expander.ts +47 -0
- package/src/guild/global-cache.ts +247 -0
- package/src/guild/hook-generator.ts +100 -0
- package/src/guild/manifest.ts +154 -0
- package/src/guild/path-utils.ts +12 -0
- package/src/guild/sync.ts +622 -0
- package/src/guild/version-resolver.ts +42 -0
- package/src/help/metadata.ts +445 -0
- package/src/help/renderer.ts +265 -0
- package/src/help/typo-suggester.ts +25 -0
- package/src/hooks/expression.ts +493 -0
- package/src/hooks/pipeline.ts +141 -0
- package/src/import.ts +773 -0
- package/src/importers/claude-code.ts +134 -0
- package/src/importers/cline.ts +103 -0
- package/src/importers/codex.ts +140 -0
- package/src/importers/copilot.ts +103 -0
- package/src/importers/cursor.ts +105 -0
- package/src/importers/index.ts +390 -0
- package/src/importers/kiro.ts +110 -0
- package/src/importers/qdeveloper.ts +103 -0
- package/src/importers/types.ts +54 -0
- package/src/importers/windsurf.ts +104 -0
- package/src/install.ts +1005 -0
- package/src/manifest-admin.ts +306 -0
- package/src/mcp-bridge.ts +240 -0
- package/src/mutation/delta.ts +50 -0
- package/src/mutation/history.ts +66 -0
- package/src/mutation/operators.ts +524 -0
- package/src/mutation/runner.ts +332 -0
- package/src/new.ts +106 -0
- package/src/outcomes/collision.ts +127 -0
- package/src/outcomes/normalize.ts +208 -0
- package/src/outcomes/registry.ts +173 -0
- package/src/parser.ts +446 -0
- package/src/provenance-backfill-cli.ts +319 -0
- package/src/provenance-backfill.ts +520 -0
- package/src/publish.ts +354 -0
- package/src/reconcile-orchestrator.ts +502 -0
- package/src/reconcile-report-renderer.ts +176 -0
- package/src/resolve-body.ts +15 -0
- package/src/rosetta/builtins/compatibility-profiles.ts +297 -0
- package/src/rosetta/builtins/contracts.ts +1033 -0
- package/src/rosetta/builtins/pretty-printers/claude-code-native.ts +122 -0
- package/src/rosetta/builtins/pretty-printers/cline-native.ts +50 -0
- package/src/rosetta/builtins/pretty-printers/codex-native.ts +127 -0
- package/src/rosetta/builtins/pretty-printers/copilot-native.ts +50 -0
- package/src/rosetta/builtins/pretty-printers/cursor-native.ts +50 -0
- package/src/rosetta/builtins/pretty-printers/index.ts +81 -0
- package/src/rosetta/builtins/pretty-printers/kiro-native.ts +166 -0
- package/src/rosetta/builtins/pretty-printers/kiro-power.ts +108 -0
- package/src/rosetta/builtins/pretty-printers/kiro-skill.ts +88 -0
- package/src/rosetta/builtins/pretty-printers/qdeveloper-native.ts +51 -0
- package/src/rosetta/builtins/pretty-printers/superpowers.ts +97 -0
- package/src/rosetta/builtins/pretty-printers/windsurf-native.ts +50 -0
- package/src/rosetta/builtins/sources/claude-code-native.ts +348 -0
- package/src/rosetta/builtins/sources/cline-native.ts +176 -0
- package/src/rosetta/builtins/sources/codex-native.ts +343 -0
- package/src/rosetta/builtins/sources/copilot-native.ts +178 -0
- package/src/rosetta/builtins/sources/cursor-native.ts +176 -0
- package/src/rosetta/builtins/sources/index.ts +95 -0
- package/src/rosetta/builtins/sources/kiro-native.ts +462 -0
- package/src/rosetta/builtins/sources/kiro-power.ts +285 -0
- package/src/rosetta/builtins/sources/kiro-skill.ts +230 -0
- package/src/rosetta/builtins/sources/qdeveloper-native.ts +181 -0
- package/src/rosetta/builtins/sources/superpowers.ts +240 -0
- package/src/rosetta/builtins/sources/windsurf-native.ts +176 -0
- package/src/rosetta/builtins/targets/claude-code.ts +181 -0
- package/src/rosetta/builtins/targets/cline.ts +87 -0
- package/src/rosetta/builtins/targets/codex.ts +226 -0
- package/src/rosetta/builtins/targets/copilot.ts +103 -0
- package/src/rosetta/builtins/targets/cursor.ts +87 -0
- package/src/rosetta/builtins/targets/index.ts +60 -0
- package/src/rosetta/builtins/targets/kiro.ts +278 -0
- package/src/rosetta/builtins/targets/qdeveloper.ts +103 -0
- package/src/rosetta/builtins/targets/windsurf.ts +87 -0
- package/src/rosetta/canonical.ts +729 -0
- package/src/rosetta/compatibility.ts +432 -0
- package/src/rosetta/contracts.ts +329 -0
- package/src/rosetta/detector.ts +724 -0
- package/src/rosetta/diagnostics.ts +630 -0
- package/src/rosetta/engine-bootstrap.ts +103 -0
- package/src/rosetta/engine.ts +744 -0
- package/src/rosetta/index.ts +381 -0
- package/src/rosetta/inspection.ts +530 -0
- package/src/rosetta/plan.ts +448 -0
- package/src/rosetta/provenance-digest.ts +369 -0
- package/src/rosetta/reconcile.ts +812 -0
- package/src/rosetta/redaction.ts +467 -0
- package/src/rosetta/registry.ts +712 -0
- package/src/rosetta/renderers.ts +571 -0
- package/src/rosetta/request-guard.ts +335 -0
- package/src/rosetta/resolution.ts +419 -0
- package/src/rosetta/source-accounting.ts +233 -0
- package/src/rosetta/templates.ts +129 -0
- package/src/rosetta-cli.ts +717 -0
- package/src/rosetta-docs-generator.ts +793 -0
- package/src/rosetta-profiles-cli.ts +367 -0
- package/src/schemas.ts +1712 -0
- package/src/spec-coordination.ts +1141 -0
- package/src/temper.ts +747 -0
- package/src/template-bundle-loader.ts +312 -0
- package/src/template-engine.ts +53 -0
- package/src/translation-application-policy.ts +496 -0
- package/src/translation-orchestrator.ts +1013 -0
- package/src/translation-plan-applier.ts +473 -0
- package/src/tutorial.ts +305 -0
- package/src/validate.ts +1093 -0
- package/src/versioning.ts +553 -0
- package/src/wizard.ts +660 -0
- package/src/workspace.ts +237 -0
- package/templates/eval-contexts/claude-code.md.njk +6 -0
- package/templates/eval-contexts/cline.md.njk +6 -0
- package/templates/eval-contexts/copilot.md.njk +6 -0
- package/templates/eval-contexts/cursor.md.njk +6 -0
- package/templates/eval-contexts/kiro.md.njk +10 -0
- package/templates/eval-contexts/qdeveloper.md.njk +6 -0
- package/templates/eval-contexts/windsurf.md.njk +6 -0
- package/templates/harness-adapters/_base/attribution-footer.md.njk +17 -0
- package/templates/harness-adapters/_base/base.md.njk +16 -0
- package/templates/harness-adapters/claude-code/claude.md.njk +1 -0
- package/templates/harness-adapters/claude-code/mcp.json.njk +1 -0
- package/templates/harness-adapters/claude-code/settings.json.njk +1 -0
- package/templates/harness-adapters/claude-code/skill-library-index.md.njk +13 -0
- package/templates/harness-adapters/claude-code/skill.md.njk +19 -0
- package/templates/harness-adapters/cline/hook.sh.njk +4 -0
- package/templates/harness-adapters/cline/mcp.json.njk +1 -0
- package/templates/harness-adapters/cline/rule.md.njk +1 -0
- package/templates/harness-adapters/codex/agents-md.md.njk +6 -0
- package/templates/harness-adapters/codex/agents-pointer.md.njk +16 -0
- package/templates/harness-adapters/codex/skill.md.njk +27 -0
- package/templates/harness-adapters/copilot/agents.md.njk +1 -0
- package/templates/harness-adapters/copilot/instructions.md.njk +1 -0
- package/templates/harness-adapters/copilot/scoped.md.njk +6 -0
- package/templates/harness-adapters/cursor/mcp.json.njk +1 -0
- package/templates/harness-adapters/cursor/rule.md.njk +6 -0
- package/templates/harness-adapters/kiro/hook.json.njk +1 -0
- package/templates/harness-adapters/kiro/mcp.json.njk +1 -0
- package/templates/harness-adapters/kiro/power-steering.md.njk +3 -0
- package/templates/harness-adapters/kiro/power.md.njk +12 -0
- package/templates/harness-adapters/kiro/steering.md.njk +16 -0
- package/templates/harness-adapters/qdeveloper/agent.md.njk +1 -0
- package/templates/harness-adapters/qdeveloper/mcp.json.njk +1 -0
- package/templates/harness-adapters/qdeveloper/rule.md.njk +1 -0
- package/templates/harness-adapters/windsurf/mcp.json.njk +1 -0
- package/templates/harness-adapters/windsurf/rule.md.njk +1 -0
- package/templates/harness-adapters/windsurf/workflow.md.njk +1 -0
- package/templates/knowledge/hooks.yaml.njk +4 -0
- package/templates/knowledge/knowledge.md.njk +53 -0
- package/templates/knowledge/mcp-servers.yaml.njk +2 -0
|
@@ -0,0 +1,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
|
+
}
|