first-tree 0.0.4 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -13
- package/dist/cli.js +5 -5
- package/dist/{help-Dtdj91HJ.js → help-DV9-AaFp.js} +1 -1
- package/dist/{init--VepFe6N.js → init-BgGH2_yC.js} +57 -11
- package/dist/onboarding-D7fGGOMN.js +10 -0
- package/dist/onboarding-lASHHmgO.js +2 -0
- package/dist/{repo-DY57bMqr.js → repo-Cc5U4DWT.js} +76 -5
- package/dist/source-integration-CuKjoheT.js +84 -0
- package/dist/{upgrade-Cgx_K2HM.js → upgrade-BvA9oKmi.js} +37 -9
- package/dist/{verify-mC9ZTd1f.js → verify-G8gNXzDX.js} +5 -1
- package/package.json +11 -9
- package/skills/first-tree/SKILL.md +30 -10
- package/skills/first-tree/agents/openai.yaml +1 -1
- package/skills/first-tree/assets/framework/examples/claude-code/README.md +2 -2
- package/skills/first-tree/assets/framework/examples/claude-code/settings.json +1 -1
- package/skills/first-tree/assets/framework/helpers/generate-codeowners.ts +1 -1
- package/skills/first-tree/assets/framework/helpers/inject-tree-context.sh +0 -0
- package/skills/first-tree/assets/framework/helpers/run-review.ts +1 -1
- package/skills/first-tree/assets/framework/templates/agents.md.template +2 -2
- package/skills/first-tree/assets/framework/templates/members-domain.md.template +1 -1
- package/skills/first-tree/assets/framework/templates/root-node.md.template +3 -3
- package/skills/first-tree/assets/framework/workflows/codeowners.yml +1 -1
- package/skills/first-tree/assets/framework/workflows/pr-review.yml +1 -1
- package/skills/first-tree/engine/init.ts +78 -6
- package/skills/first-tree/engine/repo.ts +74 -10
- package/skills/first-tree/engine/rules/agent-integration.ts +3 -1
- package/skills/first-tree/engine/rules/framework.ts +2 -2
- package/skills/first-tree/engine/runtime/adapters.ts +6 -2
- package/skills/first-tree/engine/runtime/asset-loader.ts +142 -4
- package/skills/first-tree/engine/runtime/installer.ts +18 -12
- package/skills/first-tree/engine/runtime/source-integration.ts +80 -0
- package/skills/first-tree/engine/upgrade.ts +103 -9
- package/skills/first-tree/engine/verify.ts +7 -0
- package/skills/first-tree/references/maintainer-architecture.md +4 -0
- package/skills/first-tree/references/maintainer-thin-cli.md +3 -0
- package/skills/first-tree/references/onboarding.md +56 -21
- package/skills/first-tree/references/principles.md +97 -57
- package/skills/first-tree/references/source-map.md +1 -0
- package/skills/first-tree/references/source-workspace-installation.md +52 -0
- package/skills/first-tree/references/upgrade-contract.md +67 -26
- package/skills/first-tree/scripts/check-skill-sync.sh +2 -0
- package/skills/first-tree/scripts/quick_validate.py +0 -0
- package/skills/first-tree/scripts/run-local-cli.sh +0 -0
- package/skills/first-tree/tests/asset-loader.test.ts +23 -1
- package/skills/first-tree/tests/helpers.ts +27 -3
- package/skills/first-tree/tests/init.test.ts +72 -3
- package/skills/first-tree/tests/repo.test.ts +46 -0
- package/skills/first-tree/tests/rules.test.ts +9 -7
- package/skills/first-tree/tests/skill-artifacts.test.ts +45 -0
- package/skills/first-tree/tests/upgrade.test.ts +58 -3
- package/skills/first-tree/tests/verify.test.ts +21 -3
- package/dist/installer-cH7N4RNj.js +0 -47
- package/dist/onboarding-C9cYSE6F.js +0 -2
- package/dist/onboarding-CPP8fF4D.js +0 -10
|
@@ -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
|
-
|
|
28
|
-
|
|
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",
|
|
@@ -10,11 +10,13 @@ import {
|
|
|
10
10
|
import { Repo } from "#skill/engine/repo.js";
|
|
11
11
|
import {
|
|
12
12
|
AGENT_INSTRUCTIONS_FILE,
|
|
13
|
+
CLAUDE_INSTRUCTIONS_FILE,
|
|
13
14
|
FRAMEWORK_VERSION,
|
|
14
15
|
INSTALLED_PROGRESS,
|
|
15
16
|
LEGACY_AGENT_INSTRUCTIONS_FILE,
|
|
16
17
|
LEGACY_PROGRESS,
|
|
17
18
|
} from "#skill/engine/runtime/asset-loader.js";
|
|
19
|
+
import { buildSourceIntegrationLine } from "#skill/engine/runtime/source-integration.js";
|
|
18
20
|
import {
|
|
19
21
|
makeGitRepo,
|
|
20
22
|
useTmpDir,
|
|
@@ -100,7 +102,7 @@ describe("writeProgress", () => {
|
|
|
100
102
|
|
|
101
103
|
it("overwrites existing file", () => {
|
|
102
104
|
const tmp = useTmpDir();
|
|
103
|
-
mkdirSync(join(tmp.path, "skills", "first-tree"), {
|
|
105
|
+
mkdirSync(join(tmp.path, ".agents", "skills", "first-tree"), {
|
|
104
106
|
recursive: true,
|
|
105
107
|
});
|
|
106
108
|
writeFileSync(join(tmp.path, INSTALLED_PROGRESS), "old");
|
|
@@ -124,6 +126,10 @@ const fakeGitInitializer = (root: string): void => {
|
|
|
124
126
|
makeGitRepo(root);
|
|
125
127
|
};
|
|
126
128
|
|
|
129
|
+
function escapeRegExp(text: string): string {
|
|
130
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
131
|
+
}
|
|
132
|
+
|
|
127
133
|
describe("runInit", () => {
|
|
128
134
|
it("errors when not a git repo", () => {
|
|
129
135
|
const tmp = useTmpDir();
|
|
@@ -145,7 +151,10 @@ describe("runInit", () => {
|
|
|
145
151
|
|
|
146
152
|
expect(ret).toBe(0);
|
|
147
153
|
expect(
|
|
148
|
-
existsSync(join(repoDir.path, "skills", "first-tree", "SKILL.md")),
|
|
154
|
+
existsSync(join(repoDir.path, ".agents", "skills", "first-tree", "SKILL.md")),
|
|
155
|
+
).toBe(true);
|
|
156
|
+
expect(
|
|
157
|
+
existsSync(join(repoDir.path, ".claude", "skills", "first-tree", "SKILL.md")),
|
|
149
158
|
).toBe(true);
|
|
150
159
|
expect(readFileSync(join(repoDir.path, FRAMEWORK_VERSION), "utf-8").trim()).toBe("0.2.0");
|
|
151
160
|
expect(existsSync(join(repoDir.path, "NODE.md"))).toBe(true);
|
|
@@ -202,7 +211,24 @@ describe("runInit", () => {
|
|
|
202
211
|
);
|
|
203
212
|
|
|
204
213
|
expect(ret).toBe(0);
|
|
205
|
-
expect(
|
|
214
|
+
expect(
|
|
215
|
+
existsSync(join(sourceRepoDir.path, ".agents", "skills", "first-tree", "SKILL.md")),
|
|
216
|
+
).toBe(true);
|
|
217
|
+
expect(
|
|
218
|
+
existsSync(join(sourceRepoDir.path, ".claude", "skills", "first-tree", "SKILL.md")),
|
|
219
|
+
).toBe(true);
|
|
220
|
+
expect(
|
|
221
|
+
readFileSync(join(sourceRepoDir.path, AGENT_INSTRUCTIONS_FILE), "utf-8"),
|
|
222
|
+
).toContain(buildSourceIntegrationLine(basename(treeRepo)));
|
|
223
|
+
expect(
|
|
224
|
+
readFileSync(join(sourceRepoDir.path, CLAUDE_INSTRUCTIONS_FILE), "utf-8"),
|
|
225
|
+
).toContain(buildSourceIntegrationLine(basename(treeRepo)));
|
|
226
|
+
expect(
|
|
227
|
+
existsSync(join(treeRepo, ".agents", "skills", "first-tree", "SKILL.md")),
|
|
228
|
+
).toBe(true);
|
|
229
|
+
expect(
|
|
230
|
+
existsSync(join(treeRepo, ".claude", "skills", "first-tree", "SKILL.md")),
|
|
231
|
+
).toBe(true);
|
|
206
232
|
expect(existsSync(join(treeRepo, "NODE.md"))).toBe(true);
|
|
207
233
|
expect(existsSync(join(treeRepo, AGENT_INSTRUCTIONS_FILE))).toBe(true);
|
|
208
234
|
expect(existsSync(join(treeRepo, "members", "NODE.md"))).toBe(true);
|
|
@@ -212,6 +238,49 @@ describe("runInit", () => {
|
|
|
212
238
|
expect(existsSync(join(sourceRepoDir.path, INSTALLED_PROGRESS))).toBe(false);
|
|
213
239
|
});
|
|
214
240
|
|
|
241
|
+
it("updates existing AGENTS.md and CLAUDE.md without duplicating the source integration line", () => {
|
|
242
|
+
const sourceRepoDir = useTmpDir();
|
|
243
|
+
const sourceSkillDir = useTmpDir();
|
|
244
|
+
makeSourceRepo(sourceRepoDir.path);
|
|
245
|
+
makeSourceSkill(sourceSkillDir.path, "0.2.0");
|
|
246
|
+
writeFileSync(join(sourceRepoDir.path, AGENT_INSTRUCTIONS_FILE), "# Repo Notes\n");
|
|
247
|
+
writeFileSync(join(sourceRepoDir.path, CLAUDE_INSTRUCTIONS_FILE), "# Claude Notes\n");
|
|
248
|
+
|
|
249
|
+
expect(
|
|
250
|
+
runInit(new Repo(sourceRepoDir.path), {
|
|
251
|
+
sourceRoot: sourceSkillDir.path,
|
|
252
|
+
gitInitializer: fakeGitInitializer,
|
|
253
|
+
}),
|
|
254
|
+
).toBe(0);
|
|
255
|
+
expect(
|
|
256
|
+
runInit(new Repo(sourceRepoDir.path), {
|
|
257
|
+
sourceRoot: sourceSkillDir.path,
|
|
258
|
+
gitInitializer: fakeGitInitializer,
|
|
259
|
+
}),
|
|
260
|
+
).toBe(0);
|
|
261
|
+
|
|
262
|
+
const treeRepo = join(
|
|
263
|
+
dirname(sourceRepoDir.path),
|
|
264
|
+
`${basename(sourceRepoDir.path)}-context`,
|
|
265
|
+
);
|
|
266
|
+
const expectedLine = buildSourceIntegrationLine(basename(treeRepo));
|
|
267
|
+
const agentText = readFileSync(
|
|
268
|
+
join(sourceRepoDir.path, AGENT_INSTRUCTIONS_FILE),
|
|
269
|
+
"utf-8",
|
|
270
|
+
);
|
|
271
|
+
const claudeText = readFileSync(
|
|
272
|
+
join(sourceRepoDir.path, CLAUDE_INSTRUCTIONS_FILE),
|
|
273
|
+
"utf-8",
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
expect(
|
|
277
|
+
agentText.match(new RegExp(escapeRegExp(expectedLine), "g")),
|
|
278
|
+
).toHaveLength(1);
|
|
279
|
+
expect(
|
|
280
|
+
claudeText.match(new RegExp(escapeRegExp(expectedLine), "g")),
|
|
281
|
+
).toHaveLength(1);
|
|
282
|
+
});
|
|
283
|
+
|
|
215
284
|
it("keeps supporting in-place init with --here", () => {
|
|
216
285
|
const sourceRepoDir = useTmpDir();
|
|
217
286
|
const sourceSkillDir = useTmpDir();
|
|
@@ -4,19 +4,25 @@ 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,
|
|
8
|
+
CLAUDE_INSTRUCTIONS_FILE,
|
|
7
9
|
FRAMEWORK_VERSION,
|
|
8
10
|
INSTALLED_PROGRESS,
|
|
9
11
|
LEGACY_AGENT_INSTRUCTIONS_FILE,
|
|
12
|
+
LEGACY_REPO_SKILL_PROGRESS,
|
|
13
|
+
LEGACY_REPO_SKILL_VERSION,
|
|
10
14
|
LEGACY_SKILL_PROGRESS,
|
|
11
15
|
LEGACY_SKILL_VERSION,
|
|
12
16
|
LEGACY_PROGRESS,
|
|
13
17
|
LEGACY_VERSION,
|
|
18
|
+
SOURCE_INTEGRATION_MARKER,
|
|
14
19
|
} from "#skill/engine/runtime/asset-loader.js";
|
|
15
20
|
import {
|
|
16
21
|
useTmpDir,
|
|
17
22
|
makeFramework,
|
|
18
23
|
makeGitRepo,
|
|
19
24
|
makeLegacyFramework,
|
|
25
|
+
makeLegacyRepoFramework,
|
|
20
26
|
makeLegacyNamedFramework,
|
|
21
27
|
makeSourceRepo,
|
|
22
28
|
makeSourceSkill,
|
|
@@ -182,6 +188,13 @@ describe("hasFramework", () => {
|
|
|
182
188
|
expect(repo.hasFramework()).toBe(true);
|
|
183
189
|
});
|
|
184
190
|
|
|
191
|
+
it("returns true with the previous workspace skill path", () => {
|
|
192
|
+
const tmp = useTmpDir();
|
|
193
|
+
makeLegacyRepoFramework(tmp.path);
|
|
194
|
+
const repo = new Repo(tmp.path);
|
|
195
|
+
expect(repo.hasFramework()).toBe(true);
|
|
196
|
+
});
|
|
197
|
+
|
|
185
198
|
it("returns false without VERSION file", () => {
|
|
186
199
|
const tmp = useTmpDir();
|
|
187
200
|
const repo = new Repo(tmp.path);
|
|
@@ -213,6 +226,13 @@ describe("readVersion", () => {
|
|
|
213
226
|
expect(repo.readVersion()).toBe("0.2.5");
|
|
214
227
|
});
|
|
215
228
|
|
|
229
|
+
it("reads the previous workspace skill version", () => {
|
|
230
|
+
const tmp = useTmpDir();
|
|
231
|
+
makeLegacyRepoFramework(tmp.path, "0.2.4");
|
|
232
|
+
const repo = new Repo(tmp.path);
|
|
233
|
+
expect(repo.readVersion()).toBe("0.2.4");
|
|
234
|
+
});
|
|
235
|
+
|
|
216
236
|
it("returns null when missing", () => {
|
|
217
237
|
const tmp = useTmpDir();
|
|
218
238
|
const repo = new Repo(tmp.path);
|
|
@@ -245,6 +265,14 @@ describe("path preferences", () => {
|
|
|
245
265
|
expect(repo.preferredProgressPath()).toBe(LEGACY_SKILL_PROGRESS);
|
|
246
266
|
expect(repo.frameworkVersionPath()).toBe(LEGACY_SKILL_VERSION);
|
|
247
267
|
});
|
|
268
|
+
|
|
269
|
+
it("switches path preferences for repos using the previous workspace skill path", () => {
|
|
270
|
+
const tmp = useTmpDir();
|
|
271
|
+
makeLegacyRepoFramework(tmp.path);
|
|
272
|
+
const repo = new Repo(tmp.path);
|
|
273
|
+
expect(repo.preferredProgressPath()).toBe(LEGACY_REPO_SKILL_PROGRESS);
|
|
274
|
+
expect(repo.frameworkVersionPath()).toBe(LEGACY_REPO_SKILL_VERSION);
|
|
275
|
+
});
|
|
248
276
|
});
|
|
249
277
|
|
|
250
278
|
// --- agent instructions helpers ---
|
|
@@ -428,6 +456,24 @@ describe("init heuristics", () => {
|
|
|
428
456
|
expect(repo.isLikelySourceRepo()).toBe(false);
|
|
429
457
|
});
|
|
430
458
|
|
|
459
|
+
it("treats a source repo with installed skill and integration markers as a source repo", () => {
|
|
460
|
+
const tmp = useTmpDir();
|
|
461
|
+
makeSourceRepo(tmp.path);
|
|
462
|
+
makeFramework(tmp.path);
|
|
463
|
+
writeFileSync(
|
|
464
|
+
join(tmp.path, AGENT_INSTRUCTIONS_FILE),
|
|
465
|
+
`${SOURCE_INTEGRATION_MARKER} Use the installed \`first-tree\` skill here.\n`,
|
|
466
|
+
);
|
|
467
|
+
writeFileSync(
|
|
468
|
+
join(tmp.path, CLAUDE_INSTRUCTIONS_FILE),
|
|
469
|
+
`${SOURCE_INTEGRATION_MARKER} Use the installed \`first-tree\` skill here.\n`,
|
|
470
|
+
);
|
|
471
|
+
const repo = new Repo(tmp.path);
|
|
472
|
+
expect(repo.hasSourceWorkspaceIntegration()).toBe(true);
|
|
473
|
+
expect(repo.looksLikeTreeRepo()).toBe(false);
|
|
474
|
+
expect(repo.isLikelySourceRepo()).toBe(true);
|
|
475
|
+
});
|
|
476
|
+
|
|
431
477
|
it("does not mistake the framework source repo for a user tree repo", () => {
|
|
432
478
|
const tmp = useTmpDir();
|
|
433
479
|
makeSourceRepo(tmp.path);
|
|
@@ -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(
|
|
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}}',
|
|
@@ -20,6 +20,17 @@ describe("skill artifacts", () => {
|
|
|
20
20
|
it("keeps only the canonical skill in the source repo", () => {
|
|
21
21
|
expect(existsSync(join(ROOT, "skills", "first-tree", "SKILL.md"))).toBe(true);
|
|
22
22
|
expect(existsSync(join(ROOT, "skills", "first-tree", "references", "onboarding.md"))).toBe(true);
|
|
23
|
+
expect(
|
|
24
|
+
existsSync(
|
|
25
|
+
join(
|
|
26
|
+
ROOT,
|
|
27
|
+
"skills",
|
|
28
|
+
"first-tree",
|
|
29
|
+
"references",
|
|
30
|
+
"source-workspace-installation.md",
|
|
31
|
+
),
|
|
32
|
+
),
|
|
33
|
+
).toBe(true);
|
|
23
34
|
expect(existsSync(join(ROOT, "skills", "first-tree", "assets", "framework", "manifest.json"))).toBe(true);
|
|
24
35
|
expect(existsSync(join(ROOT, "skills", "first-tree", "engine", "init.ts"))).toBe(true);
|
|
25
36
|
expect(existsSync(join(ROOT, "AGENTS.md"))).toBe(true);
|
|
@@ -43,6 +54,18 @@ describe("skill artifacts", () => {
|
|
|
43
54
|
),
|
|
44
55
|
),
|
|
45
56
|
).toBe(true);
|
|
57
|
+
expect(
|
|
58
|
+
existsSync(
|
|
59
|
+
join(
|
|
60
|
+
ROOT,
|
|
61
|
+
"skills",
|
|
62
|
+
"first-tree",
|
|
63
|
+
"engine",
|
|
64
|
+
"runtime",
|
|
65
|
+
"source-integration.ts",
|
|
66
|
+
),
|
|
67
|
+
),
|
|
68
|
+
).toBe(true);
|
|
46
69
|
expect(
|
|
47
70
|
existsSync(
|
|
48
71
|
join(
|
|
@@ -200,11 +223,16 @@ describe("skill artifacts", () => {
|
|
|
200
223
|
expect(read("README.md")).toContain("Package Name vs Command");
|
|
201
224
|
expect(read("README.md")).toContain("Canonical Documentation");
|
|
202
225
|
expect(read("README.md")).toContain("references/source-map.md");
|
|
226
|
+
expect(read("README.md")).toContain("source-workspace-installation.md");
|
|
203
227
|
expect(read("README.md")).toContain("skills/first-tree/");
|
|
228
|
+
expect(read("README.md")).toContain(".agents/skills/first-tree/");
|
|
229
|
+
expect(read("README.md")).toContain(".claude/skills/first-tree/");
|
|
204
230
|
expect(read("README.md")).toContain("bundled canonical");
|
|
205
231
|
expect(read("README.md")).toContain("dedicated tree repo");
|
|
232
|
+
expect(read("README.md")).toContain("FIRST-TREE-SOURCE-INTEGRATION:");
|
|
206
233
|
expect(read("README.md")).toContain("`first-tree` skill");
|
|
207
234
|
expect(read("AGENTS.md")).toContain("references/source-map.md");
|
|
235
|
+
expect(read("AGENTS.md")).toContain("source-workspace-installation.md");
|
|
208
236
|
expect(read("AGENTS.md")).toContain("bundled skill path");
|
|
209
237
|
expect(read("AGENTS.md")).not.toContain("### Running evals");
|
|
210
238
|
expect(read("AGENTS.md")).not.toContain("EVALS_TREE_REPO");
|
|
@@ -218,6 +246,11 @@ describe("skill artifacts", () => {
|
|
|
218
246
|
expect(onboarding).toContain("installed CLI command is");
|
|
219
247
|
expect(onboarding).toContain("currently running `first-tree` npm package");
|
|
220
248
|
expect(onboarding).toContain("npx first-tree@latest upgrade");
|
|
249
|
+
expect(onboarding).toContain(".agents/skills/first-tree/");
|
|
250
|
+
expect(onboarding).toContain(".claude/skills/first-tree/");
|
|
251
|
+
expect(onboarding).toContain("FIRST-TREE-SOURCE-INTEGRATION:");
|
|
252
|
+
expect(onboarding).toContain("source/workspace repo");
|
|
253
|
+
expect(onboarding).toContain("git submodule");
|
|
221
254
|
expect(onboarding).not.toContain("This clones the framework into `.context-tree/`");
|
|
222
255
|
expect(onboarding).not.toContain("from upstream");
|
|
223
256
|
|
|
@@ -228,11 +261,16 @@ describe("skill artifacts", () => {
|
|
|
228
261
|
expect(skillMd).toContain("maintainer-testing.md");
|
|
229
262
|
expect(skillMd).toContain("currently running `first-tree` package");
|
|
230
263
|
expect(skillMd).toContain("so it is not confused with the `first-tree`");
|
|
264
|
+
expect(skillMd).toContain(".agents/skills/first-tree/");
|
|
265
|
+
expect(skillMd).toContain(".claude/skills/first-tree/");
|
|
266
|
+
expect(skillMd).toContain("source-workspace-installation.md");
|
|
267
|
+
expect(skillMd).toContain("FIRST-TREE-SOURCE-INTEGRATION:");
|
|
231
268
|
expect(skillMd).not.toContain("canonical eval harness");
|
|
232
269
|
|
|
233
270
|
const sourceMap = read("skills/first-tree/references/source-map.md");
|
|
234
271
|
expect(sourceMap).not.toContain("repo-snapshot");
|
|
235
272
|
expect(sourceMap).not.toContain("sync-skill-artifacts.sh");
|
|
273
|
+
expect(sourceMap).toContain("source-workspace-installation.md");
|
|
236
274
|
expect(sourceMap).toContain("maintainer-architecture.md");
|
|
237
275
|
expect(sourceMap).toContain("maintainer-thin-cli.md");
|
|
238
276
|
expect(sourceMap).toContain("maintainer-build-and-distribution.md");
|
|
@@ -246,6 +284,13 @@ describe("skill artifacts", () => {
|
|
|
246
284
|
expect(sourceMap).not.toContain("vitest.eval.config.ts");
|
|
247
285
|
expect(sourceMap).toContain(".github/workflows/ci.yml");
|
|
248
286
|
|
|
287
|
+
const sourceWorkspaceInstall = read(
|
|
288
|
+
"skills/first-tree/references/source-workspace-installation.md",
|
|
289
|
+
);
|
|
290
|
+
expect(sourceWorkspaceInstall).toContain("FIRST-TREE-SOURCE-INTEGRATION:");
|
|
291
|
+
expect(sourceWorkspaceInstall).toContain("git submodule");
|
|
292
|
+
expect(sourceWorkspaceInstall).toContain("Do not run `context-tree verify`");
|
|
293
|
+
|
|
249
294
|
const maintainerArchitecture = read(
|
|
250
295
|
"skills/first-tree/references/maintainer-architecture.md",
|
|
251
296
|
);
|
|
@@ -1,19 +1,23 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { basename, join } from "node:path";
|
|
3
3
|
import { describe, expect, it } from "vitest";
|
|
4
4
|
import { Repo } from "#skill/engine/repo.js";
|
|
5
5
|
import { runUpgrade } from "#skill/engine/upgrade.js";
|
|
6
6
|
import {
|
|
7
7
|
AGENT_INSTRUCTIONS_FILE,
|
|
8
|
+
CLAUDE_INSTRUCTIONS_FILE,
|
|
8
9
|
FRAMEWORK_VERSION,
|
|
9
10
|
INSTALLED_PROGRESS,
|
|
10
11
|
LEGACY_AGENT_INSTRUCTIONS_FILE,
|
|
12
|
+
SOURCE_INTEGRATION_MARKER,
|
|
11
13
|
} from "#skill/engine/runtime/asset-loader.js";
|
|
14
|
+
import { buildSourceIntegrationLine } from "#skill/engine/runtime/source-integration.js";
|
|
12
15
|
import {
|
|
13
16
|
makeAgentsMd,
|
|
14
17
|
makeFramework,
|
|
15
18
|
makeSourceRepo,
|
|
16
19
|
makeLegacyFramework,
|
|
20
|
+
makeLegacyRepoFramework,
|
|
17
21
|
makeLegacyNamedFramework,
|
|
18
22
|
makeSourceSkill,
|
|
19
23
|
useTmpDir,
|
|
@@ -35,7 +39,7 @@ describe("runUpgrade", () => {
|
|
|
35
39
|
expect(existsSync(join(repoDir.path, ".context-tree"))).toBe(false);
|
|
36
40
|
expect(readFileSync(join(repoDir.path, FRAMEWORK_VERSION), "utf-8").trim()).toBe("0.2.0");
|
|
37
41
|
expect(readFileSync(join(repoDir.path, INSTALLED_PROGRESS), "utf-8")).toContain(
|
|
38
|
-
"skills/first-tree/assets/framework/VERSION",
|
|
42
|
+
".agents/skills/first-tree/assets/framework/VERSION",
|
|
39
43
|
);
|
|
40
44
|
expect(readFileSync(join(repoDir.path, INSTALLED_PROGRESS), "utf-8")).toContain(
|
|
41
45
|
`Rename \`${LEGACY_AGENT_INSTRUCTIONS_FILE}\` to \`${AGENT_INSTRUCTIONS_FILE}\``,
|
|
@@ -79,6 +83,24 @@ describe("runUpgrade", () => {
|
|
|
79
83
|
);
|
|
80
84
|
});
|
|
81
85
|
|
|
86
|
+
it("migrates repos that still use the previous workspace skill path", () => {
|
|
87
|
+
const repoDir = useTmpDir();
|
|
88
|
+
const sourceDir = useTmpDir();
|
|
89
|
+
makeLegacyRepoFramework(repoDir.path, "0.1.0");
|
|
90
|
+
makeSourceSkill(sourceDir.path, "0.2.0");
|
|
91
|
+
|
|
92
|
+
const result = runUpgrade(new Repo(repoDir.path), {
|
|
93
|
+
sourceRoot: sourceDir.path,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
expect(result).toBe(0);
|
|
97
|
+
expect(existsSync(join(repoDir.path, "skills", "first-tree"))).toBe(false);
|
|
98
|
+
expect(readFileSync(join(repoDir.path, FRAMEWORK_VERSION), "utf-8").trim()).toBe("0.2.0");
|
|
99
|
+
expect(readFileSync(join(repoDir.path, INSTALLED_PROGRESS), "utf-8")).toContain(
|
|
100
|
+
"skills/first-tree/",
|
|
101
|
+
);
|
|
102
|
+
});
|
|
103
|
+
|
|
82
104
|
it("refuses to replace a newer installed skill with an older packaged skill", () => {
|
|
83
105
|
const repoDir = useTmpDir();
|
|
84
106
|
const sourceDir = useTmpDir();
|
|
@@ -100,4 +122,37 @@ describe("runUpgrade", () => {
|
|
|
100
122
|
const result = runUpgrade(new Repo(repoDir.path));
|
|
101
123
|
expect(result).toBe(1);
|
|
102
124
|
});
|
|
125
|
+
|
|
126
|
+
it("refreshes source/workspace integration without writing tree progress", () => {
|
|
127
|
+
const repoDir = useTmpDir();
|
|
128
|
+
const sourceDir = useTmpDir();
|
|
129
|
+
makeSourceRepo(repoDir.path);
|
|
130
|
+
makeFramework(repoDir.path, "0.1.0");
|
|
131
|
+
writeFileSync(
|
|
132
|
+
join(repoDir.path, AGENT_INSTRUCTIONS_FILE),
|
|
133
|
+
`${SOURCE_INTEGRATION_MARKER} old text\n`,
|
|
134
|
+
);
|
|
135
|
+
writeFileSync(
|
|
136
|
+
join(repoDir.path, CLAUDE_INSTRUCTIONS_FILE),
|
|
137
|
+
`${SOURCE_INTEGRATION_MARKER} old text\n`,
|
|
138
|
+
);
|
|
139
|
+
makeSourceSkill(sourceDir.path, "0.2.0");
|
|
140
|
+
|
|
141
|
+
const result = runUpgrade(new Repo(repoDir.path), {
|
|
142
|
+
sourceRoot: sourceDir.path,
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const expectedLine = buildSourceIntegrationLine(
|
|
146
|
+
`${basename(repoDir.path)}-context`,
|
|
147
|
+
);
|
|
148
|
+
expect(result).toBe(0);
|
|
149
|
+
expect(readFileSync(join(repoDir.path, FRAMEWORK_VERSION), "utf-8").trim()).toBe("0.2.0");
|
|
150
|
+
expect(readFileSync(join(repoDir.path, AGENT_INSTRUCTIONS_FILE), "utf-8")).toContain(
|
|
151
|
+
expectedLine,
|
|
152
|
+
);
|
|
153
|
+
expect(readFileSync(join(repoDir.path, CLAUDE_INSTRUCTIONS_FILE), "utf-8")).toContain(
|
|
154
|
+
expectedLine,
|
|
155
|
+
);
|
|
156
|
+
expect(existsSync(join(repoDir.path, INSTALLED_PROGRESS))).toBe(false);
|
|
157
|
+
});
|
|
103
158
|
});
|
|
@@ -6,8 +6,10 @@ import { check, checkProgress, runVerify } from "#skill/engine/verify.js";
|
|
|
6
6
|
import { Repo } from "#skill/engine/repo.js";
|
|
7
7
|
import {
|
|
8
8
|
AGENT_INSTRUCTIONS_FILE,
|
|
9
|
+
CLAUDE_INSTRUCTIONS_FILE,
|
|
9
10
|
INSTALLED_PROGRESS,
|
|
10
11
|
LEGACY_PROGRESS,
|
|
12
|
+
SOURCE_INTEGRATION_MARKER,
|
|
11
13
|
} from "#skill/engine/runtime/asset-loader.js";
|
|
12
14
|
import {
|
|
13
15
|
useTmpDir,
|
|
@@ -43,7 +45,7 @@ describe("checkProgress", () => {
|
|
|
43
45
|
|
|
44
46
|
it("returns empty when all checked", () => {
|
|
45
47
|
const tmp = useTmpDir();
|
|
46
|
-
|
|
48
|
+
makeFramework(tmp.path);
|
|
47
49
|
writeFileSync(
|
|
48
50
|
join(tmp.path, INSTALLED_PROGRESS),
|
|
49
51
|
"# Progress\n- [x] Task one\n- [x] Task two\n",
|
|
@@ -54,7 +56,7 @@ describe("checkProgress", () => {
|
|
|
54
56
|
|
|
55
57
|
it("returns unchecked items", () => {
|
|
56
58
|
const tmp = useTmpDir();
|
|
57
|
-
|
|
59
|
+
makeFramework(tmp.path);
|
|
58
60
|
writeFileSync(
|
|
59
61
|
join(tmp.path, INSTALLED_PROGRESS),
|
|
60
62
|
"# Progress\n- [x] Done task\n- [ ] Pending task\n- [ ] Another pending\n",
|
|
@@ -65,7 +67,7 @@ describe("checkProgress", () => {
|
|
|
65
67
|
|
|
66
68
|
it("returns empty for empty progress", () => {
|
|
67
69
|
const tmp = useTmpDir();
|
|
68
|
-
|
|
70
|
+
makeFramework(tmp.path);
|
|
69
71
|
writeFileSync(join(tmp.path, INSTALLED_PROGRESS), "");
|
|
70
72
|
const repo = new Repo(tmp.path);
|
|
71
73
|
expect(checkProgress(repo)).toEqual([]);
|
|
@@ -238,4 +240,20 @@ describe("runVerify failing", () => {
|
|
|
238
240
|
const ret = runVerify(repo, passValidator);
|
|
239
241
|
expect(ret).toBe(1);
|
|
240
242
|
});
|
|
243
|
+
|
|
244
|
+
it("refuses to verify a source/workspace repo that only has local first-tree integration", () => {
|
|
245
|
+
const tmp = useTmpDir();
|
|
246
|
+
makeSourceRepo(tmp.path);
|
|
247
|
+
makeFramework(tmp.path);
|
|
248
|
+
writeFileSync(
|
|
249
|
+
join(tmp.path, AGENT_INSTRUCTIONS_FILE),
|
|
250
|
+
`${SOURCE_INTEGRATION_MARKER} Use the installed \`first-tree\` skill here.\n`,
|
|
251
|
+
);
|
|
252
|
+
writeFileSync(
|
|
253
|
+
join(tmp.path, CLAUDE_INSTRUCTIONS_FILE),
|
|
254
|
+
`${SOURCE_INTEGRATION_MARKER} Use the installed \`first-tree\` skill here.\n`,
|
|
255
|
+
);
|
|
256
|
+
const repo = new Repo(tmp.path);
|
|
257
|
+
expect(runVerify(repo, passValidator)).toBe(1);
|
|
258
|
+
});
|
|
241
259
|
});
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { g as SKILL_ROOT, m as LEGACY_SKILL_ROOT } from "./repo-DY57bMqr.js";
|
|
2
|
-
import { copyFileSync, cpSync, existsSync, mkdirSync, rmSync } from "node:fs";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { dirname, join } from "node:path";
|
|
5
|
-
//#region skills/first-tree/engine/runtime/installer.ts
|
|
6
|
-
function resolveBundledPackageRoot(startUrl = import.meta.url) {
|
|
7
|
-
let dir = dirname(fileURLToPath(startUrl));
|
|
8
|
-
while (true) {
|
|
9
|
-
if (existsSync(join(dir, "package.json")) && existsSync(join(dir, SKILL_ROOT, "SKILL.md"))) return dir;
|
|
10
|
-
const parent = dirname(dir);
|
|
11
|
-
if (parent === dir) break;
|
|
12
|
-
dir = parent;
|
|
13
|
-
}
|
|
14
|
-
throw new Error("Could not locate the bundled `first-tree` package root. Reinstall the package and try again.");
|
|
15
|
-
}
|
|
16
|
-
function resolveCanonicalSkillRoot(sourceRoot) {
|
|
17
|
-
const directSkillRoot = sourceRoot;
|
|
18
|
-
if (existsSync(join(directSkillRoot, "SKILL.md")) && existsSync(join(directSkillRoot, "assets", "framework", "VERSION"))) return directSkillRoot;
|
|
19
|
-
const nestedSkillRoot = join(sourceRoot, SKILL_ROOT);
|
|
20
|
-
if (existsSync(join(nestedSkillRoot, "SKILL.md")) && existsSync(join(nestedSkillRoot, "assets", "framework", "VERSION"))) return nestedSkillRoot;
|
|
21
|
-
throw new Error(`Canonical skill not found under ${sourceRoot}. Reinstall the \`first-tree\` package and try again.`);
|
|
22
|
-
}
|
|
23
|
-
function copyCanonicalSkill(sourceRoot, targetRoot) {
|
|
24
|
-
const src = resolveCanonicalSkillRoot(sourceRoot);
|
|
25
|
-
const dst = join(targetRoot, SKILL_ROOT);
|
|
26
|
-
const legacyDst = join(targetRoot, LEGACY_SKILL_ROOT);
|
|
27
|
-
if (existsSync(dst)) rmSync(dst, {
|
|
28
|
-
recursive: true,
|
|
29
|
-
force: true
|
|
30
|
-
});
|
|
31
|
-
if (legacyDst !== dst && existsSync(legacyDst)) rmSync(legacyDst, {
|
|
32
|
-
recursive: true,
|
|
33
|
-
force: true
|
|
34
|
-
});
|
|
35
|
-
mkdirSync(dirname(dst), { recursive: true });
|
|
36
|
-
cpSync(src, dst, { recursive: true });
|
|
37
|
-
}
|
|
38
|
-
function renderTemplateFile(frameworkRoot, templateName, targetRoot, targetPath) {
|
|
39
|
-
const src = join(frameworkRoot, "templates", templateName);
|
|
40
|
-
const dst = join(targetRoot, targetPath);
|
|
41
|
-
if (existsSync(dst) || !existsSync(src)) return false;
|
|
42
|
-
mkdirSync(dirname(dst), { recursive: true });
|
|
43
|
-
copyFileSync(src, dst);
|
|
44
|
-
return true;
|
|
45
|
-
}
|
|
46
|
-
//#endregion
|
|
47
|
-
export { resolveCanonicalSkillRoot as i, renderTemplateFile as n, resolveBundledPackageRoot as r, copyCanonicalSkill as t };
|
|
@@ -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 };
|