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.
- package/README.md +78 -27
- package/dist/cli.js +28 -13
- package/dist/{help-xEI-s9iN.js → help-5-WG9QFm.js} +1 -1
- package/dist/{init-DtOjj0wc.js → init-CAq0Uhq6.js} +187 -25
- package/dist/{installer-rcZpGLnM.js → installer-UgNasLjl.js} +20 -16
- package/dist/onboarding-3zYUeYQb.js +2 -0
- package/dist/onboarding-Dd63N-V1.js +10 -0
- package/dist/repo-DkR12VUv.js +369 -0
- package/dist/upgrade-DYzuvv1k.js +140 -0
- package/dist/{verify-CxN6JiV9.js → verify-C0IUSkMZ.js} +66 -6
- package/package.json +12 -10
- package/skills/first-tree/SKILL.md +18 -10
- package/skills/first-tree/assets/framework/VERSION +1 -1
- package/skills/first-tree/assets/framework/examples/claude-code/README.md +2 -2
- package/skills/first-tree/assets/framework/examples/claude-code/settings.json +1 -1
- package/skills/first-tree/assets/framework/helpers/generate-codeowners.ts +1 -1
- package/skills/first-tree/assets/framework/helpers/inject-tree-context.sh +0 -0
- package/skills/first-tree/assets/framework/helpers/run-review.ts +17 -3
- package/skills/first-tree/assets/framework/templates/{agent.md.template → agents.md.template} +3 -2
- package/skills/first-tree/assets/framework/templates/members-domain.md.template +1 -1
- package/skills/first-tree/assets/framework/templates/root-node.md.template +9 -6
- package/skills/first-tree/assets/framework/workflows/codeowners.yml +1 -1
- package/skills/first-tree/assets/framework/workflows/pr-review.yml +1 -1
- package/skills/first-tree/engine/commands/init.ts +1 -1
- package/skills/first-tree/engine/commands/upgrade.ts +1 -1
- package/skills/first-tree/engine/commands/verify.ts +1 -1
- package/skills/first-tree/engine/init.ts +288 -18
- package/skills/first-tree/engine/repo.ts +220 -11
- package/skills/first-tree/engine/rules/agent-instructions.ts +29 -7
- package/skills/first-tree/engine/rules/agent-integration.ts +3 -1
- package/skills/first-tree/engine/rules/framework.ts +2 -2
- package/skills/first-tree/engine/runtime/adapters.ts +6 -2
- package/skills/first-tree/engine/runtime/asset-loader.ts +143 -4
- package/skills/first-tree/engine/runtime/installer.ts +18 -12
- package/skills/first-tree/engine/upgrade.ts +99 -15
- package/skills/first-tree/engine/validators/nodes.ts +48 -3
- package/skills/first-tree/engine/verify.ts +61 -3
- package/skills/first-tree/references/maintainer-architecture.md +1 -1
- package/skills/first-tree/references/maintainer-build-and-distribution.md +3 -0
- package/skills/first-tree/references/maintainer-thin-cli.md +1 -1
- package/skills/first-tree/references/onboarding.md +57 -24
- package/skills/first-tree/references/source-map.md +3 -3
- package/skills/first-tree/references/upgrade-contract.md +62 -27
- package/skills/first-tree/scripts/check-skill-sync.sh +1 -1
- package/skills/first-tree/scripts/quick_validate.py +0 -0
- package/skills/first-tree/scripts/run-local-cli.sh +0 -0
- package/skills/first-tree/tests/asset-loader.test.ts +23 -1
- package/skills/first-tree/tests/helpers.ts +51 -7
- package/skills/first-tree/tests/init.test.ts +113 -8
- package/skills/first-tree/tests/repo.test.ts +113 -9
- package/skills/first-tree/tests/rules.test.ts +35 -14
- package/skills/first-tree/tests/skill-artifacts.test.ts +10 -0
- package/skills/first-tree/tests/thin-cli.test.ts +52 -7
- package/skills/first-tree/tests/upgrade.test.ts +39 -6
- package/skills/first-tree/tests/verify.test.ts +109 -10
- package/dist/onboarding-6Fr5Gkrk.js +0 -2
- package/dist/onboarding-B9zPGvvG.js +0 -10
- package/dist/repo-BTJG8BU1.js +0 -187
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
140
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
-
`
|
|
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 (
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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 \`${
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
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 =
|
|
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 [
|
|
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 [
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
62
|
-
|
|
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(
|