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.
Files changed (54) hide show
  1. package/README.md +36 -13
  2. package/dist/cli.js +5 -5
  3. package/dist/{help-Dtdj91HJ.js → help-DV9-AaFp.js} +1 -1
  4. package/dist/{init--VepFe6N.js → init-BgGH2_yC.js} +57 -11
  5. package/dist/onboarding-D7fGGOMN.js +10 -0
  6. package/dist/onboarding-lASHHmgO.js +2 -0
  7. package/dist/{repo-DY57bMqr.js → repo-Cc5U4DWT.js} +76 -5
  8. package/dist/source-integration-CuKjoheT.js +84 -0
  9. package/dist/{upgrade-Cgx_K2HM.js → upgrade-BvA9oKmi.js} +37 -9
  10. package/dist/{verify-mC9ZTd1f.js → verify-G8gNXzDX.js} +5 -1
  11. package/package.json +11 -9
  12. package/skills/first-tree/SKILL.md +30 -10
  13. package/skills/first-tree/agents/openai.yaml +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 +1 -1
  19. package/skills/first-tree/assets/framework/templates/agents.md.template +2 -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 +3 -3
  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/init.ts +78 -6
  25. package/skills/first-tree/engine/repo.ts +74 -10
  26. package/skills/first-tree/engine/rules/agent-integration.ts +3 -1
  27. package/skills/first-tree/engine/rules/framework.ts +2 -2
  28. package/skills/first-tree/engine/runtime/adapters.ts +6 -2
  29. package/skills/first-tree/engine/runtime/asset-loader.ts +142 -4
  30. package/skills/first-tree/engine/runtime/installer.ts +18 -12
  31. package/skills/first-tree/engine/runtime/source-integration.ts +80 -0
  32. package/skills/first-tree/engine/upgrade.ts +103 -9
  33. package/skills/first-tree/engine/verify.ts +7 -0
  34. package/skills/first-tree/references/maintainer-architecture.md +4 -0
  35. package/skills/first-tree/references/maintainer-thin-cli.md +3 -0
  36. package/skills/first-tree/references/onboarding.md +56 -21
  37. package/skills/first-tree/references/principles.md +97 -57
  38. package/skills/first-tree/references/source-map.md +1 -0
  39. package/skills/first-tree/references/source-workspace-installation.md +52 -0
  40. package/skills/first-tree/references/upgrade-contract.md +67 -26
  41. package/skills/first-tree/scripts/check-skill-sync.sh +2 -0
  42. package/skills/first-tree/scripts/quick_validate.py +0 -0
  43. package/skills/first-tree/scripts/run-local-cli.sh +0 -0
  44. package/skills/first-tree/tests/asset-loader.test.ts +23 -1
  45. package/skills/first-tree/tests/helpers.ts +27 -3
  46. package/skills/first-tree/tests/init.test.ts +72 -3
  47. package/skills/first-tree/tests/repo.test.ts +46 -0
  48. package/skills/first-tree/tests/rules.test.ts +9 -7
  49. package/skills/first-tree/tests/skill-artifacts.test.ts +45 -0
  50. package/skills/first-tree/tests/upgrade.test.ts +58 -3
  51. package/skills/first-tree/tests/verify.test.ts +21 -3
  52. package/dist/installer-cH7N4RNj.js +0 -47
  53. package/dist/onboarding-C9cYSE6F.js +0 -2
  54. 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 \`${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");
@@ -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 = "skill" | "legacy-skill" | "legacy";
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 [FRAMEWORK_VERSION, LEGACY_SKILL_VERSION, LEGACY_VERSION];
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 [INSTALLED_PROGRESS, LEGACY_SKILL_PROGRESS, LEGACY_PROGRESS];
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
- 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(
@@ -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 \`${SKILL_ROOT}/\` and reapply them if needed`,
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 \`${SKILL_ROOT}/assets/framework/examples/\` or \`${SKILL_ROOT}/assets/framework/helpers/\``,
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
- if (layout === "skill" && packagedVersion === localVersion) {
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
- "Migrated legacy .context-tree/ layout to skills/first-tree/.",
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
- "Migrated skills/first-tree-cli-framework/ to skills/first-tree/.",
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
- "Refreshed skills/first-tree/ from the bundled first-tree package.",
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.