first-tree 0.0.3 → 0.0.5

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 (59) hide show
  1. package/README.md +78 -27
  2. package/dist/cli.js +28 -13
  3. package/dist/{help-xEI-s9iN.js → help-5-WG9QFm.js} +1 -1
  4. package/dist/{init-DtOjj0wc.js → init-CAq0Uhq6.js} +187 -25
  5. package/dist/{installer-rcZpGLnM.js → installer-UgNasLjl.js} +20 -16
  6. package/dist/onboarding-3zYUeYQb.js +2 -0
  7. package/dist/onboarding-Dd63N-V1.js +10 -0
  8. package/dist/repo-DkR12VUv.js +369 -0
  9. package/dist/upgrade-DYzuvv1k.js +140 -0
  10. package/dist/{verify-CxN6JiV9.js → verify-C0IUSkMZ.js} +66 -6
  11. package/package.json +12 -10
  12. package/skills/first-tree/SKILL.md +18 -10
  13. package/skills/first-tree/assets/framework/VERSION +1 -1
  14. package/skills/first-tree/assets/framework/examples/claude-code/README.md +2 -2
  15. package/skills/first-tree/assets/framework/examples/claude-code/settings.json +1 -1
  16. package/skills/first-tree/assets/framework/helpers/generate-codeowners.ts +1 -1
  17. package/skills/first-tree/assets/framework/helpers/inject-tree-context.sh +0 -0
  18. package/skills/first-tree/assets/framework/helpers/run-review.ts +17 -3
  19. package/skills/first-tree/assets/framework/templates/{agent.md.template → agents.md.template} +3 -2
  20. package/skills/first-tree/assets/framework/templates/members-domain.md.template +1 -1
  21. package/skills/first-tree/assets/framework/templates/root-node.md.template +9 -6
  22. package/skills/first-tree/assets/framework/workflows/codeowners.yml +1 -1
  23. package/skills/first-tree/assets/framework/workflows/pr-review.yml +1 -1
  24. package/skills/first-tree/engine/commands/init.ts +1 -1
  25. package/skills/first-tree/engine/commands/upgrade.ts +1 -1
  26. package/skills/first-tree/engine/commands/verify.ts +1 -1
  27. package/skills/first-tree/engine/init.ts +288 -18
  28. package/skills/first-tree/engine/repo.ts +220 -11
  29. package/skills/first-tree/engine/rules/agent-instructions.ts +29 -7
  30. package/skills/first-tree/engine/rules/agent-integration.ts +3 -1
  31. package/skills/first-tree/engine/rules/framework.ts +2 -2
  32. package/skills/first-tree/engine/runtime/adapters.ts +6 -2
  33. package/skills/first-tree/engine/runtime/asset-loader.ts +143 -4
  34. package/skills/first-tree/engine/runtime/installer.ts +18 -12
  35. package/skills/first-tree/engine/upgrade.ts +99 -15
  36. package/skills/first-tree/engine/validators/nodes.ts +48 -3
  37. package/skills/first-tree/engine/verify.ts +61 -3
  38. package/skills/first-tree/references/maintainer-architecture.md +1 -1
  39. package/skills/first-tree/references/maintainer-build-and-distribution.md +3 -0
  40. package/skills/first-tree/references/maintainer-thin-cli.md +1 -1
  41. package/skills/first-tree/references/onboarding.md +57 -24
  42. package/skills/first-tree/references/source-map.md +3 -3
  43. package/skills/first-tree/references/upgrade-contract.md +62 -27
  44. package/skills/first-tree/scripts/check-skill-sync.sh +1 -1
  45. package/skills/first-tree/scripts/quick_validate.py +0 -0
  46. package/skills/first-tree/scripts/run-local-cli.sh +0 -0
  47. package/skills/first-tree/tests/asset-loader.test.ts +23 -1
  48. package/skills/first-tree/tests/helpers.ts +51 -7
  49. package/skills/first-tree/tests/init.test.ts +113 -8
  50. package/skills/first-tree/tests/repo.test.ts +113 -9
  51. package/skills/first-tree/tests/rules.test.ts +35 -14
  52. package/skills/first-tree/tests/skill-artifacts.test.ts +10 -0
  53. package/skills/first-tree/tests/thin-cli.test.ts +52 -7
  54. package/skills/first-tree/tests/upgrade.test.ts +39 -6
  55. package/skills/first-tree/tests/verify.test.ts +109 -10
  56. package/dist/onboarding-6Fr5Gkrk.js +0 -2
  57. package/dist/onboarding-B9zPGvvG.js +0 -10
  58. package/dist/repo-BTJG8BU1.js +0 -187
  59. package/dist/upgrade-COGgI7Rj.js +0 -96
@@ -1,12 +1,20 @@
1
1
  import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
