first-tree 0.0.4 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +17 -8
  2. package/dist/cli.js +4 -4
  3. package/dist/{help-Dtdj91HJ.js → help-5-WG9QFm.js} +1 -1
  4. package/dist/{init--VepFe6N.js → init-CAq0Uhq6.js} +19 -7
  5. package/dist/{installer-cH7N4RNj.js → installer-UgNasLjl.js} +19 -15
  6. package/dist/onboarding-3zYUeYQb.js +2 -0
  7. package/dist/onboarding-Dd63N-V1.js +10 -0
  8. package/dist/{repo-DY57bMqr.js → repo-DkR12VUv.js} +55 -4
  9. package/dist/{upgrade-Cgx_K2HM.js → upgrade-DYzuvv1k.js} +13 -8
  10. package/dist/{verify-mC9ZTd1f.js → verify-C0IUSkMZ.js} +1 -1
  11. package/package.json +12 -10
  12. package/skills/first-tree/SKILL.md +10 -6
  13. package/skills/first-tree/assets/framework/VERSION +1 -1
  14. package/skills/first-tree/assets/framework/examples/claude-code/README.md +2 -2
  15. package/skills/first-tree/assets/framework/examples/claude-code/settings.json +1 -1
  16. package/skills/first-tree/assets/framework/helpers/generate-codeowners.ts +1 -1
  17. package/skills/first-tree/assets/framework/helpers/inject-tree-context.sh +0 -0
  18. package/skills/first-tree/assets/framework/helpers/run-review.ts +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 +3 -2
  25. package/skills/first-tree/engine/repo.ts +36 -3
  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 +136 -4
  30. package/skills/first-tree/engine/runtime/installer.ts +18 -12
  31. package/skills/first-tree/engine/upgrade.ts +35 -8
  32. package/skills/first-tree/references/onboarding.md +28 -18
  33. package/skills/first-tree/references/upgrade-contract.md +49 -23
  34. package/skills/first-tree/scripts/check-skill-sync.sh +0 -0
  35. package/skills/first-tree/scripts/quick_validate.py +0 -0
  36. package/skills/first-tree/scripts/run-local-cli.sh +0 -0
  37. package/skills/first-tree/tests/asset-loader.test.ts +23 -1
  38. package/skills/first-tree/tests/helpers.ts +27 -3
  39. package/skills/first-tree/tests/init.test.ts +11 -3
  40. package/skills/first-tree/tests/repo.test.ts +26 -0
  41. package/skills/first-tree/tests/rules.test.ts +9 -7
  42. package/skills/first-tree/tests/skill-artifacts.test.ts +6 -0
  43. package/skills/first-tree/tests/upgrade.test.ts +20 -1
  44. package/skills/first-tree/tests/verify.test.ts +3 -3
  45. package/dist/onboarding-C9cYSE6F.js +0 -2
  46. package/dist/onboarding-CPP8fF4D.js +0 -10
@@ -1,8 +1,8 @@
1
1
  # Upgrade Contract
2
2
 
3
3
  This file describes the current installed-layout contract and the compatibility
4
- rules we keep for legacy `skills/first-tree-cli-framework/` and
5
- `.context-tree/` repos.
4
+ rules we keep for legacy `skills/first-tree/`,
5
+ `skills/first-tree-cli-framework/`, and `.context-tree/` repos.
6
6
 
7
7
  ## Canonical Source
8
8
 
@@ -19,20 +19,35 @@ rules we keep for legacy `skills/first-tree-cli-framework/` and
19
19
  The current installed layout in a user repo is:
20
20
 
21
21
  ```text
22
- skills/
23
- first-tree/
24
- SKILL.md
25
- progress.md
26
- references/
27
- assets/
28
- framework/
29
- manifest.json
30
- VERSION
31
- templates/
32
- workflows/
33
- prompts/
34
- examples/
35
- helpers/
22
+ .agents/
23
+ skills/
24
+ first-tree/
25
+ SKILL.md
26
+ progress.md
27
+ references/
28
+ assets/
29
+ framework/
30
+ manifest.json
31
+ VERSION
32
+ templates/
33
+ workflows/
34
+ prompts/
35
+ examples/
36
+ helpers/
37
+ .claude/
38
+ skills/
39
+ first-tree/
40
+ SKILL.md
41
+ references/
42
+ assets/
43
+ framework/
44
+ manifest.json
45
+ VERSION
46
+ templates/
47
+ workflows/
48
+ prompts/
49
+ examples/
50
+ helpers/
36
51
  ```
37
52
 
38
53
  The tree content still lives outside the skill:
@@ -41,6 +56,11 @@ The tree content still lives outside the skill:
41
56
  - `AGENTS.md`
