first-tree 0.0.4 → 0.0.6
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 +36 -13
- package/dist/cli.js +5 -5
- package/dist/{help-Dtdj91HJ.js → help-DV9-AaFp.js} +1 -1
- package/dist/{init--VepFe6N.js → init-BgGH2_yC.js} +57 -11
- package/dist/onboarding-D7fGGOMN.js +10 -0
- package/dist/onboarding-lASHHmgO.js +2 -0
- package/dist/{repo-DY57bMqr.js → repo-Cc5U4DWT.js} +76 -5
- package/dist/source-integration-CuKjoheT.js +84 -0
- package/dist/{upgrade-Cgx_K2HM.js → upgrade-BvA9oKmi.js} +37 -9
- package/dist/{verify-mC9ZTd1f.js → verify-G8gNXzDX.js} +5 -1
- package/package.json +11 -9
- package/skills/first-tree/SKILL.md +30 -10
- package/skills/first-tree/agents/openai.yaml +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 +1 -1
- package/skills/first-tree/assets/framework/templates/agents.md.template +2 -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 +3 -3
- 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/init.ts +78 -6
- package/skills/first-tree/engine/repo.ts +74 -10
- 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 +142 -4
- package/skills/first-tree/engine/runtime/installer.ts +18 -12
- package/skills/first-tree/engine/runtime/source-integration.ts +80 -0
- package/skills/first-tree/engine/upgrade.ts +103 -9
- package/skills/first-tree/engine/verify.ts +7 -0
- package/skills/first-tree/references/maintainer-architecture.md +4 -0
- package/skills/first-tree/references/maintainer-thin-cli.md +3 -0
- package/skills/first-tree/references/onboarding.md +56 -21
- package/skills/first-tree/references/principles.md +97 -57
- package/skills/first-tree/references/source-map.md +1 -0
- package/skills/first-tree/references/source-workspace-installation.md +52 -0
- package/skills/first-tree/references/upgrade-contract.md +67 -26
- package/skills/first-tree/scripts/check-skill-sync.sh +2 -0
- 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 +27 -3
- package/skills/first-tree/tests/init.test.ts +72 -3
- package/skills/first-tree/tests/repo.test.ts +46 -0
- package/skills/first-tree/tests/rules.test.ts +9 -7
- package/skills/first-tree/tests/skill-artifacts.test.ts +45 -0
- package/skills/first-tree/tests/upgrade.test.ts +58 -3
- package/skills/first-tree/tests/verify.test.ts +21 -3
- package/dist/installer-cH7N4RNj.js +0 -47
- package/dist/onboarding-C9cYSE6F.js +0 -2
- package/dist/onboarding-CPP8fF4D.js +0 -10
|
@@ -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");
|
|
@@ -17,6 +21,99 @@ export const INSTALLED_PROGRESS = join(SKILL_ROOT, "progress.md");
|
|
|
17
21
|
export const AGENT_INSTRUCTIONS_FILE = "AGENTS.md";
|
|
18
22
|
export const LEGACY_AGENT_INSTRUCTIONS_FILE = "AGENT.md";
|
|
19
23
|
export const AGENT_INSTRUCTIONS_TEMPLATE = "agents.md.template";
|
|
24
|
+
export const CLAUDE_INSTRUCTIONS_FILE = "CLAUDE.md";
|
|
25
|
+
export const SOURCE_INTEGRATION_MARKER = "FIRST-TREE-SOURCE-INTEGRATION:";
|
|
26
|
+
export const SOURCE_INTEGRATION_FILES = [
|
|
27
|
+
AGENT_INSTRUCTIONS_FILE,
|
|
28
|
+
CLAUDE_INSTRUCTIONS_FILE,
|
|
29
|
+
] as const;
|
|
30
|
+
|
|
31
|
+
export const CLAUDE_SKILL_AGENTS_DIR = join(CLAUDE_SKILL_ROOT, "agents");
|
|
32
|
+
export const CLAUDE_SKILL_REFERENCES_DIR = join(CLAUDE_SKILL_ROOT, "references");
|
|
33
|
+
export const CLAUDE_FRAMEWORK_ASSET_ROOT = join(
|
|
34
|
+
CLAUDE_SKILL_ROOT,
|
|
35
|
+
"assets",
|
|
36
|
+
"framework",
|
|
37
|
+
);
|
|
38
|
+
export const CLAUDE_FRAMEWORK_MANIFEST = join(
|
|
39
|
+
CLAUDE_FRAMEWORK_ASSET_ROOT,
|
|
40
|
+
"manifest.json",
|
|
41
|
+
);
|
|
42
|
+
export const CLAUDE_FRAMEWORK_VERSION = join(
|
|
43
|
+
CLAUDE_FRAMEWORK_ASSET_ROOT,
|
|
44
|
+
"VERSION",
|
|
45
|
+
);
|
|
46
|
+
export const CLAUDE_FRAMEWORK_TEMPLATES_DIR = join(
|
|
47
|
+
CLAUDE_FRAMEWORK_ASSET_ROOT,
|
|
48
|
+
"templates",
|
|
49
|
+
);
|
|
50
|
+
export const CLAUDE_FRAMEWORK_WORKFLOWS_DIR = join(
|
|
51
|
+
CLAUDE_FRAMEWORK_ASSET_ROOT,
|
|
52
|
+
"workflows",
|
|
53
|
+
);
|
|
54
|
+
export const CLAUDE_FRAMEWORK_PROMPTS_DIR = join(
|
|
55
|
+
CLAUDE_FRAMEWORK_ASSET_ROOT,
|
|
56
|
+
"prompts",
|
|
57
|
+
);
|
|
58
|
+
export const CLAUDE_FRAMEWORK_EXAMPLES_DIR = join(
|
|
59
|
+
CLAUDE_FRAMEWORK_ASSET_ROOT,
|
|
60
|
+
"examples",
|
|
61
|
+
);
|
|
62
|
+
export const CLAUDE_FRAMEWORK_HELPERS_DIR = join(
|
|
63
|
+
CLAUDE_FRAMEWORK_ASSET_ROOT,
|
|
64
|
+
"helpers",
|
|
65
|
+
);
|
|
66
|
+
export const CLAUDE_INSTALLED_PROGRESS = join(
|
|
67
|
+
CLAUDE_SKILL_ROOT,
|
|
68
|
+
"progress.md",
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
export const LEGACY_REPO_SKILL_ROOT = join("skills", SKILL_NAME);
|
|
72
|
+
export const LEGACY_REPO_SKILL_AGENTS_DIR = join(
|
|
73
|
+
LEGACY_REPO_SKILL_ROOT,
|
|
74
|
+
"agents",
|
|
75
|
+
);
|
|
76
|
+
export const LEGACY_REPO_SKILL_REFERENCES_DIR = join(
|
|
77
|
+
LEGACY_REPO_SKILL_ROOT,
|
|
78
|
+
"references",
|
|
79
|
+
);
|
|
80
|
+
export const LEGACY_REPO_SKILL_ASSET_ROOT = join(
|
|
81
|
+
LEGACY_REPO_SKILL_ROOT,
|
|
82
|
+
"assets",
|
|
83
|
+
"framework",
|
|
84
|
+
);
|
|
85
|
+
export const LEGACY_REPO_SKILL_MANIFEST = join(
|
|
86
|
+
LEGACY_REPO_SKILL_ASSET_ROOT,
|
|
87
|
+
"manifest.json",
|
|
88
|
+
);
|
|
89
|
+
export const LEGACY_REPO_SKILL_VERSION = join(
|
|
90
|
+
LEGACY_REPO_SKILL_ASSET_ROOT,
|
|
91
|
+
"VERSION",
|
|
92
|
+
);
|
|
93
|
+
export const LEGACY_REPO_SKILL_TEMPLATES_DIR = join(
|
|
94
|
+
LEGACY_REPO_SKILL_ASSET_ROOT,
|
|
95
|
+
"templates",
|
|
96
|
+
);
|
|
97
|
+
export const LEGACY_REPO_SKILL_WORKFLOWS_DIR = join(
|
|
98
|
+
LEGACY_REPO_SKILL_ASSET_ROOT,
|
|
99
|
+
"workflows",
|
|
100
|
+
);
|
|
101
|
+
export const LEGACY_REPO_SKILL_PROMPTS_DIR = join(
|
|
102
|
+
LEGACY_REPO_SKILL_ASSET_ROOT,
|
|
103
|
+
"prompts",
|
|
104
|
+
);
|
|
105
|
+
export const LEGACY_REPO_SKILL_EXAMPLES_DIR = join(
|
|
106
|
+
LEGACY_REPO_SKILL_ASSET_ROOT,
|
|
107
|
+
"examples",
|
|
108
|
+
);
|
|
109
|
+
export const LEGACY_REPO_SKILL_HELPERS_DIR = join(
|
|
110
|
+
LEGACY_REPO_SKILL_ASSET_ROOT,
|
|
111
|
+
"helpers",
|
|
112
|
+
);
|
|
113
|
+
export const LEGACY_REPO_SKILL_PROGRESS = join(
|
|
114
|
+
LEGACY_REPO_SKILL_ROOT,
|
|
115
|
+
"progress.md",
|
|
116
|
+
);
|
|
20
117
|
|
|
21
118
|
export const LEGACY_SKILL_NAME = "first-tree-cli-framework";
|
|
22
119
|
export const LEGACY_SKILL_ROOT = join("skills", LEGACY_SKILL_NAME);
|
|
@@ -52,7 +149,12 @@ export const LEGACY_WORKFLOWS_DIR = join(LEGACY_FRAMEWORK_ROOT, "workflows");
|
|
|
52
149
|
export const LEGACY_PROMPTS_DIR = join(LEGACY_FRAMEWORK_ROOT, "prompts");
|
|
53
150
|
export const LEGACY_EXAMPLES_DIR = join(LEGACY_FRAMEWORK_ROOT, "examples");
|
|
54
151
|
|
|
55
|
-
export type FrameworkLayout =
|
|
152
|
+
export type FrameworkLayout =
|
|
153
|
+
| "skill"
|
|
154
|
+
| "claude-skill"
|
|
155
|
+
| "legacy-repo-skill"
|
|
156
|
+
| "legacy-skill"
|
|
157
|
+
| "legacy";
|
|
56
158
|
|
|
57
159
|
function pathExists(root: string, relPath: string): boolean {
|
|
58
160
|
const fullPath = join(root, relPath);
|
|
@@ -63,12 +165,34 @@ function pathExists(root: string, relPath: string): boolean {
|
|
|
63
165
|
}
|
|
64
166
|
}
|
|
65
167
|
|
|
168
|
+
export function installedSkillRoots(): string[] {
|
|
169
|
+
return [...INSTALLED_SKILL_ROOTS];
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function installedSkillRootsDisplay(): string {
|
|
173
|
+
return installedSkillRoots()
|
|
174
|
+
.map((root) => `\`${root}/\``)
|
|
175
|
+
.join(" and ");
|
|
176
|
+
}
|
|
177
|
+
|
|
66
178
|
export function frameworkVersionCandidates(): string[] {
|
|
67
|
-
return [
|
|
179
|
+
return [
|
|
180
|
+
FRAMEWORK_VERSION,
|
|
181
|
+
CLAUDE_FRAMEWORK_VERSION,
|
|
182
|
+
LEGACY_REPO_SKILL_VERSION,
|
|
183
|
+
LEGACY_SKILL_VERSION,
|
|
184
|
+
LEGACY_VERSION,
|
|
185
|
+
];
|
|
68
186
|
}
|
|
69
187
|
|
|
70
188
|
export function progressFileCandidates(): string[] {
|
|
71
|
-
return [
|
|
189
|
+
return [
|
|
190
|
+
INSTALLED_PROGRESS,
|
|
191
|
+
CLAUDE_INSTALLED_PROGRESS,
|
|
192
|
+
LEGACY_REPO_SKILL_PROGRESS,
|
|
193
|
+
LEGACY_SKILL_PROGRESS,
|
|
194
|
+
LEGACY_PROGRESS,
|
|
195
|
+
];
|
|
72
196
|
}
|
|
73
197
|
|
|
74
198
|
export function agentInstructionsFileCandidates(): string[] {
|
|
@@ -78,6 +202,8 @@ export function agentInstructionsFileCandidates(): string[] {
|
|
|
78
202
|
export function frameworkTemplateDirCandidates(): string[] {
|
|
79
203
|
return [
|
|
80
204
|
FRAMEWORK_TEMPLATES_DIR,
|
|
205
|
+
CLAUDE_FRAMEWORK_TEMPLATES_DIR,
|
|
206
|
+
LEGACY_REPO_SKILL_TEMPLATES_DIR,
|
|
81
207
|
LEGACY_SKILL_TEMPLATES_DIR,
|
|
82
208
|
LEGACY_TEMPLATES_DIR,
|
|
83
209
|
];
|
|
@@ -86,6 +212,8 @@ export function frameworkTemplateDirCandidates(): string[] {
|
|
|
86
212
|
export function frameworkWorkflowDirCandidates(): string[] {
|
|
87
213
|
return [
|
|
88
214
|
FRAMEWORK_WORKFLOWS_DIR,
|
|
215
|
+
CLAUDE_FRAMEWORK_WORKFLOWS_DIR,
|
|
216
|
+
LEGACY_REPO_SKILL_WORKFLOWS_DIR,
|
|
89
217
|
LEGACY_SKILL_WORKFLOWS_DIR,
|
|
90
218
|
LEGACY_WORKFLOWS_DIR,
|
|
91
219
|
];
|
|
@@ -94,6 +222,8 @@ export function frameworkWorkflowDirCandidates(): string[] {
|
|
|
94
222
|
export function frameworkPromptDirCandidates(): string[] {
|
|
95
223
|
return [
|
|
96
224
|
FRAMEWORK_PROMPTS_DIR,
|
|
225
|
+
CLAUDE_FRAMEWORK_PROMPTS_DIR,
|
|
226
|
+
LEGACY_REPO_SKILL_PROMPTS_DIR,
|
|
97
227
|
LEGACY_SKILL_PROMPTS_DIR,
|
|
98
228
|
LEGACY_PROMPTS_DIR,
|
|
99
229
|
];
|
|
@@ -102,6 +232,8 @@ export function frameworkPromptDirCandidates(): string[] {
|
|
|
102
232
|
export function frameworkExampleDirCandidates(): string[] {
|
|
103
233
|
return [
|
|
104
234
|
FRAMEWORK_EXAMPLES_DIR,
|
|
235
|
+
CLAUDE_FRAMEWORK_EXAMPLES_DIR,
|
|
236
|
+
LEGACY_REPO_SKILL_EXAMPLES_DIR,
|
|
105
237
|
LEGACY_SKILL_EXAMPLES_DIR,
|
|
106
238
|
LEGACY_EXAMPLES_DIR,
|
|
107
239
|
];
|
|
@@ -123,6 +255,12 @@ export function detectFrameworkLayout(root: string): FrameworkLayout | null {
|
|
|
123
255
|
if (pathExists(root, FRAMEWORK_VERSION)) {
|
|
124
256
|
return "skill";
|
|
125
257
|
}
|
|
258
|
+
if (pathExists(root, CLAUDE_FRAMEWORK_VERSION)) {
|
|
259
|
+
return "claude-skill";
|
|
260
|
+
}
|
|
261
|
+
if (pathExists(root, LEGACY_REPO_SKILL_VERSION)) {
|
|
262
|
+
return "legacy-repo-skill";
|
|
263
|
+
}
|
|
126
264
|
if (pathExists(root, LEGACY_SKILL_VERSION)) {
|
|
127
265
|
return "legacy-skill";
|
|
128
266
|
}
|
|
@@ -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(
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
SOURCE_INTEGRATION_FILES,
|
|
5
|
+
SOURCE_INTEGRATION_MARKER,
|
|
6
|
+
} from "#skill/engine/runtime/asset-loader.js";
|
|
7
|
+
|
|
8
|
+
export type SourceIntegrationFile = (typeof SOURCE_INTEGRATION_FILES)[number];
|
|
9
|
+
|
|
10
|
+
export interface SourceIntegrationUpdate {
|
|
11
|
+
action: "created" | "updated" | "unchanged";
|
|
12
|
+
file: SourceIntegrationFile;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function buildSourceIntegrationLine(treeRepoName: string): string {
|
|
16
|
+
return (
|
|
17
|
+
`${SOURCE_INTEGRATION_MARKER} Use the installed \`first-tree\` skill here;`
|
|
18
|
+
+ ` keep all Context Tree files only in the sibling dedicated`
|
|
19
|
+
+ ` \`${treeRepoName}\` repo/submodule and update that tree when decisions`
|
|
20
|
+
+ " or constraints change."
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function hasSourceIntegrationMarker(text: string | null): boolean {
|
|
25
|
+
if (text === null) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
return text
|
|
29
|
+
.replaceAll("\r\n", "\n")
|
|
30
|
+
.split("\n")
|
|
31
|
+
.some((line) => line.startsWith(SOURCE_INTEGRATION_MARKER));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function upsertSourceIntegrationFiles(
|
|
35
|
+
root: string,
|
|
36
|
+
treeRepoName: string,
|
|
37
|
+
): SourceIntegrationUpdate[] {
|
|
38
|
+
return SOURCE_INTEGRATION_FILES.map((file) =>
|
|
39
|
+
upsertSourceIntegrationFile(root, file, treeRepoName),
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function upsertSourceIntegrationFile(
|
|
44
|
+
root: string,
|
|
45
|
+
file: SourceIntegrationFile,
|
|
46
|
+
treeRepoName: string,
|
|
47
|
+
): SourceIntegrationUpdate {
|
|
48
|
+
const fullPath = join(root, file);
|
|
49
|
+
const exists = existsSync(fullPath);
|
|
50
|
+
const nextLine = buildSourceIntegrationLine(treeRepoName);
|
|
51
|
+
const current = exists ? readFileSync(fullPath, "utf-8") : null;
|
|
52
|
+
const normalized = current?.replaceAll("\r\n", "\n") ?? "";
|
|
53
|
+
const lines = normalized === "" ? [] : normalized.split("\n");
|
|
54
|
+
const markerIndex = lines.findIndex((line) =>
|
|
55
|
+
line.startsWith(SOURCE_INTEGRATION_MARKER),
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
if (markerIndex >= 0) {
|
|
59
|
+
if (lines[markerIndex] === nextLine) {
|
|
60
|
+
return { action: "unchanged", file };
|
|
61
|
+
}
|
|
62
|
+
lines[markerIndex] = nextLine;
|
|
63
|
+
} else {
|
|
64
|
+
if (lines.length > 0 && lines.at(-1) !== "") {
|
|
65
|
+
lines.push("");
|
|
66
|
+
}
|
|
67
|
+
lines.push(nextLine);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
let nextText = lines.join("\n");
|
|
71
|
+
if (nextText !== "" && !nextText.endsWith("\n")) {
|
|
72
|
+
nextText += "\n";
|
|
73
|
+
}
|
|
74
|
+
writeFileSync(fullPath, nextText);
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
action: exists ? "updated" : "created",
|
|
78
|
+
file,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
@@ -4,20 +4,26 @@ import { Repo } from "#skill/engine/repo.js";
|
|
|
4
4
|
import {
|
|
5
5
|
AGENT_INSTRUCTIONS_FILE,
|
|
6
6
|
AGENT_INSTRUCTIONS_TEMPLATE,
|
|
7
|
+
CLAUDE_INSTRUCTIONS_FILE,
|
|
8
|
+
CLAUDE_SKILL_ROOT,
|
|
7
9
|
FRAMEWORK_WORKFLOWS_DIR,
|
|
8
10
|
FRAMEWORK_TEMPLATES_DIR,
|
|
9
11
|
FRAMEWORK_VERSION,
|
|
10
12
|
INSTALLED_PROGRESS,
|
|
11
13
|
LEGACY_AGENT_INSTRUCTIONS_FILE,
|
|
12
14
|
LEGACY_FRAMEWORK_ROOT,
|
|
15
|
+
LEGACY_REPO_SKILL_ROOT,
|
|
13
16
|
LEGACY_SKILL_ROOT,
|
|
14
17
|
SKILL_ROOT,
|
|
18
|
+
SOURCE_INTEGRATION_MARKER,
|
|
19
|
+
installedSkillRootsDisplay,
|
|
15
20
|
type FrameworkLayout,
|
|
16
21
|
} from "#skill/engine/runtime/asset-loader.js";
|
|
17
22
|
import {
|
|
18
23
|
copyCanonicalSkill,
|
|
19
24
|
resolveBundledPackageRoot,
|
|
20
25
|
} from "#skill/engine/runtime/installer.js";
|
|
26
|
+
import { upsertSourceIntegrationFiles } from "#skill/engine/runtime/source-integration.js";
|
|
21
27
|
import {
|
|
22
28
|
compareFrameworkVersions,
|
|
23
29
|
readSourceVersion,
|
|
@@ -45,9 +51,10 @@ function formatUpgradeTaskList(
|
|
|
45
51
|
const lines: string[] = [
|
|
46
52
|
`# Context Tree Upgrade — v${localVersion} -> v${packagedVersion}\n`,
|
|
47
53
|
"## Installed Skill",
|
|
48
|
-
`- [ ] Review local customizations under
|
|
54
|
+
`- [ ] Review local customizations under ${installedSkillRootsDisplay()} and reapply them if needed`,
|
|
49
55
|
`- [ ] Re-copy any workflow updates you want from \`${FRAMEWORK_WORKFLOWS_DIR}/\` into \`.github/workflows/\``,
|
|
50
|
-
`- [ ] Re-check any local agent setup that references \`${
|
|
56
|
+
`- [ ] Re-check any local agent setup that references \`${CLAUDE_SKILL_ROOT}/assets/framework/examples/\` or \`${CLAUDE_SKILL_ROOT}/assets/framework/helpers/\``,
|
|
57
|
+
`- [ ] Re-check any repo scripts or workflow files that reference \`${SKILL_ROOT}/assets/framework/\``,
|
|
51
58
|
"",
|
|
52
59
|
];
|
|
53
60
|
|
|
@@ -58,6 +65,14 @@ function formatUpgradeTaskList(
|
|
|
58
65
|
);
|
|
59
66
|
}
|
|
60
67
|
|
|
68
|
+
if (layout === "legacy-repo-skill") {
|
|
69
|
+
lines.push(
|
|
70
|
+
"## Migration",
|
|
71
|
+
`- [ ] Remove any stale \`${LEGACY_REPO_SKILL_ROOT}/\` references from repo-specific docs, scripts, workflow files, or agent config`,
|
|
72
|
+
"",
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
61
76
|
if (layout === "legacy-skill") {
|
|
62
77
|
migrationTasks.push(
|
|
63
78
|
`- [ ] Remove any stale \`${LEGACY_SKILL_ROOT}/\` references from repo-specific docs, scripts, workflow files, or agent config`,
|
|
@@ -109,8 +124,10 @@ export interface UpgradeOptions {
|
|
|
109
124
|
|
|
110
125
|
export function runUpgrade(repo?: Repo, options?: UpgradeOptions): number {
|
|
111
126
|
const workingRepo = repo ?? new Repo();
|
|
127
|
+
const workspaceOnlyIntegration =
|
|
128
|
+
workingRepo.hasSourceWorkspaceIntegration() && !workingRepo.looksLikeTreeRepo();
|
|
112
129
|
|
|
113
|
-
if (workingRepo.isLikelySourceRepo() && !workingRepo.looksLikeTreeRepo()) {
|
|
130
|
+
if (workingRepo.isLikelySourceRepo() && !workingRepo.looksLikeTreeRepo() && !workspaceOnlyIntegration) {
|
|
114
131
|
console.error(
|
|
115
132
|
"Error: no installed framework skill found here. This looks like a source/workspace repo. Run `context-tree init` to create a dedicated tree repo, or pass `--tree-path` to upgrade an existing tree repo.",
|
|
116
133
|
);
|
|
@@ -165,7 +182,74 @@ export function runUpgrade(repo?: Repo, options?: UpgradeOptions): number {
|
|
|
165
182
|
return 1;
|
|
166
183
|
}
|
|
167
184
|
|
|
168
|
-
|
|
185
|
+
const missingInstalledRoots = workingRepo.missingInstalledSkillRoots();
|
|
186
|
+
const sourceRepoTreePathHint = `../${workingRepo.repoName()}-context`;
|
|
187
|
+
|
|
188
|
+
if (workspaceOnlyIntegration) {
|
|
189
|
+
if (
|
|
190
|
+
layout === "skill" &&
|
|
191
|
+
missingInstalledRoots.length === 0 &&
|
|
192
|
+
packagedVersion === localVersion
|
|
193
|
+
) {
|
|
194
|
+
const updates = upsertSourceIntegrationFiles(
|
|
195
|
+
workingRepo.root,
|
|
196
|
+
`${workingRepo.repoName()}-context`,
|
|
197
|
+
);
|
|
198
|
+
const changedFiles = updates
|
|
199
|
+
.filter((update) => update.action !== "unchanged")
|
|
200
|
+
.map((update) => update.file);
|
|
201
|
+
if (changedFiles.length === 0) {
|
|
202
|
+
console.log(
|
|
203
|
+
`Already up to date with the bundled skill (${FRAMEWORK_VERSION} = ${localVersion}).`,
|
|
204
|
+
);
|
|
205
|
+
console.log(
|
|
206
|
+
`This repo only carries source/workspace integration. Upgrade the dedicated tree repo separately with \`context-tree upgrade --tree-path ${sourceRepoTreePathHint}\`.`,
|
|
207
|
+
);
|
|
208
|
+
return 0;
|
|
209
|
+
}
|
|
210
|
+
console.log(
|
|
211
|
+
`Already up to date with the bundled skill (${FRAMEWORK_VERSION} = ${localVersion}).`,
|
|
212
|
+
);
|
|
213
|
+
console.log(
|
|
214
|
+
`Updated the ${SOURCE_INTEGRATION_MARKER} marker lines in ${changedFiles.map((file) => `\`${file}\``).join(" and ")}.`,
|
|
215
|
+
);
|
|
216
|
+
console.log(
|
|
217
|
+
`This repo only carries source/workspace integration. Upgrade the dedicated tree repo separately with \`context-tree upgrade --tree-path ${sourceRepoTreePathHint}\`.`,
|
|
218
|
+
);
|
|
219
|
+
return 0;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
copyCanonicalSkill(sourceRoot, workingRepo.root);
|
|
223
|
+
const updates = upsertSourceIntegrationFiles(
|
|
224
|
+
workingRepo.root,
|
|
225
|
+
`${workingRepo.repoName()}-context`,
|
|
226
|
+
);
|
|
227
|
+
const changedFiles = updates
|
|
228
|
+
.filter((update) => update.action !== "unchanged")
|
|
229
|
+
.map((update) => update.file);
|
|
230
|
+
console.log(
|
|
231
|
+
`Refreshed ${installedSkillRootsDisplay()} in this source/workspace repo.`,
|
|
232
|
+
);
|
|
233
|
+
if (changedFiles.length > 0) {
|
|
234
|
+
console.log(
|
|
235
|
+
`Updated the ${SOURCE_INTEGRATION_MARKER} marker lines in ${changedFiles.map((file) => `\`${file}\``).join(" and ")}.`,
|
|
236
|
+
);
|
|
237
|
+
} else {
|
|
238
|
+
console.log(
|
|
239
|
+
`The ${SOURCE_INTEGRATION_MARKER} marker lines in ${AGENT_INSTRUCTIONS_FILE} and ${CLAUDE_INSTRUCTIONS_FILE} were already current.`,
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
console.log(
|
|
243
|
+
`This repo is not the Context Tree. Upgrade the dedicated tree repo separately with \`context-tree upgrade --tree-path ${sourceRepoTreePathHint}\`.`,
|
|
244
|
+
);
|
|
245
|
+
return 0;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (
|
|
249
|
+
layout === "skill" &&
|
|
250
|
+
missingInstalledRoots.length === 0 &&
|
|
251
|
+
packagedVersion === localVersion
|
|
252
|
+
) {
|
|
169
253
|
console.log(
|
|
170
254
|
`Already up to date with the bundled skill (${FRAMEWORK_VERSION} = ${localVersion}).`,
|
|
171
255
|
);
|
|
@@ -179,16 +263,26 @@ export function runUpgrade(repo?: Repo, options?: UpgradeOptions): number {
|
|
|
179
263
|
force: true,
|
|
180
264
|
});
|
|
181
265
|
console.log(
|
|
182
|
-
|
|
266
|
+
`Migrated legacy .context-tree/ layout to ${installedSkillRootsDisplay()}.`,
|
|
183
267
|
);
|
|
184
|
-
} else if (layout === "legacy-skill") {
|
|
268
|
+
} else if (layout === "legacy-repo-skill") {
|
|
185
269
|
console.log(
|
|
186
|
-
|
|
270
|
+
`Migrated legacy ${LEGACY_REPO_SKILL_ROOT}/ layout to ${installedSkillRootsDisplay()}.`,
|
|
187
271
|
);
|
|
188
|
-
} else {
|
|
272
|
+
} else if (layout === "legacy-skill") {
|
|
189
273
|
console.log(
|
|
190
|
-
|
|
274
|
+
`Migrated ${LEGACY_SKILL_ROOT}/ to ${installedSkillRootsDisplay()}.`,
|
|
191
275
|
);
|
|
276
|
+
} else {
|
|
277
|
+
if (missingInstalledRoots.length > 0) {
|
|
278
|
+
console.log(
|
|
279
|
+
`Repaired missing installed skill roots (${missingInstalledRoots.map((root) => `${root}/`).join(", ")}) and refreshed ${installedSkillRootsDisplay()} from the bundled first-tree package.`,
|
|
280
|
+
);
|
|
281
|
+
} else {
|
|
282
|
+
console.log(
|
|
283
|
+
`Refreshed ${installedSkillRootsDisplay()} from the bundled first-tree package.`,
|
|
284
|
+
);
|
|
285
|
+
}
|
|
192
286
|
}
|
|
193
287
|
|
|
194
288
|
const output = formatUpgradeTaskList(
|
|
@@ -50,6 +50,13 @@ export function runVerify(repo?: Repo, nodeValidator?: NodeValidator): number {
|
|
|
50
50
|
const r = repo ?? new Repo();
|
|
51
51
|
const validate = nodeValidator ?? defaultNodeValidator;
|
|
52
52
|
|
|
53
|
+
if (r.hasSourceWorkspaceIntegration() && !r.looksLikeTreeRepo()) {
|
|
54
|
+
console.error(
|
|
55
|
+
`Error: this repo only has the first-tree source/workspace integration installed. Verify the dedicated tree repo instead, for example \`context-tree verify --tree-path ../${r.repoName()}-context\`.`,
|
|
56
|
+
);
|
|
57
|
+
return 1;
|
|
58
|
+
}
|
|
59
|
+
|
|
53
60
|
if (r.isLikelySourceRepo() && !r.looksLikeTreeRepo()) {
|
|
54
61
|
console.error(
|
|
55
62
|
"Error: no installed framework skill found here. This looks like a source/workspace repo. Run `context-tree init` to create a dedicated tree repo, or pass `--tree-path` to verify an existing tree repo.",
|
|
@@ -13,6 +13,10 @@ This reference explains how to maintain the `first-tree` source repo itself.
|
|
|
13
13
|
This repo is not a user context tree. User decision content lives in the repos
|
|
14
14
|
that install the framework.
|
|
15
15
|
|
|
16
|
+
When a source/workspace repo installs first-tree, that repo should keep only
|
|
17
|
+
the local skill integration and marker lines. Tree content still belongs only
|
|
18
|
+
in a dedicated `*-context` repo.
|
|
19
|
+
|
|
16
20
|
## Canonical Layers
|
|
17
21
|
|
|
18
22
|
1. `SKILL.md` defines when to use the skill and the maintainer workflow.
|
|
@@ -33,6 +33,9 @@ These root files are shell code, not canonical knowledge stores:
|
|
|
33
33
|
- Keep root prose short. It should point to the skill, not duplicate the skill.
|
|
34
34
|
- Keep command semantics, install layout rules, and maintainer guidance in the
|
|
35
35
|
skill references.
|
|
36
|
+
- If init/upgrade semantics change for source/workspace repos versus dedicated
|
|
37
|
+
tree repos, update `references/source-workspace-installation.md` and
|
|
38
|
+
`references/upgrade-contract.md` instead of expanding root shell prose.
|
|
36
39
|
- If the shell gains behavior that is not obviously mechanical, move that
|
|
37
40
|
behavior or its contract into the skill.
|
|
38
41
|
- When in doubt, prefer adding a skill reference over expanding root docs.
|