2
- import { join, resolve } from "node:path";
2
+ import { basename, dirname, join, resolve } from "node:path";
3
3
  import {
4
+ AGENT_INSTRUCTIONS_FILE,
5
+ CLAUDE_FRAMEWORK_VERSION,
6
+ CLAUDE_INSTALLED_PROGRESS,
4
7
  FRAMEWORK_VERSION,
8
+ INSTALLED_PROGRESS,
9
+ LEGACY_AGENT_INSTRUCTIONS_FILE,
10
+ LEGACY_PROGRESS,
11
+ LEGACY_REPO_SKILL_PROGRESS,
12
+ LEGACY_REPO_SKILL_VERSION,
5
13
  LEGACY_SKILL_PROGRESS,
6
14
  LEGACY_SKILL_VERSION,
7
- LEGACY_PROGRESS,
8
15
  LEGACY_VERSION,
9
- INSTALLED_PROGRESS,
16
+ agentInstructionsFileCandidates,
17
+ installedSkillRoots,
10
18
  type FrameworkLayout,
11
19
  detectFrameworkLayout,
12
20
  frameworkVersionCandidates,
@@ -17,6 +25,59 @@ import {
17
25
  const FRONTMATTER_RE = /^---\s*\n(.*?)\n---/s;
18
26
  const OWNERS_RE = /^owners:\s*\[([^\]]*)\]/m;
19
27
  const TITLE_RE = /^title:\s*['"]?(.+?)['"]?\s*$/m;
28
+ const EMPTY_REPO_ENTRY_ALLOWLIST = new Set([
29
+ ".DS_Store",
30
+ ".editorconfig",
31
+ ".gitattributes",
32
+ ".github",
33
+ ".gitignore",
34
+ "AGENT.md",
35
+ "AGENTS.md",
36
+ "CLAUDE.md",
37
+ "LICENSE",
38
+ "LICENSE.md",
39
+ "LICENSE.txt",
40
+ "README",
41
+ "README.md",
42
+ "README.txt",
43
+ ]);
44
+ const SOURCE_FILE_HINTS = new Set([
45
+ ".gitmodules",
46
+ "Cargo.toml",
47
+ "Dockerfile",
48
+ "Gemfile",
49
+ "Makefile",
50
+ "bun.lock",
51
+ "bun.lockb",
52
+ "docker-compose.yml",
53
+ "go.mod",
54
+ "package-lock.json",
55
+ "package.json",
56
+ "pnpm-lock.yaml",
57
+ "pyproject.toml",
58
+ "requirements.txt",
59
+ "tsconfig.json",
60
+ "uv.lock",
61
+ "vite.config.ts",
62
+ "vite.config.js",
63
+ ]);
64
+ const SOURCE_DIR_HINTS = new Set([
65
+ "app",
66
+ "apps",
67
+ "backend",
68
+ "cli",
69
+ "client",
70
+ "docs",
71
+ "e2e",
72
+ "frontend",
73
+ "lib",
74
+ "packages",
75
+ "scripts",
76
+ "server",
77
+ "src",
78
+ "test",
79
+ "tests",
80
+ ]);
20
81
 
21
82
  export const FRAMEWORK_BEGIN_MARKER = "<!-- BEGIN CONTEXT-TREE FRAMEWORK";
22
83
  export const FRAMEWORK_END_MARKER = "<!-- END CONTEXT-TREE FRAMEWORK -->";
@@ -26,11 +87,35 @@ export interface Frontmatter {
26
87
  owners?: string[];
27
88
  }
28
89
 
90
+ function hasGitMetadata(root: string): boolean {
91
+ try {
92
+ const stat = statSync(join(root, ".git"));
93
+ return stat.isDirectory() || stat.isFile();
94
+ } catch {
95
+ return false;
96
+ }
97
+ }
98
+
99
+ function discoverGitRoot(start: string): string | null {
100
+ let dir = start;
101
+ while (true) {
102
+ if (hasGitMetadata(dir)) {
103
+ return dir;
104
+ }
105
+ const parent = dirname(dir);
106
+ if (parent === dir) {
107
+ return null;
108
+ }
109
+ dir = parent;
110
+ }
111
+ }
112
+
29
113
  export class Repo {
30
114
  readonly root: string;
31
115
 
32
116
  constructor(root?: string) {
33
- this.root = resolve(root ?? process.cwd());
117
+ const start = resolve(root ?? process.cwd());
118
+ this.root = root === undefined ? discoverGitRoot(start) ?? start : start;
34
119
  }
35
120
 
36
121
  pathExists(relPath: string): boolean {
@@ -84,12 +169,24 @@ export class Repo {
84
169
  return knownConfigs.some((c) => this.pathExists(c));
85
170
  }
86
171
 
172
+ installedSkillRoots(): string[] {
173
+ return installedSkillRoots();
174
+ }
175
+
176
+ missingInstalledSkillRoots(): string[] {
177
+ return this.installedSkillRoots().filter(
178
+ (root) =>
179
+ !this.pathExists(join(root, "SKILL.md")) ||
180
+ !this.pathExists(join(root, "assets", "framework", "VERSION")),
181
+ );
182
+ }
183
+
184
+ hasCurrentInstalledSkill(): boolean {
185
+ return this.missingInstalledSkillRoots().length === 0;
186
+ }
187
+
87
188
  isGitRepo(): boolean {
88
- try {
89
- return statSync(join(this.root, ".git")).isDirectory();
90
- } catch {
91
- return false;
92
- }
189
+ return hasGitMetadata(this.root);
93
190
  }
94
191
 
95
192
  hasFramework(): boolean {
@@ -122,6 +219,12 @@ export class Repo {
122
219
  if (layout === "legacy-skill") {
123
220
  return LEGACY_SKILL_PROGRESS;
124
221
  }
222
+ if (layout === "legacy-repo-skill") {
223
+ return LEGACY_REPO_SKILL_PROGRESS;
224
+ }
225
+ if (layout === "claude-skill") {
226
+ return CLAUDE_INSTALLED_PROGRESS;
227
+ }
125
228
  return INSTALLED_PROGRESS;
126
229
  }
127
230
 
@@ -133,11 +236,39 @@ export class Repo {
133
236
  if (layout === "legacy-skill") {
134
237
  return LEGACY_SKILL_VERSION;
135
238
  }
239
+ if (layout === "legacy-repo-skill") {
240
+ return LEGACY_REPO_SKILL_VERSION;
241
+ }
242
+ if (layout === "claude-skill") {
243
+ return CLAUDE_FRAMEWORK_VERSION;
244
+ }
136
245
  return FRAMEWORK_VERSION;
137
246
  }
138
247
 
139
- hasAgentMdMarkers(): boolean {
140
- const text = this.readFile("AGENT.md");
248
+ agentInstructionsPath(): string | null {
249
+ return resolveFirstExistingPath(this.root, agentInstructionsFileCandidates());
250
+ }
251
+
252
+ hasCanonicalAgentInstructionsFile(): boolean {
253
+ return this.pathExists(AGENT_INSTRUCTIONS_FILE);
254
+ }
255
+
256
+ hasLegacyAgentInstructionsFile(): boolean {
257
+ return this.pathExists(LEGACY_AGENT_INSTRUCTIONS_FILE);
258
+ }
259
+
260
+ hasDuplicateAgentInstructionsFiles(): boolean {
261
+ return this.hasCanonicalAgentInstructionsFile() && this.hasLegacyAgentInstructionsFile();
262
+ }
263
+
264
+ readAgentInstructions(): string | null {
265
+ const relPath = this.agentInstructionsPath();
266
+ if (relPath === null) return null;
267
+ return this.readFile(relPath);
268
+ }
269
+
270
+ hasAgentInstructionsMarkers(): boolean {
271
+ const text = this.readAgentInstructions();
141
272
  if (text === null) return false;
142
273
  return text.includes(FRAMEWORK_BEGIN_MARKER) && text.includes(FRAMEWORK_END_MARKER);
143
274
  }
@@ -181,4 +312,82 @@ export class Repo {
181
312
  hasPlaceholderNode(): boolean {
182
313
  return this.fileContains("NODE.md", "<!-- PLACEHOLDER");
183
314
  }
315
+
316
+ repoName(): string {
317
+ return basename(this.root);
318
+ }
319
+
320
+ topLevelEntries(): string[] {
321
+ try {
322
+ return readdirSync(this.root).filter((entry) => entry !== ".git");
323
+ } catch {
324
+ return [];
325
+ }
326
+ }
327
+
328
+ looksLikeTreeRepo(): boolean {
329
+ if (
330
+ this.pathExists("package.json")
331
+ && this.pathExists("src/cli.ts")
332
+ && this.pathExists("skills/first-tree/SKILL.md")
333
+ && this.progressPath() === null
334
+ && this.frontmatter("NODE.md") === null
335
+ && !this.hasAgentInstructionsMarkers()
336
+ && !this.pathExists("members/NODE.md")
337
+ ) {
338
+ return false;
339
+ }
340
+
341
+ return (
342
+ this.progressPath() !== null
343
+ || this.hasFramework()
344
+ || this.hasAgentInstructionsMarkers()
345
+ || this.pathExists("members/NODE.md")
346
+ || this.frontmatter("NODE.md") !== null
347
+ );
348
+ }
349
+
350
+ isLikelyEmptyRepo(): boolean {
351
+ const relevant = this.topLevelEntries().filter(
352
+ (entry) => !EMPTY_REPO_ENTRY_ALLOWLIST.has(entry),
353
+ );
354
+ return relevant.length === 0;
355
+ }
356
+
357
+ isLikelySourceRepo(): boolean {
358
+ if (this.looksLikeTreeRepo()) {
359
+ return false;
360
+ }
361
+
362
+ const entries = this.topLevelEntries().filter(
363
+ (entry) => !EMPTY_REPO_ENTRY_ALLOWLIST.has(entry),
364
+ );
365
+ if (entries.length === 0) {
366
+ return false;
367
+ }
368
+
369
+ let directoryCount = 0;
370
+
371
+ for (const entry of entries) {
372
+ if (SOURCE_FILE_HINTS.has(entry)) {
373
+ return true;
374
+ }
375
+ if (isDirectory(this.root, entry)) {
376
+ directoryCount += 1;
377
+ if (SOURCE_DIR_HINTS.has(entry)) {
378
+ return true;
379
+ }
380
+ }
381
+ }
382
+
383
+ return directoryCount >= 2 || entries.length >= 4;
384
+ }
385
+ }
386
+
387
+ function isDirectory(root: string, relPath: string): boolean {
388
+ try {
389
+ return statSync(join(root, relPath)).isDirectory();
390
+ } catch {
391
+ return false;
392
+ }
184
393
  }
@@ -1,20 +1,42 @@
1
1
  import { FRAMEWORK_END_MARKER } from "#skill/engine/repo.js";
2
2
  import type { Repo } from "#skill/engine/repo.js";
3
3
  import type { RuleResult } from "#skill/engine/rules/index.js";
4
- import { FRAMEWORK_TEMPLATES_DIR } from "#skill/engine/runtime/asset-loader.js";
4
+ import {
5
+ AGENT_INSTRUCTIONS_FILE,
6
+ AGENT_INSTRUCTIONS_TEMPLATE,
7
+ FRAMEWORK_TEMPLATES_DIR,
8
+ LEGACY_AGENT_INSTRUCTIONS_FILE,
9
+ } from "#skill/engine/runtime/asset-loader.js";
5
10
 
6
11
  export function evaluate(repo: Repo): RuleResult {
7
12
  const tasks: string[] = [];
8
- if (!repo.pathExists("AGENT.md")) {
13
+ const hasCanonicalInstructions = repo.hasCanonicalAgentInstructionsFile();
14
+ const hasLegacyInstructions = repo.hasLegacyAgentInstructionsFile();
15
+
16
+ if (!hasCanonicalInstructions && !hasLegacyInstructions) {
17
+ tasks.push(
18
+ `${AGENT_INSTRUCTIONS_FILE} is missing — create from \`${FRAMEWORK_TEMPLATES_DIR}/${AGENT_INSTRUCTIONS_TEMPLATE}\``,
19
+ );
20
+ return { group: "Agent Instructions", order: 3, tasks };
21
+ }
22
+
23
+ if (hasCanonicalInstructions && hasLegacyInstructions) {
9
24
  tasks.push(
10
- `AGENT.md is missing create from \`${FRAMEWORK_TEMPLATES_DIR}/agent.md.template\``,
25
+ `Merge any remaining user-authored content from \`${LEGACY_AGENT_INSTRUCTIONS_FILE}\` into \`${AGENT_INSTRUCTIONS_FILE}\`, then delete the legacy file`,
11
26
  );
12
- } else if (!repo.hasAgentMdMarkers()) {
27
+ } else if (hasLegacyInstructions) {
28
+ tasks.push(
29
+ `Rename \`${LEGACY_AGENT_INSTRUCTIONS_FILE}\` to \`${AGENT_INSTRUCTIONS_FILE}\` to use the canonical agent instructions filename`,
30
+ );
31
+ }
32
+
33
+ const instructionsPath = repo.agentInstructionsPath() ?? AGENT_INSTRUCTIONS_FILE;
34
+ if (!repo.hasAgentInstructionsMarkers()) {
13
35
  tasks.push(
14
- "AGENT.md exists but is missing framework markers — add `<!-- BEGIN CONTEXT-TREE FRAMEWORK -->` and `<!-- END CONTEXT-TREE FRAMEWORK -->` sections",
36
+ `\`${instructionsPath}\` exists but is missing framework markers — add \`<!-- BEGIN CONTEXT-TREE FRAMEWORK -->\` and \`<!-- END CONTEXT-TREE FRAMEWORK -->\` sections`,
15
37
  );
16
38
  } else {
17
- const text = repo.readFile("AGENT.md") ?? "";
39
+ const text = repo.readAgentInstructions() ?? "";
18
40
  const afterMarker = text.split(FRAMEWORK_END_MARKER);
19
41
  if (afterMarker.length > 1) {
20
42
  const userSection = afterMarker[1].trim();
@@ -28,7 +50,7 @@ export function evaluate(repo: Repo): RuleResult {
28
50
  );
29
51
  if (lines.length === 0) {
30
52
  tasks.push(
31
- "Add your project-specific instructions below the framework markers in AGENT.md",
53
+ `Add your project-specific instructions below the framework markers in ${AGENT_INSTRUCTIONS_FILE}`,
32
54
  );
33
55
  }
34
56
  }
@@ -1,13 +1,15 @@
1
1
  import type { Repo } from "#skill/engine/repo.js";
2
2
  import type { RuleResult } from "#skill/engine/rules/index.js";
3
+ import { claudeCodeExampleCandidates } from "#skill/engine/runtime/adapters.js";
3
4
  import { FRAMEWORK_EXAMPLES_DIR } from "#skill/engine/runtime/asset-loader.js";
4
5
 
5
6
  export function evaluate(repo: Repo): RuleResult {
6
7
  const tasks: string[] = [];
8
+ const [claudeExamplePath] = claudeCodeExampleCandidates();
7
9
  if (repo.pathExists(".claude/settings.json")) {
8
10
  if (!repo.fileContains(".claude/settings.json", "inject-tree-context")) {
9
11
  tasks.push(
10
- `Add SessionStart hook to \`.claude/settings.json\` (see \`${FRAMEWORK_EXAMPLES_DIR}/claude-code/\`)`,
12
+ `Add SessionStart hook to \`.claude/settings.json\` (see \`${claudeExamplePath}/\`)`,
11
13
  );
12
14
  }
13
15
  } else if (!repo.anyAgentConfig()) {
@@ -1,12 +1,12 @@
1
1
  import type { Repo } from "#skill/engine/repo.js";
2
2
  import type { RuleResult } from "#skill/engine/rules/index.js";
3
- import { SKILL_ROOT } from "#skill/engine/runtime/asset-loader.js";
3
+ import { installedSkillRootsDisplay } from "#skill/engine/runtime/asset-loader.js";
4
4
 
5
5
  export function evaluate(repo: Repo): RuleResult {
6
6
  const tasks: string[] = [];
7
7
  if (!repo.hasFramework()) {
8
8
  tasks.push(
9
- `\`${SKILL_ROOT}/\` not found — run \`context-tree init\` to install the framework skill bundled with the current \`first-tree\` package`,
9
+ `${installedSkillRootsDisplay()} not found — run \`context-tree init\` to install the framework skill bundled with the current \`first-tree\` package`,
10
10
  );
11
11
  }
12
12
  return { group: "Framework", order: 1, tasks };
@@ -1,7 +1,9 @@
1
1
  import { join } from "node:path";
2
2
  import {
3
+ CLAUDE_FRAMEWORK_EXAMPLES_DIR,
4
+ CLAUDE_FRAMEWORK_HELPERS_DIR,
3
5
  FRAMEWORK_EXAMPLES_DIR,
4
- FRAMEWORK_HELPERS_DIR,
6
+ LEGACY_REPO_SKILL_EXAMPLES_DIR,
5
7
  LEGACY_SKILL_EXAMPLES_DIR,
6
8
  LEGACY_EXAMPLES_DIR,
7
9
  } from "#skill/engine/runtime/asset-loader.js";
@@ -11,12 +13,14 @@ export const CODEX_CONFIG_PATH = ".codex/config.json";
11
13
 
12
14
  export function claudeCodeExampleCandidates(): string[] {
13
15
  return [
16
+ join(CLAUDE_FRAMEWORK_EXAMPLES_DIR, "claude-code"),
14
17
  join(FRAMEWORK_EXAMPLES_DIR, "claude-code"),
18
+ join(LEGACY_REPO_SKILL_EXAMPLES_DIR, "claude-code"),
15
19
  join(LEGACY_SKILL_EXAMPLES_DIR, "claude-code"),
16
20
  join(LEGACY_EXAMPLES_DIR, "claude-code"),
17
21
  ];
18
22
  }
19
23
 
20
24
  export function injectTreeContextHint(): string {
21
- return join(FRAMEWORK_HELPERS_DIR, "inject-tree-context.sh");
25
+ return join(CLAUDE_FRAMEWORK_HELPERS_DIR, "inject-tree-context.sh");
22
26
  }
@@ -2,7 +2,11 @@ import { existsSync, statSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
 
4
4
  export const SKILL_NAME = "first-tree";
5
- export const SKILL_ROOT = join("skills", SKILL_NAME);
5
+ export const BUNDLED_SKILL_ROOT = join("skills", SKILL_NAME);
6
+ export const SKILL_ROOT = join(".agents", "skills", SKILL_NAME);
7
+ export const CLAUDE_SKILL_ROOT = join(".claude", "skills", SKILL_NAME);
8
+ export const INSTALLED_SKILL_ROOTS = [SKILL_ROOT, CLAUDE_SKILL_ROOT] as const;
9
+
6
10
  export const SKILL_AGENTS_DIR = join(SKILL_ROOT, "agents");
7
11
  export const SKILL_REFERENCES_DIR = join(SKILL_ROOT, "references");
8
12
  export const FRAMEWORK_ASSET_ROOT = join(SKILL_ROOT, "assets", "framework");
@@ -14,6 +18,96 @@ export const FRAMEWORK_PROMPTS_DIR = join(FRAMEWORK_ASSET_ROOT, "prompts");
14
18
  export const FRAMEWORK_EXAMPLES_DIR = join(FRAMEWORK_ASSET_ROOT, "examples");
15
19
  export const FRAMEWORK_HELPERS_DIR = join(FRAMEWORK_ASSET_ROOT, "helpers");
16
20
  export const INSTALLED_PROGRESS = join(SKILL_ROOT, "progress.md");
21
+ export const AGENT_INSTRUCTIONS_FILE = "AGENTS.md";
22
+ export const LEGACY_AGENT_INSTRUCTIONS_FILE = "AGENT.md";
23
+ export const AGENT_INSTRUCTIONS_TEMPLATE = "agents.md.template";
24
+
25
+ export const CLAUDE_SKILL_AGENTS_DIR = join(CLAUDE_SKILL_ROOT, "agents");
26
+ export const CLAUDE_SKILL_REFERENCES_DIR = join(CLAUDE_SKILL_ROOT, "references");
27
+ export const CLAUDE_FRAMEWORK_ASSET_ROOT = join(
28
+ CLAUDE_SKILL_ROOT,
29
+ "assets",
30
+ "framework",
31
+ );
32
+ export const CLAUDE_FRAMEWORK_MANIFEST = join(
33
+ CLAUDE_FRAMEWORK_ASSET_ROOT,
34
+ "manifest.json",
35
+ );
36
+ export const CLAUDE_FRAMEWORK_VERSION = join(
37
+ CLAUDE_FRAMEWORK_ASSET_ROOT,
38
+ "VERSION",
39
+ );
40
+ export const CLAUDE_FRAMEWORK_TEMPLATES_DIR = join(
41
+ CLAUDE_FRAMEWORK_ASSET_ROOT,
42
+ "templates",
43
+ );
44
+ export const CLAUDE_FRAMEWORK_WORKFLOWS_DIR = join(
45
+ CLAUDE_FRAMEWORK_ASSET_ROOT,
46
+ "workflows",
47
+ );
48
+ export const CLAUDE_FRAMEWORK_PROMPTS_DIR = join(
49
+ CLAUDE_FRAMEWORK_ASSET_ROOT,
50
+ "prompts",
51
+ );
52
+ export const CLAUDE_FRAMEWORK_EXAMPLES_DIR = join(
53
+ CLAUDE_FRAMEWORK_ASSET_ROOT,
54
+ "examples",
55
+ );
56
+ export const CLAUDE_FRAMEWORK_HELPERS_DIR = join(
57
+ CLAUDE_FRAMEWORK_ASSET_ROOT,
58
+ "helpers",
59
+ );
60
+ export const CLAUDE_INSTALLED_PROGRESS = join(
61
+ CLAUDE_SKILL_ROOT,
62
+ "progress.md",
63
+ );
64
+
65
+ export const LEGACY_REPO_SKILL_ROOT = join("skills", SKILL_NAME);
66
+ export const LEGACY_REPO_SKILL_AGENTS_DIR = join(
67
+ LEGACY_REPO_SKILL_ROOT,
68
+ "agents",
69
+ );
70
+ export const LEGACY_REPO_SKILL_REFERENCES_DIR = join(
71
+ LEGACY_REPO_SKILL_ROOT,
72
+ "references",
73
+ );
74
+ export const LEGACY_REPO_SKILL_ASSET_ROOT = join(
75
+ LEGACY_REPO_SKILL_ROOT,
76
+ "assets",
77
+ "framework",
78
+ );
79
+ export const LEGACY_REPO_SKILL_MANIFEST = join(
80
+ LEGACY_REPO_SKILL_ASSET_ROOT,
81
+ "manifest.json",
82
+ );
83
+ export const LEGACY_REPO_SKILL_VERSION = join(
84
+ LEGACY_REPO_SKILL_ASSET_ROOT,
85
+ "VERSION",
86
+ );
87
+ export const LEGACY_REPO_SKILL_TEMPLATES_DIR = join(
88
+ LEGACY_REPO_SKILL_ASSET_ROOT,
89
+ "templates",
90
+ );
91
+ export const LEGACY_REPO_SKILL_WORKFLOWS_DIR = join(
92
+ LEGACY_REPO_SKILL_ASSET_ROOT,
93
+ "workflows",
94
+ );
95
+ export const LEGACY_REPO_SKILL_PROMPTS_DIR = join(
96
+ LEGACY_REPO_SKILL_ASSET_ROOT,
97
+ "prompts",
98
+ );
99
+ export const LEGACY_REPO_SKILL_EXAMPLES_DIR = join(
100
+ LEGACY_REPO_SKILL_ASSET_ROOT,
101
+ "examples",
102
+ );
103
+ export const LEGACY_REPO_SKILL_HELPERS_DIR = join(
104
+ LEGACY_REPO_SKILL_ASSET_ROOT,
105
+ "helpers",
106
+ );
107
+ export const LEGACY_REPO_SKILL_PROGRESS = join(
108
+ LEGACY_REPO_SKILL_ROOT,
109
+ "progress.md",
110
+ );
17
111
 
18
112
  export const LEGACY_SKILL_NAME = "first-tree-cli-framework";
19
113
  export const LEGACY_SKILL_ROOT = join("skills", LEGACY_SKILL_NAME);
@@ -49,7 +143,12 @@ export const LEGACY_WORKFLOWS_DIR = join(LEGACY_FRAMEWORK_ROOT, "workflows");
49
143
  export const LEGACY_PROMPTS_DIR = join(LEGACY_FRAMEWORK_ROOT, "prompts");
50
144
  export const LEGACY_EXAMPLES_DIR = join(LEGACY_FRAMEWORK_ROOT, "examples");
51
145
 
52
- export type FrameworkLayout = "skill" | "legacy-skill" | "legacy";
146
+ export type FrameworkLayout =
147
+ | "skill"
148
+ | "claude-skill"
149
+ | "legacy-repo-skill"
150
+ | "legacy-skill"
151
+ | "legacy";
53
152
 
54
153
  function pathExists(root: string, relPath: string): boolean {
55
154
  const fullPath = join(root, relPath);
@@ -60,17 +159,45 @@ function pathExists(root: string, relPath: string): boolean {
60
159
  }
61
160
  }
62
161
 
162
+ export function installedSkillRoots(): string[] {
163
+ return [...INSTALLED_SKILL_ROOTS];
164
+ }
165
+
166
+ export function installedSkillRootsDisplay(): string {
167
+ return installedSkillRoots()
168
+ .map((root) => `\`${root}/\``)
169
+ .join(" and ");
170
+ }
171
+
63
172
  export function frameworkVersionCandidates(): string[] {
64
- return [FRAMEWORK_VERSION, LEGACY_SKILL_VERSION, LEGACY_VERSION];
173
+ return [
174
+ FRAMEWORK_VERSION,
175
+ CLAUDE_FRAMEWORK_VERSION,
176
+ LEGACY_REPO_SKILL_VERSION,
177
+ LEGACY_SKILL_VERSION,
178
+ LEGACY_VERSION,
179
+ ];
65
180
  }
66
181
 
67
182
  export function progressFileCandidates(): string[] {
68
- return [INSTALLED_PROGRESS, LEGACY_SKILL_PROGRESS, LEGACY_PROGRESS];
183
+ return [
184
+ INSTALLED_PROGRESS,
185
+ CLAUDE_INSTALLED_PROGRESS,
186
+ LEGACY_REPO_SKILL_PROGRESS,
187
+ LEGACY_SKILL_PROGRESS,
188
+ LEGACY_PROGRESS,
189
+ ];
190
+ }
191
+
192
+ export function agentInstructionsFileCandidates(): string[] {
193
+ return [AGENT_INSTRUCTIONS_FILE, LEGACY_AGENT_INSTRUCTIONS_FILE];
69
194
  }
70
195
 
71
196
  export function frameworkTemplateDirCandidates(): string[] {
72
197
  return [
73
198
  FRAMEWORK_TEMPLATES_DIR,
199
+ CLAUDE_FRAMEWORK_TEMPLATES_DIR,
200
+ LEGACY_REPO_SKILL_TEMPLATES_DIR,
74
201
  LEGACY_SKILL_TEMPLATES_DIR,
75
202
  LEGACY_TEMPLATES_DIR,
76
203
  ];
@@ -79,6 +206,8 @@ export function frameworkTemplateDirCandidates(): string[] {
79
206
  export function frameworkWorkflowDirCandidates(): string[] {
80
207
  return [
81
208
  FRAMEWORK_WORKFLOWS_DIR,
209
+ CLAUDE_FRAMEWORK_WORKFLOWS_DIR,
210
+ LEGACY_REPO_SKILL_WORKFLOWS_DIR,
82
211
  LEGACY_SKILL_WORKFLOWS_DIR,
83
212
  LEGACY_WORKFLOWS_DIR,
84
213
  ];
@@ -87,6 +216,8 @@ export function frameworkWorkflowDirCandidates(): string[] {
87
216
  export function frameworkPromptDirCandidates(): string[] {
88
217
  return [
89
218
  FRAMEWORK_PROMPTS_DIR,
219
+ CLAUDE_FRAMEWORK_PROMPTS_DIR,
220
+ LEGACY_REPO_SKILL_PROMPTS_DIR,
90
221
  LEGACY_SKILL_PROMPTS_DIR,
91
222
  LEGACY_PROMPTS_DIR,
92
223
  ];
@@ -95,6 +226,8 @@ export function frameworkPromptDirCandidates(): string[] {
95
226
  export function frameworkExampleDirCandidates(): string[] {
96
227
  return [
97
228
  FRAMEWORK_EXAMPLES_DIR,
229
+ CLAUDE_FRAMEWORK_EXAMPLES_DIR,
230
+ LEGACY_REPO_SKILL_EXAMPLES_DIR,
98
231
  LEGACY_SKILL_EXAMPLES_DIR,
99
232
  LEGACY_EXAMPLES_DIR,
100
233
  ];
@@ -116,6 +249,12 @@ export function detectFrameworkLayout(root: string): FrameworkLayout | null {
116
249
  if (pathExists(root, FRAMEWORK_VERSION)) {
117
250
  return "skill";
118
251
  }
252
+ if (pathExists(root, CLAUDE_FRAMEWORK_VERSION)) {
253
+ return "claude-skill";
254
+ }
255
+ if (pathExists(root, LEGACY_REPO_SKILL_VERSION)) {
256
+ return "legacy-repo-skill";
257
+ }
119
258
  if (pathExists(root, LEGACY_SKILL_VERSION)) {
120
259
  return "legacy-skill";
121
260
  }
@@ -2,9 +2,10 @@ import { copyFileSync, cpSync, existsSync, mkdirSync, rmSync } from "node:fs";
2
2
  import { dirname, join } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import {
5
- FRAMEWORK_ASSET_ROOT,
5
+ BUNDLED_SKILL_ROOT,
6
+ INSTALLED_SKILL_ROOTS,
7
+ LEGACY_REPO_SKILL_ROOT,
6
8
  LEGACY_SKILL_ROOT,
7
- SKILL_ROOT,
8
9
  } from "#skill/engine/runtime/asset-loader.js";
9
10
 
10
11
  export function resolveBundledPackageRoot(startUrl = import.meta.url): string {
@@ -12,7 +13,7 @@ export function resolveBundledPackageRoot(startUrl = import.meta.url): string {
12
13
  while (true) {
13
14
  if (
14
15
  existsSync(join(dir, "package.json")) &&
15
- existsSync(join(dir, SKILL_ROOT, "SKILL.md"))
16
+ existsSync(join(dir, BUNDLED_SKILL_ROOT, "SKILL.md"))
16
17
  ) {
17
18
  return dir;
18
19
  }
@@ -38,7 +39,7 @@ export function resolveCanonicalSkillRoot(sourceRoot: string): string {
38
39
  return directSkillRoot;
39
40
  }
40
41
 
41
- const nestedSkillRoot = join(sourceRoot, SKILL_ROOT);
42
+ const nestedSkillRoot = join(sourceRoot, BUNDLED_SKILL_ROOT);
42
43
  if (
43
44
  existsSync(join(nestedSkillRoot, "SKILL.md")) &&
44
45
  existsSync(join(nestedSkillRoot, "assets", "framework", "VERSION"))
@@ -53,16 +54,21 @@ export function resolveCanonicalSkillRoot(sourceRoot: string): string {
53
54
 
54
55
  export function copyCanonicalSkill(sourceRoot: string, targetRoot: string): void {
55
56
  const src = resolveCanonicalSkillRoot(sourceRoot);
56
- const dst = join(targetRoot, SKILL_ROOT);
57
- const legacyDst = join(targetRoot, LEGACY_SKILL_ROOT);
58
- if (existsSync(dst)) {
59
- rmSync(dst, { recursive: true, force: true });
57
+ for (const relPath of [
58
+ ...INSTALLED_SKILL_ROOTS,
59
+ LEGACY_REPO_SKILL_ROOT,
60
+ LEGACY_SKILL_ROOT,
61
+ ]) {
62
+ const fullPath = join(targetRoot, relPath);
63
+ if (existsSync(fullPath)) {
64
+ rmSync(fullPath, { recursive: true, force: true });
65
+ }
60
66
  }
61
- if (legacyDst !== dst && existsSync(legacyDst)) {
62
- rmSync(legacyDst, { recursive: true, force: true });
67
+ for (const relPath of INSTALLED_SKILL_ROOTS) {
68
+ const dst = join(targetRoot, relPath);
69
+ mkdirSync(dirname(dst), { recursive: true });
70
+ cpSync(src, dst, { recursive: true });
63
71
  }
64
- mkdirSync(dirname(dst), { recursive: true });
65
- cpSync(src, dst, { recursive: true });
66
72
  }
67
73
 
68
74
  export function renderTemplateFile(