42
57
  - `members/`
43
58
 
59
+ The repo-owned `.agents/skills/first-tree/` path is the primary installed root
60
+ for progress state, workflow references, and helper scripts. The matching
61
+ `.claude/skills/first-tree/` path mirrors the same payload for Claude-facing
62
+ skill discovery and hooks.
63
+
44
64
  ## Command Intent
45
65
 
46
66
  - `context-tree init`
@@ -48,7 +68,7 @@ The tree content still lives outside the skill:
48
68
  tree repo by default
49
69
  - installs the skill into the target tree repo
50
70
  - renders top-level tree scaffolding from the skill templates
51
- - writes progress state to `skills/first-tree/progress.md`
71
+ - writes progress state to `.agents/skills/first-tree/progress.md`
52
72
  - `context-tree verify`
53
73
  - checks progress state from the installed skill
54
74
  - validates root/frontmatter/agent markers
@@ -57,8 +77,11 @@ The tree content still lives outside the skill:
57
77
  - compares the installed skill payload version to the skill bundled with the
58
78
  currently running `first-tree` package
59
79
  - refreshes the installed skill payload without overwriting tree content
80
+ - migrates repos that still use the previous `skills/first-tree/` path onto
81
+ `.agents/skills/first-tree/` and `.claude/skills/first-tree/`
60
82
  - migrates repos that still use the previous
61
- `skills/first-tree-cli-framework/` path onto `skills/first-tree/`
83
+ `skills/first-tree-cli-framework/` path onto `.agents/skills/first-tree/`
84
+ and `.claude/skills/first-tree/`
62
85
  - migrates legacy `.context-tree/` repos onto the installed skill layout
63
86
  - preserves user-authored sections such as the editable part of `AGENTS.md`
64
87
 
@@ -73,14 +96,17 @@ The tree content still lives outside the skill:
73
96
  - Normal `context-tree init` and `context-tree upgrade` flows do not clone the
74
97
  source repo or require network access.
75
98
  - `context-tree verify` may still read a legacy
76
- `skills/first-tree-cli-framework/...` or `.context-tree/...` layout in an
77
- existing user repo so the repo can be upgraded in place.
99
+ `.claude/skills/first-tree/...`, `skills/first-tree/...`,
100
+ `skills/first-tree-cli-framework/...`, or `.context-tree/...` layout in an
101
+ existing user repo so the repo can be repaired or upgraded in place.
78
102
  - `context-tree upgrade` must migrate either legacy layout onto
79
- `skills/first-tree/` and remove the old directory afterward.
80
- - When both layouts are present, prefer the installed skill layout.
103
+ `.agents/skills/first-tree/` and `.claude/skills/first-tree/`, and remove
104
+ old skill directories afterward.
105
+ - When both current and legacy layouts are present, prefer the
106
+ `.agents/skills/first-tree/` layout.
81
107
  - Existing repos may still have a legacy `AGENT.md`; `init` and `upgrade`
82
108
  must not silently overwrite it, and follow-up tasks should direct users to
83
- rename it to `AGENTS.md`.
109
+ rename or merge it into `AGENTS.md`.
84
110
 
85
111
  ## Invariants
86
112
 
