first-tree 0.0.5 → 0.0.7

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 (41) hide show
  1. package/README.md +25 -7
  2. package/dist/cli.js +9 -6
  3. package/dist/{help-5-WG9QFm.js → help-BRO4mTG6.js} +1 -1
  4. package/dist/{init-CAq0Uhq6.js → init-BSs0ILp_.js} +49 -9
  5. package/dist/onboarding-BS8btkG4.js +2 -0
  6. package/dist/onboarding-D3hnxIie.js +10 -0
  7. package/dist/{repo-DkR12VUv.js → repo-0z7N9r17.js} +22 -16
  8. package/dist/source-integration-C2iiN4k_.js +80 -0
  9. package/dist/{upgrade-DYzuvv1k.js → upgrade-DvBdbph3.js} +26 -5
  10. package/dist/{verify-C0IUSkMZ.js → verify-DRt5mCqO.js} +7 -3
  11. package/package.json +2 -2
  12. package/skills/first-tree/SKILL.md +31 -5
  13. package/skills/first-tree/agents/openai.yaml +1 -1
  14. package/skills/first-tree/engine/init.ts +88 -5
  15. package/skills/first-tree/engine/repo.ts +38 -15
  16. package/skills/first-tree/engine/runtime/adapters.ts +0 -2
  17. package/skills/first-tree/engine/runtime/asset-loader.ts +6 -36
  18. package/skills/first-tree/engine/runtime/installer.ts +0 -2
  19. package/skills/first-tree/engine/runtime/source-integration.ts +80 -0
  20. package/skills/first-tree/engine/upgrade.ts +68 -12
  21. package/skills/first-tree/engine/validators/nodes.ts +2 -11
  22. package/skills/first-tree/engine/verify.ts +7 -0
  23. package/skills/first-tree/references/maintainer-architecture.md +4 -0
  24. package/skills/first-tree/references/maintainer-thin-cli.md +3 -0
  25. package/skills/first-tree/references/onboarding.md +33 -5
  26. package/skills/first-tree/references/principles.md +97 -57
  27. package/skills/first-tree/references/source-map.md +1 -0
  28. package/skills/first-tree/references/source-workspace-installation.md +64 -0
  29. package/skills/first-tree/references/upgrade-contract.md +23 -12
  30. package/skills/first-tree/scripts/check-skill-sync.sh +2 -1
  31. package/skills/first-tree/tests/asset-loader.test.ts +0 -24
  32. package/skills/first-tree/tests/helpers.ts +0 -14
  33. package/skills/first-tree/tests/init.test.ts +67 -0
  34. package/skills/first-tree/tests/repo.test.ts +20 -25
  35. package/skills/first-tree/tests/skill-artifacts.test.ts +43 -1
  36. package/skills/first-tree/tests/thin-cli.test.ts +5 -0
  37. package/skills/first-tree/tests/upgrade.test.ts +38 -23
  38. package/skills/first-tree/tests/verify.test.ts +18 -0
  39. package/dist/installer-UgNasLjl.js +0 -51
  40. package/dist/onboarding-3zYUeYQb.js +0 -2
  41. package/dist/onboarding-Dd63N-V1.js +0 -10
@@ -28,6 +28,7 @@ repos.
28
28
  2. Read the user-facing reference that matches the task:
29
29
  - `references/onboarding.md`
30
30
  - `references/about.md`
31
+ - `references/source-workspace-installation.md`
31
32
  - `references/principles.md`
32
33
  - `references/ownership-and-naming.md`
33
34
  - `references/upgrade-contract.md`
@@ -58,16 +59,39 @@ repos.
58
59
 
59
60
  ### Working In A User Tree Repo
60
61
 
62
+ - When the task is to "install and use first-tree" in an existing
63
+ source/workspace repo, start with
64
+ `references/source-workspace-installation.md` and follow that workflow
65
+ end-to-end before improvising.
66
+ - When a user asks to install first-tree for an existing source/workspace repo,
67
+ the current repo keeps only the installed skill plus a
68
+ `FIRST-TREE-SOURCE-INTEGRATION:` line in `AGENTS.md` and `CLAUDE.md`. Do not
69
+ create `NODE.md`, `members/`, or tree-scoped `AGENTS.md` there.
61
70
  - `context-tree init` defaults to creating or reusing a sibling dedicated tree