File without changes
File without changes
File without changes
@@ -2,8 +2,11 @@ import { mkdirSync, writeFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { describe, expect, it } from "vitest";
4
4
  import {
5
+ CLAUDE_INSTALLED_PROGRESS,
5
6
  FRAMEWORK_VERSION,
6
7
  INSTALLED_PROGRESS,
8
+ LEGACY_REPO_SKILL_PROGRESS,
9
+ LEGACY_REPO_SKILL_VERSION,
7
10
  LEGACY_SKILL_PROGRESS,
8
11
  LEGACY_SKILL_VERSION,
9
12
  LEGACY_PROGRESS,
@@ -18,7 +21,7 @@ import { useTmpDir } from "./helpers.js";
18
21
  describe("asset-loader", () => {
19
22
  it("prefers the installed skill layout when both layouts exist", () => {
20
23
  const tmp = useTmpDir();
21
- mkdirSync(join(tmp.path, "skills", "first-tree", "assets", "framework"), {
24
+ mkdirSync(join(tmp.path, ".agents", "skills", "first-tree", "assets", "framework"), {
22
25
  recursive: true,
23
26
  });
24
27
  mkdirSync(join(tmp.path, ".context-tree"), { recursive: true });
@@ -39,6 +42,21 @@ describe("asset-loader", () => {
39
42
  expect(detectFrameworkLayout(tmp.path)).toBe("legacy");
40
43
  });
41
44
 
45
+ it("detects the previous workspace skill path before older layouts", () => {
46
+ const tmp = useTmpDir();
47
+ mkdirSync(join(tmp.path, "skills", "first-tree", "assets", "framework"), {
48
+ recursive: true,
49
+ });
50
+ mkdirSync(join(tmp.path, ".context-tree"), { recursive: true });
51
+ writeFileSync(join(tmp.path, LEGACY_REPO_SKILL_VERSION), "0.2.0\n");
52
+ writeFileSync(join(tmp.path, LEGACY_VERSION), "0.1.0\n");
53
+
54
+ expect(detectFrameworkLayout(tmp.path)).toBe("legacy-repo-skill");
55
+ expect(
56
+ resolveFirstExistingPath(tmp.path, frameworkVersionCandidates()),
57
+ ).toBe(LEGACY_REPO_SKILL_VERSION);
58
+ });
59
+
42
60
  it("detects the previous installed skill name before the .context-tree layout", () => {
43
61
  const tmp = useTmpDir();
44
62
  mkdirSync(
@@ -59,12 +77,16 @@ describe("asset-loader", () => {
59
77
 
60
78
  it("prefers the installed progress file candidate", () => {
61
79
  const tmp = useTmpDir();
80
+ mkdirSync(join(tmp.path, ".agents", "skills", "first-tree"), { recursive: true });
81
+ mkdirSync(join(tmp.path, ".claude", "skills", "first-tree"), { recursive: true });
62
82
  mkdirSync(join(tmp.path, "skills", "first-tree"), { recursive: true });
63
83
  mkdirSync(join(tmp.path, "skills", "first-tree-cli-framework"), {
64
84
  recursive: true,
65
85
  });
66
86
  mkdirSync(join(tmp.path, ".context-tree"), { recursive: true });
67
87
  writeFileSync(join(tmp.path, INSTALLED_PROGRESS), "new");
88
+ writeFileSync(join(tmp.path, CLAUDE_INSTALLED_PROGRESS), "claude");
89
+ writeFileSync(join(tmp.path, LEGACY_REPO_SKILL_PROGRESS), "old-repo-skill");
68
90
  writeFileSync(join(tmp.path, LEGACY_SKILL_PROGRESS), "old-skill");
69
91
  writeFileSync(join(tmp.path, LEGACY_PROGRESS), "old");
70
92
 
@@ -5,10 +5,13 @@ import { afterEach } from "vitest";
5
5
  import {
6
6
  AGENT_INSTRUCTIONS_FILE,
7
7
  AGENT_INSTRUCTIONS_TEMPLATE,
8
+ CLAUDE_SKILL_ROOT,
8
9
  FRAMEWORK_VERSION,
9
10
  LEGACY_AGENT_INSTRUCTIONS_FILE,
11
+ LEGACY_REPO_SKILL_VERSION,
10
12
  LEGACY_SKILL_VERSION,
11
13
  LEGACY_VERSION,
14
+ SKILL_ROOT,
12
15
  } from "#skill/engine/runtime/asset-loader.js";
13
16
 
14
17
  interface TmpDir {
@@ -24,10 +27,20 @@ export function useTmpDir(): TmpDir {
24
27
  }
25
28
 
26
29
  export function makeFramework(root: string, version = "0.1.0"): void {
27
- mkdirSync(join(root, "skills", "first-tree", "assets", "framework"), {
28
- recursive: true,
29
- });
30
+ for (const skillRoot of [SKILL_ROOT, CLAUDE_SKILL_ROOT]) {
31
+ mkdirSync(join(root, skillRoot, "assets", "framework"), {
32
+ recursive: true,
33
+ });
34
+ writeFileSync(
35
+ join(root, skillRoot, "SKILL.md"),
36
+ "---\nname: first-tree\ndescription: installed\n---\n",
37
+ );
38
+ }
30
39
  writeFileSync(join(root, FRAMEWORK_VERSION), `${version}\n`);
40
+ writeFileSync(
41
+ join(root, CLAUDE_SKILL_ROOT, "assets", "framework", "VERSION"),
42
+ `${version}\n`,
43
+ );
31
44
  }
32
45
 
33
46
  export function makeGitRepo(root: string): void {
@@ -50,6 +63,17 @@ export function makeLegacyFramework(root: string, version = "0.1.0"): void {
50
63
  writeFileSync(join(root, LEGACY_VERSION), `${version}\n`);
51
64
  }
52
65
 
66
+ export function makeLegacyRepoFramework(root: string, version = "0.1.0"): void {
67
+ mkdirSync(join(root, "skills", "first-tree", "assets", "framework"), {
68
+ recursive: true,
69
+ });
70
+ writeFileSync(
71
+ join(root, "skills", "first-tree", "SKILL.md"),
72
+ "---\nname: first-tree\ndescription: legacy installed\n---\n",
73
+ );
74
+ writeFileSync(join(root, LEGACY_REPO_SKILL_VERSION), `${version}\n`);
75
+ }
76
+
53
77
  export function makeLegacyNamedFramework(
54
78
  root: string,
55
79
  version = "0.1.0",
@@ -100,7 +100,7 @@ describe("writeProgress", () => {
100
100
 
101
101
  it("overwrites existing file", () => {
102
102
  const tmp = useTmpDir();
103
- mkdirSync(join(tmp.path, "skills", "first-tree"), {
103
+ mkdirSync(join(tmp.path, ".agents", "skills", "first-tree"), {
104
104
  recursive: true,
105
105
  });
106
106
  writeFileSync(join(tmp.path, INSTALLED_PROGRESS), "old");
@@ -145,7 +145,10 @@ describe("runInit", () => {
145
145
 
146
146
  expect(ret).toBe(0);
147
147
  expect(
148
- existsSync(join(repoDir.path, "skills", "first-tree", "SKILL.md")),
148
+ existsSync(join(repoDir.path, ".agents", "skills", "first-tree", "SKILL.md")),
149
+ ).toBe(true);
150
+ expect(
151
+ existsSync(join(repoDir.path, ".claude", "skills", "first-tree", "SKILL.md")),
149
152
  ).toBe(true);
150
153
  expect(readFileSync(join(repoDir.path, FRAMEWORK_VERSION), "utf-8").trim()).toBe("0.2.0");
151
154
  expect(existsSync(join(repoDir.path, "NODE.md"))).toBe(true);
@@ -202,7 +205,12 @@ describe("runInit", () => {
202
205
  );
203
206
 
204
207
  expect(ret).toBe(0);
205
- expect(existsSync(join(treeRepo, "skills", "first-tree", "SKILL.md"))).toBe(true);
208
+ expect(
209
+ existsSync(join(treeRepo, ".agents", "skills", "first-tree", "SKILL.md")),
210
+ ).toBe(true);
211
+ expect(
212
+ existsSync(join(treeRepo, ".claude", "skills", "first-tree", "SKILL.md")),
213
+ ).toBe(true);
206
214
  expect(existsSync(join(treeRepo, "NODE.md"))).toBe(true);
207
215
  expect(existsSync(join(treeRepo, AGENT_INSTRUCTIONS_FILE))).toBe(true);
208
216
  expect(existsSync(join(treeRepo, "members", "NODE.md"))).toBe(true);
@@ -4,9 +4,12 @@ import { describe, expect, it } from "vitest";
4
4
  import { Repo } from "#skill/engine/repo.js";
5
5
  import {
6
6
  AGENT_INSTRUCTIONS_FILE,
7
+ CLAUDE_INSTALLED_PROGRESS,
7
8
  FRAMEWORK_VERSION,
8
9
  INSTALLED_PROGRESS,
9
10
  LEGACY_AGENT_INSTRUCTIONS_FILE,
11
+ LEGACY_REPO_SKILL_PROGRESS,
12
+ LEGACY_REPO_SKILL_VERSION,
10
13
  LEGACY_SKILL_PROGRESS,
11
14
  LEGACY_SKILL_VERSION,
12
15
  LEGACY_PROGRESS,
@@ -17,6 +20,7 @@ import {
17
20
  makeFramework,
18
21
  makeGitRepo,
19
22
  makeLegacyFramework,
23
+ makeLegacyRepoFramework,
20
24
  makeLegacyNamedFramework,
21
25
  makeSourceRepo,
22
26
  makeSourceSkill,
@@ -182,6 +186,13 @@ describe("hasFramework", () => {
182
186
  expect(repo.hasFramework()).toBe(true);
183
187
  });
184
188
 
189
+ it("returns true with the previous workspace skill path", () => {
190
+ const tmp = useTmpDir();
191
+ makeLegacyRepoFramework(tmp.path);
192
+ const repo = new Repo(tmp.path);
193
+ expect(repo.hasFramework()).toBe(true);
194
+ });
195
+
185
196
  it("returns false without VERSION file", () => {
186
197
  const tmp = useTmpDir();
187
198
  const repo = new Repo(tmp.path);
@@ -213,6 +224,13 @@ describe("readVersion", () => {
213
224
  expect(repo.readVersion()).toBe("0.2.5");
214
225
  });
215
226
 
227
+ it("reads the previous workspace skill version", () => {
228
+ const tmp = useTmpDir();
229
+ makeLegacyRepoFramework(tmp.path, "0.2.4");
230
+ const repo = new Repo(tmp.path);
231
+ expect(repo.readVersion()).toBe("0.2.4");
232
+ });
233
+
216
234
  it("returns null when missing", () => {
217
235
  const tmp = useTmpDir();
218
236
  const repo = new Repo(tmp.path);
@@ -245,6 +263,14 @@ describe("path preferences", () => {
245
263
  expect(repo.preferredProgressPath()).toBe(LEGACY_SKILL_PROGRESS);
246
264
  expect(repo.frameworkVersionPath()).toBe(LEGACY_SKILL_VERSION);
247
265
  });
266
+
267
+ it("switches path preferences for repos using the previous workspace skill path", () => {
268
+ const tmp = useTmpDir();
269
+ makeLegacyRepoFramework(tmp.path);
270
+ const repo = new Repo(tmp.path);
271
+ expect(repo.preferredProgressPath()).toBe(LEGACY_REPO_SKILL_PROGRESS);
272
+ expect(repo.frameworkVersionPath()).toBe(LEGACY_REPO_SKILL_VERSION);
273
+ });
248
274
  });
249
275
 
250
276
  // --- agent instructions helpers ---
@@ -29,7 +29,7 @@ describe("framework rule", () => {
29
29
  const result = framework.evaluate(repo);
30
30
  expect(result.group).toBe("Framework");
31
31
  expect(result.tasks).toHaveLength(1);
32
- expect(result.tasks[0]).toContain("skills/first-tree/");
32
+ expect(result.tasks[0]).toContain(".agents/skills/first-tree/");
33
33
  });
34
34
 
35
35
  it("passes when framework exists", () => {
@@ -233,7 +233,9 @@ describe("ciValidation rule", () => {
233
233
  const result = ciValidation.evaluate(repo);
234
234
  expect(result.tasks).toHaveLength(4);
235
235
  expect(result.tasks[0]).toContain("validation workflow");
236
- expect(result.tasks[0]).toContain("skills/first-tree/assets/framework/workflows/validate.yml");
236
+ expect(result.tasks[0]).toContain(
237
+ ".agents/skills/first-tree/assets/framework/workflows/validate.yml",
238
+ );
237
239
  expect(result.tasks[1]).toContain("PR reviews");
238
240
  expect(result.tasks[2]).toContain("API secret");
239
241
  expect(result.tasks[3]).toContain("CODEOWNERS");
@@ -271,7 +273,7 @@ describe("ciValidation rule", () => {
271
273
  mkdirSync(wfDir, { recursive: true });
272
274
  writeFileSync(
273
275
  join(wfDir, "pr-review.yml"),
274
- "name: PR Review\non: pull_request\njobs:\n review:\n steps:\n - run: npx tsx skills/first-tree/assets/framework/helpers/run-review.ts\n",
276
+ "name: PR Review\non: pull_request\njobs:\n review:\n steps:\n - run: npx tsx .agents/skills/first-tree/assets/framework/helpers/run-review.ts\n",
275
277
  );
276
278
  const repo = new Repo(tmp.path);
277
279
  const result = ciValidation.evaluate(repo);
@@ -290,7 +292,7 @@ describe("ciValidation rule", () => {
290
292
  );
291
293
  writeFileSync(
292
294
  join(wfDir, "pr-review.yml"),
293
- "name: PR Review\non: pull_request\njobs:\n review:\n steps:\n - run: npx tsx skills/first-tree/assets/framework/helpers/run-review.ts\n",
295
+ "name: PR Review\non: pull_request\njobs:\n review:\n steps:\n - run: npx tsx .agents/skills/first-tree/assets/framework/helpers/run-review.ts\n",
294
296
  );
295
297
  const repo = new Repo(tmp.path);
296
298
  const result = ciValidation.evaluate(repo);
@@ -308,11 +310,11 @@ describe("ciValidation rule", () => {
308
310
  );
309
311
  writeFileSync(
310
312
  join(wfDir, "pr-review.yml"),
311
- "name: PR Review\non: pull_request\njobs:\n review:\n steps:\n - run: npx tsx skills/first-tree/assets/framework/helpers/run-review.ts\n",
313
+ "name: PR Review\non: pull_request\njobs:\n review:\n steps:\n - run: npx tsx .agents/skills/first-tree/assets/framework/helpers/run-review.ts\n",
312
314
  );
313
315
  writeFileSync(
314
316
  join(wfDir, "codeowners.yml"),
315
- "name: Update CODEOWNERS\non: pull_request\njobs:\n update:\n steps:\n - run: npx tsx skills/first-tree/assets/framework/helpers/generate-codeowners.ts\n",
317
+ "name: Update CODEOWNERS\non: pull_request\njobs:\n update:\n steps:\n - run: npx tsx .agents/skills/first-tree/assets/framework/helpers/generate-codeowners.ts\n",
316
318
  );
317
319
  const repo = new Repo(tmp.path);
318
320
  const result = ciValidation.evaluate(repo);
@@ -386,7 +388,7 @@ describe("evaluateAll", () => {
386
388
  makeNode(tmp.path);
387
389
  makeAgentsMd(tmp.path, { markers: true, userContent: true });
388
390
  makeMembers(tmp.path, 1);
389
- mkdirSync(join(tmp.path, ".claude"));
391
+ mkdirSync(join(tmp.path, ".claude"), { recursive: true });
390
392
  writeFileSync(
391
393
  join(tmp.path, ".claude", "settings.json"),
392
394
  '{"hooks": {"inject-tree-context": true}}',
@@ -201,6 +201,8 @@ describe("skill artifacts", () => {
201
201
  expect(read("README.md")).toContain("Canonical Documentation");
202
202
  expect(read("README.md")).toContain("references/source-map.md");
203
203
  expect(read("README.md")).toContain("skills/first-tree/");
204
+ expect(read("README.md")).toContain(".agents/skills/first-tree/");
205
+ expect(read("README.md")).toContain(".claude/skills/first-tree/");
204
206
  expect(read("README.md")).toContain("bundled canonical");
205
207
  expect(read("README.md")).toContain("dedicated tree repo");
206
208
  expect(read("README.md")).toContain("`first-tree` skill");
@@ -218,6 +220,8 @@ describe("skill artifacts", () => {
218
220
  expect(onboarding).toContain("installed CLI command is");
219
221
  expect(onboarding).toContain("currently running `first-tree` npm package");
220
222
  expect(onboarding).toContain("npx first-tree@latest upgrade");
223
+ expect(onboarding).toContain(".agents/skills/first-tree/");
224
+ expect(onboarding).toContain(".claude/skills/first-tree/");
221
225
  expect(onboarding).not.toContain("This clones the framework into `.context-tree/`");
222
226
  expect(onboarding).not.toContain("from upstream");
223
227
 
@@ -228,6 +232,8 @@ describe("skill artifacts", () => {
228
232
  expect(skillMd).toContain("maintainer-testing.md");
229
233
  expect(skillMd).toContain("currently running `first-tree` package");
230
234
  expect(skillMd).toContain("so it is not confused with the `first-tree`");
235
+ expect(skillMd).toContain(".agents/skills/first-tree/");
236
+ expect(skillMd).toContain(".claude/skills/first-tree/");
231
237
  expect(skillMd).not.toContain("canonical eval harness");
232
238
 
233
239
  const sourceMap = read("skills/first-tree/references/source-map.md");
@@ -14,6 +14,7 @@ import {
14
14
  makeFramework,
15
15
  makeSourceRepo,
16
16
  makeLegacyFramework,
17
+ makeLegacyRepoFramework,
17
18
  makeLegacyNamedFramework,
18
19
  makeSourceSkill,
19
20
  useTmpDir,
@@ -35,7 +36,7 @@ describe("runUpgrade", () => {
35
36
  expect(existsSync(join(repoDir.path, ".context-tree"))).toBe(false);
36
37
  expect(readFileSync(join(repoDir.path, FRAMEWORK_VERSION), "utf-8").trim()).toBe("0.2.0");
37
38
  expect(readFileSync(join(repoDir.path, INSTALLED_PROGRESS), "utf-8")).toContain(
38
- "skills/first-tree/assets/framework/VERSION",
39
+ ".agents/skills/first-tree/assets/framework/VERSION",
39
40
  );
40
41
  expect(readFileSync(join(repoDir.path, INSTALLED_PROGRESS), "utf-8")).toContain(
41
42
  `Rename \`${LEGACY_AGENT_INSTRUCTIONS_FILE}\` to \`${AGENT_INSTRUCTIONS_FILE}\``,
@@ -79,6 +80,24 @@ describe("runUpgrade", () => {
79
80
  );
80
81
  });
81
82
 
83
+ it("migrates repos that still use the previous workspace skill path", () => {
84
+ const repoDir = useTmpDir();
85
+ const sourceDir = useTmpDir();
86
+ makeLegacyRepoFramework(repoDir.path, "0.1.0");
87
+ makeSourceSkill(sourceDir.path, "0.2.0");
88
+
89
+ const result = runUpgrade(new Repo(repoDir.path), {
90
+ sourceRoot: sourceDir.path,
91
+ });
92
+
93
+ expect(result).toBe(0);
94
+ expect(existsSync(join(repoDir.path, "skills", "first-tree"))).toBe(false);
95
+ expect(readFileSync(join(repoDir.path, FRAMEWORK_VERSION), "utf-8").trim()).toBe("0.2.0");
96
+ expect(readFileSync(join(repoDir.path, INSTALLED_PROGRESS), "utf-8")).toContain(
97
+ "skills/first-tree/",
98
+ );
99
+ });
100
+
82
101
  it("refuses to replace a newer installed skill with an older packaged skill", () => {
83
102
  const repoDir = useTmpDir();
84
103
  const sourceDir = useTmpDir();
@@ -43,7 +43,7 @@ describe("checkProgress", () => {
43
43
 
44
44
  it("returns empty when all checked", () => {
45
45
  const tmp = useTmpDir();
46
- mkdirSync(join(tmp.path, "skills", "first-tree"), { recursive: true });
46
+ makeFramework(tmp.path);
47
47
  writeFileSync(
48
48
  join(tmp.path, INSTALLED_PROGRESS),
49
49
  "# Progress\n- [x] Task one\n- [x] Task two\n",
@@ -54,7 +54,7 @@ describe("checkProgress", () => {
54
54
 
55
55
  it("returns unchecked items", () => {
56
56
  const tmp = useTmpDir();
57
- mkdirSync(join(tmp.path, "skills", "first-tree"), { recursive: true });
57
+ makeFramework(tmp.path);
58
58
  writeFileSync(
59
59
  join(tmp.path, INSTALLED_PROGRESS),
60
60
  "# Progress\n- [x] Done task\n- [ ] Pending task\n- [ ] Another pending\n",
@@ -65,7 +65,7 @@ describe("checkProgress", () => {
65
65
 
66
66
  it("returns empty for empty progress", () => {
67
67
  const tmp = useTmpDir();
68
- mkdirSync(join(tmp.path, "skills", "first-tree"), { recursive: true });
68
+ makeFramework(tmp.path);
69
69
  writeFileSync(join(tmp.path, INSTALLED_PROGRESS), "");
70
70
  const repo = new Repo(tmp.path);
71
71
  expect(checkProgress(repo)).toEqual([]);
@@ -1,2 +0,0 @@
1
- import { t as runOnboarding } from "./onboarding-CPP8fF4D.js";
2
- export { runOnboarding };
@@ -1,10 +0,0 @@
1
- //#region skills/first-tree/references/onboarding.md
2
- var onboarding_default = "# Context Tree Onboarding\n\nYou are setting up a **Context Tree** — the living source of truth for an organization. This document tells you what it is and how to bootstrap one.\n\n---\n\n## What Is a Context Tree\n\nA Context Tree is a Git repository where every directory is a **domain** and every file is a **node**. Each node captures decisions, designs, and cross-domain relationships — the knowledge that would otherwise scatter across PRs, documents, and people's heads.\n\nKey properties:\n\n- **Nodes are markdown files.** Each directory has a `NODE.md` that describes the domain. Leaf `.md` files capture specific decisions or designs.\n- **Every node has an owner.** Declared in YAML frontmatter. Owners approve changes to their nodes.\n- **Organized by concern, not by repo or team.** An agent working on \"add SSO\" finds all auth context in one place — not split across 4 repos.\n- **The tree is never a snapshot — it's the current state.** When decisions change, the tree updates. Stale nodes are bugs.\n\n### Frontmatter Format\n\nEvery node has frontmatter:\n\n```yaml\n---\ntitle: \"Auth Architecture\"\nowners: [alice, bob]\nsoft_links: [/infrastructure/deployments]\n---\n```\n\n- `owners` — who can approve changes. `owners: []` inherits from parent. `owners: [*]` means anyone.\n- `soft_links` — cross-references to related nodes in other domains.\n\n### What Belongs in the Tree\n\nInformation an agent needs to **decide** on an approach — not to execute it.\n\n**Yes:** \"Auth spans 4 repos: backend issues JWTs, frontend uses Better Auth, extension uses OAuth popup, desktop uses localhost callback.\"\n\n**No:** The function signature of `auth_service.verify()` — that's in the code.\n\n---\n\n## Four Principles\n\n1. **Source of truth for decisions, not execution.** The tree captures the *what* and *why*. Execution details stay in source systems.\n2. **Agents are first-class participants.** The tree is designed for agents to navigate and update.\n3. **Transparency by default.** Reading is open to all. Writing requires owner approval.\n4. **Git-native.** Nodes are files, domains are directories. History, ownership, and review follow Git conventions.\n\n---\n\n## How to Set Up a Context Tree\n\n### Prerequisites\n\n- A source/workspace Git repository, or an already-created dedicated tree repo\n- Node.js 18+\n- The npm package is `first-tree`, the installed CLI command is\n `context-tree`, and the installed skill directory in the tree is\n `skills/first-tree/`\n- Use `npx first-tree init` for one-off runs, or `npm install -g first-tree`\n to add the `context-tree` command to your PATH\n\n### Step 1: Initialize\n\nRecommended workflow: run `context-tree init` from your source or workspace repo.\nThe CLI will create a sibling dedicated tree repo named `<repo>-context` by\ndefault and install the framework there.\n\n```bash\ncd my-org\ncontext-tree init\ncd ../my-org-context\n```\n\nIf you already created a dedicated tree repo manually, initialize it in place:\n\n```bash\nmkdir my-org-context && cd my-org-context\ngit init\ncontext-tree init --here\n```\n\nEither way, the framework installs into `skills/first-tree/`, renders\nscaffolding (`NODE.md`, `AGENTS.md`, `members/NODE.md`), and generates a task\nlist in `skills/first-tree/progress.md`.\n\nPublishing tip: keep the tree repo in the same GitHub organization as the\nsource repo unless you have a reason not to.\n\n### Step 2: Work Through the Task List\n\nRead `skills/first-tree/progress.md`. It contains a checklist tailored to the current state of the repo. Complete each task:\n\n- Fill in `NODE.md` with your organization name, owners, and domains\n- Add project-specific instructions to `AGENTS.md` below the framework markers\n- Create member nodes under `members/`\n- Optionally configure agent integration (e.g., Claude Code session hooks)\n- Copy validation workflows from `skills/first-tree/assets/framework/workflows/` to `.github/workflows/`\n\nAs you complete each task, check it off in `skills/first-tree/progress.md` by changing `- [ ]` to `- [x]`.\n\n### Step 3: Verify\n\n```bash\ncontext-tree verify\n```\n\nOr, from your source/workspace repo:\n\n```bash\ncontext-tree verify --tree-path ../my-org-context\n```\n\nThis fails if any items in `skills/first-tree/progress.md` remain unchecked, and runs deterministic checks (valid frontmatter, node structure, member nodes exist).\n\n### Step 4: Design Your Domains\n\nCreate top-level directories for your organization's primary concerns. Each needs a `NODE.md`:\n\n```\nmy-org-tree/\n NODE.md # root — lists all domains\n engineering/\n NODE.md # decisions about architecture, infra, tooling\n product/\n NODE.md # strategy, roadmap, user research\n marketing/\n NODE.md # positioning, campaigns\n members/\n NODE.md # team members and agents\n alice/\n NODE.md # individual member node\n```\n\n### Step 5: Populate from Existing Work\n\nFor each domain, extract knowledge from existing repos, docs, and systems:\n\n- Decisions and their rationale\n- Cross-domain relationships and dependencies\n- Constraints that aren't obvious from the code\n\nThe tree doesn't duplicate source code — it captures what connects things and why they were built that way.\n\n---\n\n## CLI Reference\n\n| Command | Description |\n|---------|-------------|\n| `context-tree init` | Create or refresh a dedicated tree repo. By default, running in a source/workspace repo creates a sibling `<repo>-context`; use `--here` to initialize the current repo in place. |\n| `context-tree verify` | Check the installed progress file for unchecked items + run deterministic validation. Use `--tree-path` when invoking from another working directory. |\n| `context-tree upgrade` | Refresh the installed framework skill from the currently running `first-tree` npm package and generate follow-up tasks. Use `--tree-path` when invoking from another working directory. |\n| `context-tree help onboarding` | Print this onboarding guide. |\n\n---\n\n## Upgrading the Framework\n\nWhen the framework updates:\n\n```bash\ncontext-tree upgrade\n```\n\n`context-tree upgrade` refreshes `skills/first-tree/` from the\nskill bundled with the currently running `first-tree` npm package, preserves your\ntree content, and generates follow-up tasks in\n`skills/first-tree/progress.md`.\n\nIf your repo still uses the older `skills/first-tree-cli-framework/` path,\n`context-tree upgrade` will migrate it to `skills/first-tree/` first.\n\nTo pick up a newer framework release, first run a newer package version, for\nexample `npx first-tree@latest upgrade`, or update your global `first-tree`\ninstall before running `context-tree upgrade`.\n\n---\n\n## Further Reading\n\n- `skills/first-tree/references/principles.md` — Core principles with detailed examples\n- `skills/first-tree/references/ownership-and-naming.md` — How nodes are named and owned\n- `AGENTS.md` in your tree — The before/during/after workflow for every task\n";
3
- //#endregion
4
- //#region skills/first-tree/engine/onboarding.ts
5
- function runOnboarding(output = console.log) {
6
- output(onboarding_default);
7
- return 0;
8
- }
9
- //#endregion
10
- export { onboarding_default as n, runOnboarding as t };