62
- repo when invoked from a source/workspace repo. Use `--here` to initialize
63
- the current repo in place when you are already inside the tree repo.
71
+ repo when invoked from a source/workspace repo. It installs the bundled skill
72
+ into the source/workspace repo and scaffolds tree files only in the
73
+ dedicated tree repo. Use `--here` to initialize the current repo in place
74
+ when you are already inside the tree repo.
75
+ - Never run `context-tree init --here` in a source/workspace repo unless the
76
+ user explicitly wants that repo itself to become the dedicated Context Tree.
77
+ `--here` is for when you have already switched into the `*-context` repo.
64
78
  - `context-tree init` installs this skill into the target tree repo and
65
79
  scaffolds `.agents/skills/first-tree/`, `.claude/skills/first-tree/`,
66
80
  `NODE.md`, `AGENTS.md`, and `members/NODE.md`.
81
+ - The default source/workspace workflow is: create or reuse `<repo>-context`,
82
+ prefer pushing it in the same GitHub organization as the source repo, add it
83
+ back to the source/workspace repo as a git submodule, and open a PR to the
84
+ source/workspace repo's default branch instead of merging automatically.
85
+ - If permissions, auth, or local filesystem constraints block the dedicated
86
+ repo workflow, stop and report the blocker. Do not fall back to in-place tree
87
+ bootstrap in the source/workspace repo.
67
88
  - `context-tree upgrade` refreshes the installed skill from the copy bundled
68
- with the currently running `first-tree` package. To pick up a newer
69
- framework, run a newer package version first. It also migrates older repos
70
- that still use `skills/first-tree/` or `skills/first-tree-cli-framework/`.
89
+ with the currently running `first-tree` package. In a source/workspace repo
90
+ it refreshes only the local skill plus the
91
+ `FIRST-TREE-SOURCE-INTEGRATION:` line; upgrade the dedicated tree repo
92
+ separately with `--tree-path`. To pick up a newer framework, run a newer
93
+ package version first. It also migrates older repos that still use
94
+ `skills/first-tree/`.
71
95
  - The user's tree content lives outside the skill; the skill only carries the
72
96
  reusable framework payload plus maintenance guidance.
73
97
  - The tree still stores decisions, constraints, and ownership; execution detail
@@ -108,6 +132,8 @@ repos.
108
132
  - `engine/`: canonical framework and CLI behavior
109
133
  - `tests/`: canonical unit and structure validation
110
134
  - `references/source-map.md`: canonical reading index
135
+ - `references/source-workspace-installation.md`: source/workspace install
136
+ contract
111
137
  - `references/maintainer-architecture.md`: source-repo architecture and
112
138
  invariants
113
139
  - `references/maintainer-thin-cli.md`: root shell contract
@@ -1,4 +1,4 @@
1
1
  interface:
2
2
  display_name: "First Tree"
3
3
  short_description: "Maintain the First Tree skill and thin Context Tree CLI"
4
- default_prompt: "Use $first-tree to maintain the canonical first-tree skill, its thin context-tree CLI, or its build, packaging, test, eval, and CI wiring."
4
+ default_prompt: "Use $first-tree to maintain the canonical first-tree skill, its thin context-tree CLI, or its build, packaging, test, eval, and CI wiring. When a source/workspace repo installs first-tree, read references/source-workspace-installation.md first, keep that repo limited to local skill integration plus the FIRST-TREE-SOURCE-INTEGRATION marker lines, and keep all NODE.md/tree content only in a dedicated sibling *-context repo. Never choose context-tree init --here in the source/workspace repo unless the user explicitly wants that repo itself to become the tree."
@@ -19,12 +19,15 @@ import {
19
19
  import {
20
20
  AGENT_INSTRUCTIONS_FILE,
21
21
  AGENT_INSTRUCTIONS_TEMPLATE,
22
+ CLAUDE_INSTRUCTIONS_FILE,
22
23
  FRAMEWORK_ASSET_ROOT,
23
24
  FRAMEWORK_VERSION,
24
25
  INSTALLED_PROGRESS,
25
26
  LEGACY_AGENT_INSTRUCTIONS_FILE,
26
27
  installedSkillRootsDisplay,
28
+ SOURCE_INTEGRATION_MARKER,
27
29
  } from "#skill/engine/runtime/asset-loader.js";
30
+ import { upsertSourceIntegrationFiles } from "#skill/engine/runtime/source-integration.js";
28
31
 
29
32
  /**
30
33
  * The interactive prompt tool the agent should use to present choices.
@@ -34,11 +37,16 @@ import {
34
37
  export const INTERACTIVE_TOOL = "AskUserQuestion";
35
38
  export const INIT_USAGE = `usage: context-tree init [--here] [--tree-name NAME] [--tree-path PATH]
36
39
 
37
- By default, running \`context-tree init\` inside a source or workspace repo creates
38
- a sibling dedicated tree repo named \`<repo>-context\`.
40
+ By default, running \`context-tree init\` inside a source or workspace repo installs
41
+ the first-tree skill in the current repo, updates \`AGENTS.md\` and \`CLAUDE.md\`
42
+ with a \`${SOURCE_INTEGRATION_MARKER}\` line, and creates a sibling dedicated tree
43
+ repo named \`<repo>-context\`.
44
+
45
+ Do not use \`--here\` inside a source/workspace repo unless you explicitly want
46
+ that repo itself to become the Context Tree.
39
47
 
40
48
  Options:
41
- --here Initialize the current repo in place
49
+ --here Initialize the current repo in place after you are already in the dedicated tree repo
42
50
  --tree-name NAME Name the dedicated sibling tree repo to create
43
51
  --tree-path PATH Use an explicit tree repo path
44
52
  --help Show this help message
@@ -62,6 +70,7 @@ const TEMPLATE_MAP: TemplateTarget[] = [
62
70
 
63
71
  interface TaskListContext {
64
72
  sourceRepoPath?: string;
73
+ sourceRepoName?: string;
65
74
  dedicatedTreeRepo?: boolean;
66
75
  }
67
76
 
@@ -106,6 +115,23 @@ export function formatTaskList(
106
115
  if (context.sourceRepoPath) {
107
116
  lines.push(`**Bootstrap source repo:** \`${context.sourceRepoPath}\``, "");
108
117
  }
118
+ if (context.sourceRepoName) {
119
+ lines.push(
120
+ `**Source/workspace contract:** Keep \`${context.sourceRepoName}\` limited to the installed skill plus the \`${SOURCE_INTEGRATION_MARKER}\` lines in \`${AGENT_INSTRUCTIONS_FILE}\` and \`${CLAUDE_INSTRUCTIONS_FILE}\`. Never add \`NODE.md\`, \`members/\`, or tree-scoped \`${AGENT_INSTRUCTIONS_FILE}\` there.`,
121
+ "",
122
+ );
123
+ lines.push("## Source Workspace Workflow");
124
+ lines.push(
125
+ `- [ ] Publish this dedicated tree repo to the same GitHub organization as \`${context.sourceRepoName}\``,
126
+ );
127
+ lines.push(
128
+ `- [ ] Add this tree repo back to \`${context.sourceRepoName}\` as a git submodule after the remote exists`,
129
+ );
130
+ lines.push(
131
+ `- [ ] Open a PR against the source/workspace repo's default branch with only the installed skill, the \`${SOURCE_INTEGRATION_MARKER}\` marker lines in \`${AGENT_INSTRUCTIONS_FILE}\` / \`${CLAUDE_INSTRUCTIONS_FILE}\`, and the new submodule pointer`,
132
+ );
133
+ lines.push("");
134
+ }
109
135
  lines.push(
110
136
  "When you publish this tree repo, keep it in the same GitHub organization" +
111
137
  " as the source repo unless you have a reason not to.",
@@ -179,12 +205,31 @@ export function runInit(repo?: Repo, options?: InitOptions): number {
179
205
  return 1;
180
206
  }
181
207
  const r = initTarget.repo;
208
+ if (options?.here && sourceRepo.isLikelySourceRepo() && !sourceRepo.looksLikeTreeRepo()) {
209
+ console.log(
210
+ "Warning: `context-tree init --here` is initializing this source/workspace" +
211
+ " repo in place. This will create `NODE.md`, `members/`, and tree-scoped" +
212
+ ` ${AGENT_INSTRUCTIONS_FILE} here. Use plain \`context-tree init\` to create` +
213
+ " a sibling dedicated tree repo instead.",
214
+ );
215
+ console.log();
216
+ }
182
217
  const taskListContext = initTarget.dedicatedTreeRepo
183
218
  ? {
184
219
  dedicatedTreeRepo: true,
220
+ sourceRepoName: sourceRepo.repoName(),
185
221
  sourceRepoPath: relativePathFrom(r.root, sourceRepo.root),
186
222
  }
187
223
  : undefined;
224
+ let sourceRoot: string | null = null;
225
+
226
+ const resolveSourceRoot = (): string => {
227
+ if (sourceRoot !== null) {
228
+ return sourceRoot;
229
+ }
230
+ sourceRoot = options?.sourceRoot ?? resolveBundledPackageRoot();
231
+ return sourceRoot;
232
+ };
188
233
 
189
234
  if (initTarget.dedicatedTreeRepo) {
190
235
  console.log(
@@ -196,17 +241,55 @@ export function runInit(repo?: Repo, options?: InitOptions): number {
196
241
  if (initTarget.createdGitRepo) {
197
242
  console.log(" Initialized a new git repo for the tree.");
198
243
  }
244
+ console.log(
245
+ " The source/workspace repo should keep only the installed skill and the" +
246
+ ` ${SOURCE_INTEGRATION_MARKER} lines in ${AGENT_INSTRUCTIONS_FILE} and ${CLAUDE_INSTRUCTIONS_FILE}.`,
247
+ );
248
+ console.log(
249
+ ` Never add NODE.md, members/, or tree-scoped ${AGENT_INSTRUCTIONS_FILE} to the source/workspace repo.`,
250
+ );
199
251
  console.log();
200
252
  }
201
253
 
254
+ if (initTarget.dedicatedTreeRepo) {
255
+ try {
256
+ const resolvedSourceRoot = resolveSourceRoot();
257
+ const hadSourceSkill = sourceRepo.hasCurrentInstalledSkill();
258
+ if (!hadSourceSkill) {
259
+ console.log(
260
+ "Installing the first-tree skill into the source/workspace repo...",
261
+ );
262
+ installSkill(resolvedSourceRoot, sourceRepo.root);
263
+ }
264
+ const updates = upsertSourceIntegrationFiles(sourceRepo.root, r.repoName());
265
+ const changedFiles = updates
266
+ .filter((update) => update.action !== "unchanged")
267
+ .map((update) => update.file);
268
+ if (changedFiles.length > 0) {
269
+ console.log(
270
+ ` Updated source/workspace instructions in ${changedFiles.map((file) => `\`${file}\``).join(" and ")}`,
271
+ );
272
+ } else {
273
+ console.log(
274
+ ` Source/workspace instructions already contain ${SOURCE_INTEGRATION_MARKER}`,
275
+ );
276
+ }
277
+ console.log();
278
+ } catch (err) {
279
+ const message = err instanceof Error ? err.message : "unknown error";
280
+ console.error(`Error: ${message}`);
281
+ return 1;
282
+ }
283
+ }
284
+
202
285
  if (!r.hasCurrentInstalledSkill()) {
203
286
  try {
204
- const sourceRoot = options?.sourceRoot ?? resolveBundledPackageRoot();
287
+ const resolvedSourceRoot = resolveSourceRoot();
205
288
  console.log(
206
289
  "Installing the framework skill bundled with this first-tree package...",
207
290
  );
208
291
  console.log("Installing skill and scaffolding...");
209
- installSkill(sourceRoot, r.root);
292
+ installSkill(resolvedSourceRoot, r.root);
210
293
  renderTemplates(r.root);
211
294
  console.log();
212
295
  } catch (err) {
@@ -10,8 +10,6 @@ import {
10
10
  LEGACY_PROGRESS,
11
11
  LEGACY_REPO_SKILL_PROGRESS,
12
12
  LEGACY_REPO_SKILL_VERSION,
13
- LEGACY_SKILL_PROGRESS,
14
- LEGACY_SKILL_VERSION,
15
13
  LEGACY_VERSION,
16
14
  agentInstructionsFileCandidates,
17
15
  installedSkillRoots,
@@ -20,13 +18,17 @@ import {
20
18
  frameworkVersionCandidates,
21
19
  progressFileCandidates,
22
20
  resolveFirstExistingPath,
21
+ SOURCE_INTEGRATION_FILES,
22
+ SOURCE_INTEGRATION_MARKER,
23
23
  } from "#skill/engine/runtime/asset-loader.js";
24
24
 
25
25
  const FRONTMATTER_RE = /^---\s*\n(.*?)\n---/s;
26
26
  const OWNERS_RE = /^owners:\s*\[([^\]]*)\]/m;
27
27
  const TITLE_RE = /^title:\s*['"]?(.+?)['"]?\s*$/m;
28
28
  const EMPTY_REPO_ENTRY_ALLOWLIST = new Set([
29
+ ".agents",
29
30
  ".DS_Store",
31
+ ".claude",
30
32
  ".editorconfig",
31
33
  ".gitattributes",
32
34
  ".github",
@@ -216,9 +218,6 @@ export class Repo {
216
218
  if (layout === "legacy") {
217
219
  return LEGACY_PROGRESS;
218
220
  }
219
- if (layout === "legacy-skill") {
220
- return LEGACY_SKILL_PROGRESS;
221
- }
222
221
  if (layout === "legacy-repo-skill") {
223
222
  return LEGACY_REPO_SKILL_PROGRESS;
224
223
  }
@@ -233,9 +232,6 @@ export class Repo {
233
232
  if (layout === "legacy") {
234
233
  return LEGACY_VERSION;
235
234
  }
236
- if (layout === "legacy-skill") {
237
- return LEGACY_SKILL_VERSION;
238
- }
239
235
  if (layout === "legacy-repo-skill") {
240
236
  return LEGACY_REPO_SKILL_VERSION;
241
237
  }
@@ -273,6 +269,23 @@ export class Repo {
273
269
  return text.includes(FRAMEWORK_BEGIN_MARKER) && text.includes(FRAMEWORK_END_MARKER);
274
270
  }
275
271
 
272
+ hasSourceIntegrationFile(relPath: string): boolean {
273
+ return this.fileContains(relPath, SOURCE_INTEGRATION_MARKER);
274
+ }
275
+
276
+ hasSourceWorkspaceIntegration(): boolean {
277
+ return SOURCE_INTEGRATION_FILES.some((file) => this.hasSourceIntegrationFile(file));
278
+ }
279
+
280
+ hasTreeContent(): boolean {
281
+ return (
282
+ this.progressPath() !== null
283
+ || this.hasAgentInstructionsMarkers()
284
+ || this.pathExists("members/NODE.md")
285
+ || this.frontmatter("NODE.md") !== null
286
+ );
287
+ }
288
+
276
289
  hasMembers(): boolean {
277
290
  const membersDir = join(this.root, "members");
278
291
  try {
@@ -338,13 +351,19 @@ export class Repo {
338
351
  return false;
339
352
  }
340
353
 
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
- );
354
+ if (this.hasTreeContent()) {
355
+ return true;
356
+ }
357
+
358
+ if (this.hasFramework() && this.hasSourceWorkspaceIntegration()) {
359
+ return false;
360
+ }
361
+
362
+ if (this.hasFramework()) {
363
+ return !this.hasLikelySourceRepoSignals();
364
+ }
365
+
366
+ return false;
348
367
  }
349
368
 
350
369
  isLikelyEmptyRepo(): boolean {
@@ -359,6 +378,10 @@ export class Repo {
359
378
  return false;
360
379
  }
361
380
 
381
+ return this.hasLikelySourceRepoSignals();
382
+ }
383
+
384
+ private hasLikelySourceRepoSignals(): boolean {
362
385
  const entries = this.topLevelEntries().filter(
363
386
  (entry) => !EMPTY_REPO_ENTRY_ALLOWLIST.has(entry),
364
387
  );
@@ -4,7 +4,6 @@ import {
4
4
  CLAUDE_FRAMEWORK_HELPERS_DIR,
5
5
  FRAMEWORK_EXAMPLES_DIR,
6
6
  LEGACY_REPO_SKILL_EXAMPLES_DIR,
7
- LEGACY_SKILL_EXAMPLES_DIR,
8
7
  LEGACY_EXAMPLES_DIR,
9
8
  } from "#skill/engine/runtime/asset-loader.js";
10
9
 
@@ -16,7 +15,6 @@ export function claudeCodeExampleCandidates(): string[] {
16
15
  join(CLAUDE_FRAMEWORK_EXAMPLES_DIR, "claude-code"),
17
16
  join(FRAMEWORK_EXAMPLES_DIR, "claude-code"),
18
17
  join(LEGACY_REPO_SKILL_EXAMPLES_DIR, "claude-code"),
19
- join(LEGACY_SKILL_EXAMPLES_DIR, "claude-code"),
20
18
  join(LEGACY_EXAMPLES_DIR, "claude-code"),
21
19
  ];
22
20
  }
@@ -21,6 +21,12 @@ export const INSTALLED_PROGRESS = join(SKILL_ROOT, "progress.md");
21
21
  export const AGENT_INSTRUCTIONS_FILE = "AGENTS.md";
22
22
  export const LEGACY_AGENT_INSTRUCTIONS_FILE = "AGENT.md";
23
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;
24
30
 
25
31
  export const CLAUDE_SKILL_AGENTS_DIR = join(CLAUDE_SKILL_ROOT, "agents");
26
32
  export const CLAUDE_SKILL_REFERENCES_DIR = join(CLAUDE_SKILL_ROOT, "references");
@@ -109,32 +115,6 @@ export const LEGACY_REPO_SKILL_PROGRESS = join(
109
115
  "progress.md",
110
116
  );
111
117
 
112
- export const LEGACY_SKILL_NAME = "first-tree-cli-framework";
113
- export const LEGACY_SKILL_ROOT = join("skills", LEGACY_SKILL_NAME);
114
- export const LEGACY_SKILL_ASSET_ROOT = join(
115
- LEGACY_SKILL_ROOT,
116
- "assets",
117
- "framework",
118
- );
119
- export const LEGACY_SKILL_VERSION = join(LEGACY_SKILL_ASSET_ROOT, "VERSION");
120
- export const LEGACY_SKILL_TEMPLATES_DIR = join(
121
- LEGACY_SKILL_ASSET_ROOT,
122
- "templates",
123
- );
124
- export const LEGACY_SKILL_WORKFLOWS_DIR = join(
125
- LEGACY_SKILL_ASSET_ROOT,
126
- "workflows",
127
- );
128
- export const LEGACY_SKILL_PROMPTS_DIR = join(
129
- LEGACY_SKILL_ASSET_ROOT,
130
- "prompts",
131
- );
132
- export const LEGACY_SKILL_EXAMPLES_DIR = join(
133
- LEGACY_SKILL_ASSET_ROOT,
134
- "examples",
135
- );
136
- export const LEGACY_SKILL_PROGRESS = join(LEGACY_SKILL_ROOT, "progress.md");
137
-
138
118
  export const LEGACY_FRAMEWORK_ROOT = ".context-tree";
139
119
  export const LEGACY_VERSION = join(LEGACY_FRAMEWORK_ROOT, "VERSION");
140
120
  export const LEGACY_PROGRESS = join(LEGACY_FRAMEWORK_ROOT, "progress.md");
@@ -147,7 +127,6 @@ export type FrameworkLayout =
147
127
  | "skill"
148
128
  | "claude-skill"
149
129
  | "legacy-repo-skill"
150
- | "legacy-skill"
151
130
  | "legacy";
152
131
 
153
132
  function pathExists(root: string, relPath: string): boolean {
@@ -174,7 +153,6 @@ export function frameworkVersionCandidates(): string[] {
174
153
  FRAMEWORK_VERSION,
175
154
  CLAUDE_FRAMEWORK_VERSION,
176
155
  LEGACY_REPO_SKILL_VERSION,
177
- LEGACY_SKILL_VERSION,
178
156
  LEGACY_VERSION,
179
157
  ];
180
158
  }
@@ -184,7 +162,6 @@ export function progressFileCandidates(): string[] {
184
162
  INSTALLED_PROGRESS,
185
163
  CLAUDE_INSTALLED_PROGRESS,
186
164
  LEGACY_REPO_SKILL_PROGRESS,
187
- LEGACY_SKILL_PROGRESS,
188
165
  LEGACY_PROGRESS,
189
166
  ];
190
167
  }
@@ -198,7 +175,6 @@ export function frameworkTemplateDirCandidates(): string[] {
198
175
  FRAMEWORK_TEMPLATES_DIR,
199
176
  CLAUDE_FRAMEWORK_TEMPLATES_DIR,
200
177
  LEGACY_REPO_SKILL_TEMPLATES_DIR,
201
- LEGACY_SKILL_TEMPLATES_DIR,
202
178
  LEGACY_TEMPLATES_DIR,
203
179
  ];
204
180
  }
@@ -208,7 +184,6 @@ export function frameworkWorkflowDirCandidates(): string[] {
208
184
  FRAMEWORK_WORKFLOWS_DIR,
209
185
  CLAUDE_FRAMEWORK_WORKFLOWS_DIR,
210
186
  LEGACY_REPO_SKILL_WORKFLOWS_DIR,
211
- LEGACY_SKILL_WORKFLOWS_DIR,
212
187
  LEGACY_WORKFLOWS_DIR,
213
188
  ];
214
189
  }
@@ -218,7 +193,6 @@ export function frameworkPromptDirCandidates(): string[] {
218
193
  FRAMEWORK_PROMPTS_DIR,
219
194
  CLAUDE_FRAMEWORK_PROMPTS_DIR,
220
195
  LEGACY_REPO_SKILL_PROMPTS_DIR,
221
- LEGACY_SKILL_PROMPTS_DIR,
222
196
  LEGACY_PROMPTS_DIR,
223
197
  ];
224
198
  }
@@ -228,7 +202,6 @@ export function frameworkExampleDirCandidates(): string[] {
228
202
  FRAMEWORK_EXAMPLES_DIR,
229
203
  CLAUDE_FRAMEWORK_EXAMPLES_DIR,
230
204
  LEGACY_REPO_SKILL_EXAMPLES_DIR,
231
- LEGACY_SKILL_EXAMPLES_DIR,
232
205
  LEGACY_EXAMPLES_DIR,
233
206
  ];
234
207
  }
@@ -255,9 +228,6 @@ export function detectFrameworkLayout(root: string): FrameworkLayout | null {
255
228
  if (pathExists(root, LEGACY_REPO_SKILL_VERSION)) {
256
229
  return "legacy-repo-skill";
257
230
  }
258
- if (pathExists(root, LEGACY_SKILL_VERSION)) {
259
- return "legacy-skill";
260
- }
261
231
  if (pathExists(root, LEGACY_VERSION)) {
262
232
  return "legacy";
263
233
  }
@@ -5,7 +5,6 @@ import {
5
5
  BUNDLED_SKILL_ROOT,
6
6
  INSTALLED_SKILL_ROOTS,
7
7
  LEGACY_REPO_SKILL_ROOT,
8
- LEGACY_SKILL_ROOT,
9
8
  } from "#skill/engine/runtime/asset-loader.js";
10
9
 
11
10
  export function resolveBundledPackageRoot(startUrl = import.meta.url): string {
@@ -57,7 +56,6 @@ export function copyCanonicalSkill(sourceRoot: string, targetRoot: string): void
57
56
  for (const relPath of [
58
57
  ...INSTALLED_SKILL_ROOTS,
59
58
  LEGACY_REPO_SKILL_ROOT,
60
- LEGACY_SKILL_ROOT,
61
59
  ]) {
62
60
  const fullPath = join(targetRoot, relPath);
63
61
  if (existsSync(fullPath)) {
@@ -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,6 +4,7 @@ import { Repo } from "#skill/engine/repo.js";
4
4
  import {
5
5
  AGENT_INSTRUCTIONS_FILE,
6
6
  AGENT_INSTRUCTIONS_TEMPLATE,
7
+ CLAUDE_INSTRUCTIONS_FILE,
7
8
  CLAUDE_SKILL_ROOT,
8
9
  FRAMEWORK_WORKFLOWS_DIR,
9
10
  FRAMEWORK_TEMPLATES_DIR,
@@ -12,8 +13,8 @@ import {
12
13
  LEGACY_AGENT_INSTRUCTIONS_FILE,
13
14
  LEGACY_FRAMEWORK_ROOT,
14
15
  LEGACY_REPO_SKILL_ROOT,
15
- LEGACY_SKILL_ROOT,
16
16
  SKILL_ROOT,
17
+ SOURCE_INTEGRATION_MARKER,
17
18
  installedSkillRootsDisplay,
18
19
  type FrameworkLayout,
19
20
  } from "#skill/engine/runtime/asset-loader.js";
@@ -21,6 +22,7 @@ import {
21
22
  copyCanonicalSkill,
22
23
  resolveBundledPackageRoot,
23
24
  } from "#skill/engine/runtime/installer.js";
25
+ import { upsertSourceIntegrationFiles } from "#skill/engine/runtime/source-integration.js";
24
26
  import {
25
27
  compareFrameworkVersions,
26
28
  readSourceVersion,
@@ -70,12 +72,6 @@ function formatUpgradeTaskList(
70
72
  );
71
73
  }
72
74
 
73
- if (layout === "legacy-skill") {
74
- migrationTasks.push(
75
- `- [ ] Remove any stale \`${LEGACY_SKILL_ROOT}/\` references from repo-specific docs, scripts, workflow files, or agent config`,
76
- );
77
- }
78
-
79
75
  if (repo.hasCanonicalAgentInstructionsFile() && repo.hasLegacyAgentInstructionsFile()) {
80
76
  migrationTasks.push(
81
77
  `- [ ] Merge any remaining user-authored content from \`${LEGACY_AGENT_INSTRUCTIONS_FILE}\` into \`${AGENT_INSTRUCTIONS_FILE}\`, then delete the legacy file`,
@@ -121,8 +117,10 @@ export interface UpgradeOptions {
121
117
 
122
118
  export function runUpgrade(repo?: Repo, options?: UpgradeOptions): number {
123
119
  const workingRepo = repo ?? new Repo();
120
+ const workspaceOnlyIntegration =
121
+ workingRepo.hasSourceWorkspaceIntegration() && !workingRepo.looksLikeTreeRepo();
124
122
 
125
- if (workingRepo.isLikelySourceRepo() && !workingRepo.looksLikeTreeRepo()) {
123
+ if (workingRepo.isLikelySourceRepo() && !workingRepo.looksLikeTreeRepo() && !workspaceOnlyIntegration) {
126
124
  console.error(
127
125
  "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.",
128
126
  );
@@ -178,6 +176,68 @@ export function runUpgrade(repo?: Repo, options?: UpgradeOptions): number {
178
176
  }
179
177
 
180
178
  const missingInstalledRoots = workingRepo.missingInstalledSkillRoots();
179
+ const sourceRepoTreePathHint = `../${workingRepo.repoName()}-context`;
180
+
181
+ if (workspaceOnlyIntegration) {
182
+ if (
183
+ layout === "skill" &&
184
+ missingInstalledRoots.length === 0 &&
185
+ packagedVersion === localVersion
186
+ ) {
187
+ const updates = upsertSourceIntegrationFiles(
188
+ workingRepo.root,
189
+ `${workingRepo.repoName()}-context`,
190
+ );
191
+ const changedFiles = updates
192
+ .filter((update) => update.action !== "unchanged")
193
+ .map((update) => update.file);
194
+ if (changedFiles.length === 0) {
195
+ console.log(
196
+ `Already up to date with the bundled skill (${FRAMEWORK_VERSION} = ${localVersion}).`,
197
+ );
198
+ console.log(
199
+ `This repo only carries source/workspace integration. Upgrade the dedicated tree repo separately with \`context-tree upgrade --tree-path ${sourceRepoTreePathHint}\`.`,
200
+ );
201
+ return 0;
202
+ }
203
+ console.log(
204
+ `Already up to date with the bundled skill (${FRAMEWORK_VERSION} = ${localVersion}).`,
205
+ );
206
+ console.log(
207
+ `Updated the ${SOURCE_INTEGRATION_MARKER} marker lines in ${changedFiles.map((file) => `\`${file}\``).join(" and ")}.`,
208
+ );
209
+ console.log(
210
+ `This repo only carries source/workspace integration. Upgrade the dedicated tree repo separately with \`context-tree upgrade --tree-path ${sourceRepoTreePathHint}\`.`,
211
+ );
212
+ return 0;
213
+ }
214
+
215
+ copyCanonicalSkill(sourceRoot, workingRepo.root);
216
+ const updates = upsertSourceIntegrationFiles(
217
+ workingRepo.root,
218
+ `${workingRepo.repoName()}-context`,
219
+ );
220
+ const changedFiles = updates
221
+ .filter((update) => update.action !== "unchanged")
222
+ .map((update) => update.file);
223
+ console.log(
224
+ `Refreshed ${installedSkillRootsDisplay()} in this source/workspace repo.`,
225
+ );
226
+ if (changedFiles.length > 0) {
227
+ console.log(
228
+ `Updated the ${SOURCE_INTEGRATION_MARKER} marker lines in ${changedFiles.map((file) => `\`${file}\``).join(" and ")}.`,
229
+ );
230
+ } else {
231
+ console.log(
232
+ `The ${SOURCE_INTEGRATION_MARKER} marker lines in ${AGENT_INSTRUCTIONS_FILE} and ${CLAUDE_INSTRUCTIONS_FILE} were already current.`,
233
+ );
234
+ }
235
+ console.log(
236
+ `This repo is not the Context Tree. Upgrade the dedicated tree repo separately with \`context-tree upgrade --tree-path ${sourceRepoTreePathHint}\`.`,
237
+ );
238
+ return 0;
239
+ }
240
+
181
241
  if (
182
242
  layout === "skill" &&
183
243
  missingInstalledRoots.length === 0 &&
@@ -202,10 +262,6 @@ export function runUpgrade(repo?: Repo, options?: UpgradeOptions): number {
202
262
  console.log(
203
263
  `Migrated legacy ${LEGACY_REPO_SKILL_ROOT}/ layout to ${installedSkillRootsDisplay()}.`,
204
264
  );
205
- } else if (layout === "legacy-skill") {
206
- console.log(
207
- `Migrated ${LEGACY_SKILL_ROOT}/ to ${installedSkillRootsDisplay()}.`,
208
- );
209
265
  } else {
210
266
  if (missingInstalledRoots.length > 0) {
211
267
  console.log(