first-tree 0.0.3 → 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 (59) hide show
  1. package/README.md +78 -27
  2. package/dist/cli.js +28 -13
  3. package/dist/{help-xEI-s9iN.js → help-5-WG9QFm.js} +1 -1
  4. package/dist/{init-DtOjj0wc.js → init-CAq0Uhq6.js} +187 -25
  5. package/dist/{installer-rcZpGLnM.js → installer-UgNasLjl.js} +20 -16
  6. package/dist/onboarding-3zYUeYQb.js +2 -0
  7. package/dist/onboarding-Dd63N-V1.js +10 -0
  8. package/dist/repo-DkR12VUv.js +369 -0
  9. package/dist/upgrade-DYzuvv1k.js +140 -0
  10. package/dist/{verify-CxN6JiV9.js → verify-C0IUSkMZ.js} +66 -6
  11. package/package.json +12 -10
  12. package/skills/first-tree/SKILL.md +18 -10
  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 +17 -3
  19. package/skills/first-tree/assets/framework/templates/{agent.md.template → agents.md.template} +3 -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 +9 -6
  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/commands/init.ts +1 -1
  25. package/skills/first-tree/engine/commands/upgrade.ts +1 -1
  26. package/skills/first-tree/engine/commands/verify.ts +1 -1
  27. package/skills/first-tree/engine/init.ts +288 -18
  28. package/skills/first-tree/engine/repo.ts +220 -11
  29. package/skills/first-tree/engine/rules/agent-instructions.ts +29 -7
  30. package/skills/first-tree/engine/rules/agent-integration.ts +3 -1
  31. package/skills/first-tree/engine/rules/framework.ts +2 -2
  32. package/skills/first-tree/engine/runtime/adapters.ts +6 -2
  33. package/skills/first-tree/engine/runtime/asset-loader.ts +143 -4
  34. package/skills/first-tree/engine/runtime/installer.ts +18 -12
  35. package/skills/first-tree/engine/upgrade.ts +99 -15
  36. package/skills/first-tree/engine/validators/nodes.ts +48 -3
  37. package/skills/first-tree/engine/verify.ts +61 -3
  38. package/skills/first-tree/references/maintainer-architecture.md +1 -1
  39. package/skills/first-tree/references/maintainer-build-and-distribution.md +3 -0
  40. package/skills/first-tree/references/maintainer-thin-cli.md +1 -1
  41. package/skills/first-tree/references/onboarding.md +57 -24
  42. package/skills/first-tree/references/source-map.md +3 -3
  43. package/skills/first-tree/references/upgrade-contract.md +62 -27
  44. package/skills/first-tree/scripts/check-skill-sync.sh +1 -1
  45. package/skills/first-tree/scripts/quick_validate.py +0 -0
  46. package/skills/first-tree/scripts/run-local-cli.sh +0 -0
  47. package/skills/first-tree/tests/asset-loader.test.ts +23 -1
  48. package/skills/first-tree/tests/helpers.ts +51 -7
  49. package/skills/first-tree/tests/init.test.ts +113 -8
  50. package/skills/first-tree/tests/repo.test.ts +113 -9
  51. package/skills/first-tree/tests/rules.test.ts +35 -14
  52. package/skills/first-tree/tests/skill-artifacts.test.ts +10 -0
  53. package/skills/first-tree/tests/thin-cli.test.ts +52 -7
  54. package/skills/first-tree/tests/upgrade.test.ts +39 -6
  55. package/skills/first-tree/tests/verify.test.ts +109 -10
  56. package/dist/onboarding-6Fr5Gkrk.js +0 -2
  57. package/dist/onboarding-B9zPGvvG.js +0 -10
  58. package/dist/repo-BTJG8BU1.js +0 -187
  59. package/dist/upgrade-COGgI7Rj.js +0 -96
@@ -0,0 +1,369 @@
1
+ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
2
+ import { basename, dirname, join, resolve } from "node:path";
3
+ //#region skills/first-tree/engine/runtime/asset-loader.ts
4
+ const SKILL_NAME = "first-tree";
5
+ const BUNDLED_SKILL_ROOT = join("skills", SKILL_NAME);
6
+ const SKILL_ROOT = join(".agents", "skills", SKILL_NAME);
7
+ const CLAUDE_SKILL_ROOT = join(".claude", "skills", SKILL_NAME);
8
+ const INSTALLED_SKILL_ROOTS = [SKILL_ROOT, CLAUDE_SKILL_ROOT];
9
+ join(SKILL_ROOT, "agents");
10
+ join(SKILL_ROOT, "references");
11
+ const FRAMEWORK_ASSET_ROOT = join(SKILL_ROOT, "assets", "framework");
12
+ join(FRAMEWORK_ASSET_ROOT, "manifest.json");
13
+ const FRAMEWORK_VERSION = join(FRAMEWORK_ASSET_ROOT, "VERSION");
14
+ const FRAMEWORK_TEMPLATES_DIR = join(FRAMEWORK_ASSET_ROOT, "templates");
15
+ const FRAMEWORK_WORKFLOWS_DIR = join(FRAMEWORK_ASSET_ROOT, "workflows");
16
+ join(FRAMEWORK_ASSET_ROOT, "prompts");
17
+ const FRAMEWORK_EXAMPLES_DIR = join(FRAMEWORK_ASSET_ROOT, "examples");
18
+ join(FRAMEWORK_ASSET_ROOT, "helpers");
19
+ const INSTALLED_PROGRESS = join(SKILL_ROOT, "progress.md");
20
+ const AGENT_INSTRUCTIONS_FILE = "AGENTS.md";
21
+ const LEGACY_AGENT_INSTRUCTIONS_FILE = "AGENT.md";
22
+ const AGENT_INSTRUCTIONS_TEMPLATE = "agents.md.template";
23
+ join(CLAUDE_SKILL_ROOT, "agents");
24
+ join(CLAUDE_SKILL_ROOT, "references");
25
+ const CLAUDE_FRAMEWORK_ASSET_ROOT = join(CLAUDE_SKILL_ROOT, "assets", "framework");
26
+ join(CLAUDE_FRAMEWORK_ASSET_ROOT, "manifest.json");
27
+ const CLAUDE_FRAMEWORK_VERSION = join(CLAUDE_FRAMEWORK_ASSET_ROOT, "VERSION");
28
+ join(CLAUDE_FRAMEWORK_ASSET_ROOT, "templates");
29
+ join(CLAUDE_FRAMEWORK_ASSET_ROOT, "workflows");
30
+ join(CLAUDE_FRAMEWORK_ASSET_ROOT, "prompts");
31
+ const CLAUDE_FRAMEWORK_EXAMPLES_DIR = join(CLAUDE_FRAMEWORK_ASSET_ROOT, "examples");
32
+ join(CLAUDE_FRAMEWORK_ASSET_ROOT, "helpers");
33
+ const CLAUDE_INSTALLED_PROGRESS = join(CLAUDE_SKILL_ROOT, "progress.md");
34
+ const LEGACY_REPO_SKILL_ROOT = join("skills", SKILL_NAME);
35
+ join(LEGACY_REPO_SKILL_ROOT, "agents");
36
+ join(LEGACY_REPO_SKILL_ROOT, "references");
37
+ const LEGACY_REPO_SKILL_ASSET_ROOT = join(LEGACY_REPO_SKILL_ROOT, "assets", "framework");
38
+ join(LEGACY_REPO_SKILL_ASSET_ROOT, "manifest.json");
39
+ const LEGACY_REPO_SKILL_VERSION = join(LEGACY_REPO_SKILL_ASSET_ROOT, "VERSION");
40
+ join(LEGACY_REPO_SKILL_ASSET_ROOT, "templates");
41
+ join(LEGACY_REPO_SKILL_ASSET_ROOT, "workflows");
42
+ join(LEGACY_REPO_SKILL_ASSET_ROOT, "prompts");
43
+ const LEGACY_REPO_SKILL_EXAMPLES_DIR = join(LEGACY_REPO_SKILL_ASSET_ROOT, "examples");
44
+ join(LEGACY_REPO_SKILL_ASSET_ROOT, "helpers");
45
+ const LEGACY_REPO_SKILL_PROGRESS = join(LEGACY_REPO_SKILL_ROOT, "progress.md");
46
+ const LEGACY_SKILL_NAME = "first-tree-cli-framework";
47
+ const LEGACY_SKILL_ROOT = join("skills", LEGACY_SKILL_NAME);
48
+ const LEGACY_SKILL_ASSET_ROOT = join(LEGACY_SKILL_ROOT, "assets", "framework");
49
+ const LEGACY_SKILL_VERSION = join(LEGACY_SKILL_ASSET_ROOT, "VERSION");
50
+ join(LEGACY_SKILL_ASSET_ROOT, "templates");
51
+ join(LEGACY_SKILL_ASSET_ROOT, "workflows");
52
+ join(LEGACY_SKILL_ASSET_ROOT, "prompts");
53
+ const LEGACY_SKILL_EXAMPLES_DIR = join(LEGACY_SKILL_ASSET_ROOT, "examples");
54
+ const LEGACY_SKILL_PROGRESS = join(LEGACY_SKILL_ROOT, "progress.md");
55
+ const LEGACY_FRAMEWORK_ROOT = ".context-tree";
56
+ const LEGACY_VERSION = join(LEGACY_FRAMEWORK_ROOT, "VERSION");
57
+ const LEGACY_PROGRESS = join(LEGACY_FRAMEWORK_ROOT, "progress.md");
58
+ join(LEGACY_FRAMEWORK_ROOT, "templates");
59
+ join(LEGACY_FRAMEWORK_ROOT, "workflows");
60
+ join(LEGACY_FRAMEWORK_ROOT, "prompts");
61
+ const LEGACY_EXAMPLES_DIR = join(LEGACY_FRAMEWORK_ROOT, "examples");
62
+ function pathExists(root, relPath) {
63
+ const fullPath = join(root, relPath);
64
+ try {
65
+ return existsSync(fullPath);
66
+ } catch {
67
+ return false;
68
+ }
69
+ }
70
+ function installedSkillRoots() {
71
+ return [...INSTALLED_SKILL_ROOTS];
72
+ }
73
+ function installedSkillRootsDisplay() {
74
+ return installedSkillRoots().map((root) => `\`${root}/\``).join(" and ");
75
+ }
76
+ function frameworkVersionCandidates() {
77
+ return [
78
+ FRAMEWORK_VERSION,
79
+ CLAUDE_FRAMEWORK_VERSION,
80
+ LEGACY_REPO_SKILL_VERSION,
81
+ LEGACY_SKILL_VERSION,
82
+ LEGACY_VERSION
83
+ ];
84
+ }
85
+ function progressFileCandidates() {
86
+ return [
87
+ INSTALLED_PROGRESS,
88
+ CLAUDE_INSTALLED_PROGRESS,
89
+ LEGACY_REPO_SKILL_PROGRESS,
90
+ LEGACY_SKILL_PROGRESS,
91
+ LEGACY_PROGRESS
92
+ ];
93
+ }
94
+ function agentInstructionsFileCandidates() {
95
+ return [AGENT_INSTRUCTIONS_FILE, LEGACY_AGENT_INSTRUCTIONS_FILE];
96
+ }
97
+ function resolveFirstExistingPath(root, candidates) {
98
+ for (const candidate of candidates) if (pathExists(root, candidate)) return candidate;
99
+ return null;
100
+ }
101
+ function detectFrameworkLayout(root) {
102
+ if (pathExists(root, FRAMEWORK_VERSION)) return "skill";
103
+ if (pathExists(root, CLAUDE_FRAMEWORK_VERSION)) return "claude-skill";
104
+ if (pathExists(root, LEGACY_REPO_SKILL_VERSION)) return "legacy-repo-skill";
105
+ if (pathExists(root, LEGACY_SKILL_VERSION)) return "legacy-skill";
106
+ if (pathExists(root, LEGACY_VERSION)) return "legacy";
107
+ return null;
108
+ }
109
+ //#endregion
110
+ //#region skills/first-tree/engine/repo.ts
111
+ const FRONTMATTER_RE = /^---\s*\n(.*?)\n---/s;
112
+ const OWNERS_RE = /^owners:\s*\[([^\]]*)\]/m;
113
+ const TITLE_RE = /^title:\s*['"]?(.+?)['"]?\s*$/m;
114
+ const EMPTY_REPO_ENTRY_ALLOWLIST = new Set([
115
+ ".DS_Store",
116
+ ".editorconfig",
117
+ ".gitattributes",
118
+ ".github",
119
+ ".gitignore",
120
+ "AGENT.md",
121
+ "AGENTS.md",
122
+ "CLAUDE.md",
123
+ "LICENSE",
124
+ "LICENSE.md",
125
+ "LICENSE.txt",
126
+ "README",
127
+ "README.md",
128
+ "README.txt"
129
+ ]);
130
+ const SOURCE_FILE_HINTS = new Set([
131
+ ".gitmodules",
132
+ "Cargo.toml",
133
+ "Dockerfile",
134
+ "Gemfile",
135
+ "Makefile",
136
+ "bun.lock",
137
+ "bun.lockb",
138
+ "docker-compose.yml",
139
+ "go.mod",
140
+ "package-lock.json",
141
+ "package.json",
142
+ "pnpm-lock.yaml",
143
+ "pyproject.toml",
144
+ "requirements.txt",
145
+ "tsconfig.json",
146
+ "uv.lock",
147
+ "vite.config.ts",
148
+ "vite.config.js"
149
+ ]);
150
+ const SOURCE_DIR_HINTS = new Set([
151
+ "app",
152
+ "apps",
153
+ "backend",
154
+ "cli",
155
+ "client",
156
+ "docs",
157
+ "e2e",
158
+ "frontend",
159
+ "lib",
160
+ "packages",
161
+ "scripts",
162
+ "server",
163
+ "src",
164
+ "test",
165
+ "tests"
166
+ ]);
167
+ const FRAMEWORK_END_MARKER = "<!-- END CONTEXT-TREE FRAMEWORK -->";
168
+ function hasGitMetadata(root) {
169
+ try {
170
+ const stat = statSync(join(root, ".git"));
171
+ return stat.isDirectory() || stat.isFile();
172
+ } catch {
173
+ return false;
174
+ }
175
+ }
176
+ function discoverGitRoot(start) {
177
+ let dir = start;
178
+ while (true) {
179
+ if (hasGitMetadata(dir)) return dir;
180
+ const parent = dirname(dir);
181
+ if (parent === dir) return null;
182
+ dir = parent;
183
+ }
184
+ }
185
+ var Repo = class {
186
+ root;
187
+ constructor(root) {
188
+ const start = resolve(root ?? process.cwd());
189
+ this.root = root === void 0 ? discoverGitRoot(start) ?? start : start;
190
+ }
191
+ pathExists(relPath) {
192
+ return existsSync(join(this.root, relPath));
193
+ }
194
+ fileContains(relPath, text) {
195
+ const fullPath = join(this.root, relPath);
196
+ try {
197
+ if (!statSync(fullPath).isFile()) return false;
198
+ return readFileSync(fullPath, "utf-8").includes(text);
199
+ } catch {
200
+ return false;
201
+ }
202
+ }
203
+ readFile(relPath) {
204
+ try {
205
+ return readFileSync(join(this.root, relPath), "utf-8");
206
+ } catch {
207
+ return null;
208
+ }
209
+ }
210
+ frontmatter(relPath) {
211
+ const text = this.readFile(relPath);
212
+ if (text === null) return null;
213
+ const m = text.match(FRONTMATTER_RE);
214
+ if (!m) return null;
215
+ const fm = m[1];
216
+ const result = {};
217
+ const titleM = fm.match(TITLE_RE);
218
+ if (titleM) result.title = titleM[1].trim();
219
+ const ownersM = fm.match(OWNERS_RE);
220
+ if (ownersM) {
221
+ const raw = ownersM[1].trim();
222
+ result.owners = raw ? raw.split(",").map((o) => o.trim()).filter(Boolean) : [];
223
+ }
224
+ return result.title !== void 0 || result.owners !== void 0 ? result : null;
225
+ }
226
+ anyAgentConfig() {
227
+ return [".claude/settings.json", ".codex/config.json"].some((c) => this.pathExists(c));
228
+ }
229
+ installedSkillRoots() {
230
+ return installedSkillRoots();
231
+ }
232
+ missingInstalledSkillRoots() {
233
+ return this.installedSkillRoots().filter((root) => !this.pathExists(join(root, "SKILL.md")) || !this.pathExists(join(root, "assets", "framework", "VERSION")));
234
+ }
235
+ hasCurrentInstalledSkill() {
236
+ return this.missingInstalledSkillRoots().length === 0;
237
+ }
238
+ isGitRepo() {
239
+ return hasGitMetadata(this.root);
240
+ }
241
+ hasFramework() {
242
+ return this.frameworkLayout() !== null;
243
+ }
244
+ frameworkLayout() {
245
+ return detectFrameworkLayout(this.root);
246
+ }
247
+ readVersion() {
248
+ const versionPath = resolveFirstExistingPath(this.root, frameworkVersionCandidates());
249
+ if (versionPath === null) return null;
250
+ const text = this.readFile(versionPath);
251
+ return text ? text.trim() : null;
252
+ }
253
+ progressPath() {
254
+ return resolveFirstExistingPath(this.root, progressFileCandidates());
255
+ }
256
+ preferredProgressPath() {
257
+ const layout = this.frameworkLayout();
258
+ if (layout === "legacy") return LEGACY_PROGRESS;
259
+ if (layout === "legacy-skill") return LEGACY_SKILL_PROGRESS;
260
+ if (layout === "legacy-repo-skill") return LEGACY_REPO_SKILL_PROGRESS;
261
+ if (layout === "claude-skill") return CLAUDE_INSTALLED_PROGRESS;
262
+ return INSTALLED_PROGRESS;
263
+ }
264
+ frameworkVersionPath() {
265
+ const layout = this.frameworkLayout();
266
+ if (layout === "legacy") return LEGACY_VERSION;
267
+ if (layout === "legacy-skill") return LEGACY_SKILL_VERSION;
268
+ if (layout === "legacy-repo-skill") return LEGACY_REPO_SKILL_VERSION;
269
+ if (layout === "claude-skill") return CLAUDE_FRAMEWORK_VERSION;
270
+ return FRAMEWORK_VERSION;
271
+ }
272
+ agentInstructionsPath() {
273
+ return resolveFirstExistingPath(this.root, agentInstructionsFileCandidates());
274
+ }
275
+ hasCanonicalAgentInstructionsFile() {
276
+ return this.pathExists(AGENT_INSTRUCTIONS_FILE);
277
+ }
278
+ hasLegacyAgentInstructionsFile() {
279
+ return this.pathExists(LEGACY_AGENT_INSTRUCTIONS_FILE);
280
+ }
281
+ hasDuplicateAgentInstructionsFiles() {
282
+ return this.hasCanonicalAgentInstructionsFile() && this.hasLegacyAgentInstructionsFile();
283
+ }
284
+ readAgentInstructions() {
285
+ const relPath = this.agentInstructionsPath();
286
+ if (relPath === null) return null;
287
+ return this.readFile(relPath);
288
+ }
289
+ hasAgentInstructionsMarkers() {
290
+ const text = this.readAgentInstructions();
291
+ if (text === null) return false;
292
+ return text.includes("<!-- BEGIN CONTEXT-TREE FRAMEWORK") && text.includes("<!-- END CONTEXT-TREE FRAMEWORK -->");
293
+ }
294
+ hasMembers() {
295
+ const membersDir = join(this.root, "members");
296
+ try {
297
+ if (!statSync(membersDir).isDirectory()) return false;
298
+ } catch {
299
+ return false;
300
+ }
301
+ return existsSync(join(membersDir, "NODE.md"));
302
+ }
303
+ memberCount() {
304
+ const membersDir = join(this.root, "members");
305
+ try {
306
+ if (!statSync(membersDir).isDirectory()) return 0;
307
+ } catch {
308
+ return 0;
309
+ }
310
+ let count = 0;
311
+ const walk = (dir) => {
312
+ for (const entry of readdirSync(dir)) {
313
+ const childPath = join(dir, entry);
314
+ try {
315
+ if (!statSync(childPath).isDirectory()) continue;
316
+ } catch {
317
+ continue;
318
+ }
319
+ if (existsSync(join(childPath, "NODE.md"))) count++;
320
+ walk(childPath);
321
+ }
322
+ };
323
+ walk(membersDir);
324
+ return count;
325
+ }
326
+ hasPlaceholderNode() {
327
+ return this.fileContains("NODE.md", "<!-- PLACEHOLDER");
328
+ }
329
+ repoName() {
330
+ return basename(this.root);
331
+ }
332
+ topLevelEntries() {
333
+ try {
334
+ return readdirSync(this.root).filter((entry) => entry !== ".git");
335
+ } catch {
336
+ return [];
337
+ }
338
+ }
339
+ looksLikeTreeRepo() {
340
+ if (this.pathExists("package.json") && this.pathExists("src/cli.ts") && this.pathExists("skills/first-tree/SKILL.md") && this.progressPath() === null && this.frontmatter("NODE.md") === null && !this.hasAgentInstructionsMarkers() && !this.pathExists("members/NODE.md")) return false;
341
+ return this.progressPath() !== null || this.hasFramework() || this.hasAgentInstructionsMarkers() || this.pathExists("members/NODE.md") || this.frontmatter("NODE.md") !== null;
342
+ }
343
+ isLikelyEmptyRepo() {
344
+ return this.topLevelEntries().filter((entry) => !EMPTY_REPO_ENTRY_ALLOWLIST.has(entry)).length === 0;
345
+ }
346
+ isLikelySourceRepo() {
347
+ if (this.looksLikeTreeRepo()) return false;
348
+ const entries = this.topLevelEntries().filter((entry) => !EMPTY_REPO_ENTRY_ALLOWLIST.has(entry));
349
+ if (entries.length === 0) return false;
350
+ let directoryCount = 0;
351
+ for (const entry of entries) {
352
+ if (SOURCE_FILE_HINTS.has(entry)) return true;
353
+ if (isDirectory(this.root, entry)) {
354
+ directoryCount += 1;
355
+ if (SOURCE_DIR_HINTS.has(entry)) return true;
356
+ }
357
+ }
358
+ return directoryCount >= 2 || entries.length >= 4;
359
+ }
360
+ };
361
+ function isDirectory(root, relPath) {
362
+ try {
363
+ return statSync(join(root, relPath)).isDirectory();
364
+ } catch {
365
+ return false;
366
+ }
367
+ }
368
+ //#endregion
369
+ export { SKILL_NAME as C, LEGACY_SKILL_ROOT as S, installedSkillRootsDisplay as T, LEGACY_FRAMEWORK_ROOT as _, BUNDLED_SKILL_ROOT as a, LEGACY_SKILL_EXAMPLES_DIR as b, FRAMEWORK_ASSET_ROOT as c, FRAMEWORK_VERSION as d, FRAMEWORK_WORKFLOWS_DIR as f, LEGACY_EXAMPLES_DIR as g, LEGACY_AGENT_INSTRUCTIONS_FILE as h, AGENT_INSTRUCTIONS_TEMPLATE as i, FRAMEWORK_EXAMPLES_DIR as l, INSTALLED_SKILL_ROOTS as m, Repo as n, CLAUDE_FRAMEWORK_EXAMPLES_DIR as o, INSTALLED_PROGRESS as p, AGENT_INSTRUCTIONS_FILE as r, CLAUDE_SKILL_ROOT as s, FRAMEWORK_END_MARKER as t, FRAMEWORK_TEMPLATES_DIR as u, LEGACY_REPO_SKILL_EXAMPLES_DIR as v, SKILL_ROOT as w, LEGACY_SKILL_NAME as x, LEGACY_REPO_SKILL_ROOT as y };
@@ -0,0 +1,140 @@
1
+ import { S as LEGACY_SKILL_ROOT, T as installedSkillRootsDisplay, _ as LEGACY_FRAMEWORK_ROOT, d as FRAMEWORK_VERSION, f as FRAMEWORK_WORKFLOWS_DIR, h as LEGACY_AGENT_INSTRUCTIONS_FILE, i as AGENT_INSTRUCTIONS_TEMPLATE, n as Repo, p as INSTALLED_PROGRESS, r as AGENT_INSTRUCTIONS_FILE, s as CLAUDE_SKILL_ROOT, u as FRAMEWORK_TEMPLATES_DIR, w as SKILL_ROOT, y as LEGACY_REPO_SKILL_ROOT } from "./repo-DkR12VUv.js";
2
+ import { i as resolveCanonicalSkillRoot, r as resolveBundledPackageRoot, t as copyCanonicalSkill } from "./installer-UgNasLjl.js";
3
+ import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
4
+ import { dirname, join, resolve } from "node:path";
5
+ //#region skills/first-tree/engine/runtime/upgrader.ts
6
+ function compareFrameworkVersions(left, right) {
7
+ const result = left.localeCompare(right, void 0, {
8
+ numeric: true,
9
+ sensitivity: "base"
10
+ });
11
+ if (result < 0) return -1;
12
+ if (result > 0) return 1;
13
+ return 0;
14
+ }
15
+ function readSourceVersion(sourceRoot) {
16
+ const versionPath = join(resolveCanonicalSkillRoot(sourceRoot), "assets", "framework", "VERSION");
17
+ try {
18
+ return readFileSync(versionPath, "utf-8").trim();
19
+ } catch {
20
+ return null;
21
+ }
22
+ }
23
+ //#endregion
24
+ //#region skills/first-tree/engine/upgrade.ts
25
+ const UPGRADE_USAGE = `usage: context-tree upgrade [--tree-path PATH]
26
+
27
+ Options:
28
+ --tree-path PATH Upgrade a tree repo from another working directory
29
+ --help Show this help message
30
+ `;
31
+ function writeProgress(repo, content) {
32
+ const progressPath = join(repo.root, repo.preferredProgressPath());
33
+ mkdirSync(dirname(progressPath), { recursive: true });
34
+ writeFileSync(progressPath, content);
35
+ }
36
+ function formatUpgradeTaskList(repo, localVersion, packagedVersion, layout) {
37
+ const lines = [
38
+ `# Context Tree Upgrade — v${localVersion} -> v${packagedVersion}\n`,
39
+ "## Installed Skill",
40
+ `- [ ] Review local customizations under ${installedSkillRootsDisplay()} and reapply them if needed`,
41
+ `- [ ] Re-copy any workflow updates you want from \`${FRAMEWORK_WORKFLOWS_DIR}/\` into \`.github/workflows/\``,
42
+ `- [ ] Re-check any local agent setup that references \`${CLAUDE_SKILL_ROOT}/assets/framework/examples/\` or \`${CLAUDE_SKILL_ROOT}/assets/framework/helpers/\``,
43
+ `- [ ] Re-check any repo scripts or workflow files that reference \`${SKILL_ROOT}/assets/framework/\``,
44
+ ""
45
+ ];
46
+ const migrationTasks = [];
47
+ if (layout === "legacy") migrationTasks.push("- [ ] Remove any stale `.context-tree/` references from repo-specific docs, scripts, or workflow files");
48
+ if (layout === "legacy-repo-skill") lines.push("## Migration", `- [ ] Remove any stale \`${LEGACY_REPO_SKILL_ROOT}/\` references from repo-specific docs, scripts, workflow files, or agent config`, "");
49
+ if (layout === "legacy-skill") migrationTasks.push(`- [ ] Remove any stale \`${LEGACY_SKILL_ROOT}/\` references from repo-specific docs, scripts, workflow files, or agent config`);
50
+ if (repo.hasCanonicalAgentInstructionsFile() && repo.hasLegacyAgentInstructionsFile()) migrationTasks.push(`- [ ] Merge any remaining user-authored content from \`${LEGACY_AGENT_INSTRUCTIONS_FILE}\` into \`${AGENT_INSTRUCTIONS_FILE}\`, then delete the legacy file`);
51
+ else if (repo.hasLegacyAgentInstructionsFile()) migrationTasks.push(`- [ ] Rename \`${LEGACY_AGENT_INSTRUCTIONS_FILE}\` to \`${AGENT_INSTRUCTIONS_FILE}\` to use the canonical agent instructions filename`);
52
+ if (migrationTasks.length > 0) lines.push("## Migration", ...migrationTasks, "");
53
+ if (repo.hasAgentInstructionsMarkers()) lines.push("## Agent Instructions", `- [ ] Compare the framework section in \`${AGENT_INSTRUCTIONS_FILE}\` with \`${FRAMEWORK_TEMPLATES_DIR}/${AGENT_INSTRUCTIONS_TEMPLATE}\` and update the content between the markers if needed`, "");
54
+ lines.push("## Verification", `- [ ] \`${FRAMEWORK_VERSION}\` reads \`${packagedVersion}\``, "- [ ] `context-tree verify` passes", "", "---", "", `**Important:** As you complete each task, check it off in \`${INSTALLED_PROGRESS}\` by changing \`- [ ]\` to \`- [x]\`. Run \`context-tree verify\` when done — it will fail if any items remain unchecked.`, "");
55
+ return lines.join("\n");
56
+ }
57
+ function runUpgrade(repo, options) {
58
+ const workingRepo = repo ?? new Repo();
59
+ if (workingRepo.isLikelySourceRepo() && !workingRepo.looksLikeTreeRepo()) {
60
+ console.error("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.");
61
+ return 1;
62
+ }
63
+ if (!workingRepo.hasFramework()) {
64
+ console.error("Error: no installed framework skill found. Run `context-tree init` first.");
65
+ return 1;
66
+ }
67
+ const layout = workingRepo.frameworkLayout();
68
+ if (layout === null) {
69
+ console.error("Error: no installed framework skill found. Run `context-tree init` first.");
70
+ return 1;
71
+ }
72
+ const localVersion = workingRepo.readVersion() ?? "unknown";
73
+ console.log(`Local framework version: ${localVersion}\n`);
74
+ console.log("Checking the framework skill bundled with this first-tree package...");
75
+ let sourceRoot;
76
+ try {
77
+ sourceRoot = options?.sourceRoot ?? resolveBundledPackageRoot();
78
+ } catch (err) {
79
+ const message = err instanceof Error ? err.message : "unknown error";
80
+ console.error(`Error: ${message}`);
81
+ return 1;
82
+ }
83
+ const packagedVersion = readSourceVersion(sourceRoot);
84
+ if (packagedVersion === null) {
85
+ console.log("Could not read the bundled framework version. Reinstall or update `first-tree` and try again.");
86
+ return 1;
87
+ }
88
+ if (localVersion !== "unknown" && compareFrameworkVersions(localVersion, packagedVersion) > 0) {
89
+ console.log("The installed framework is newer than the skill bundled with this `first-tree` package. Install a newer package version before running `context-tree upgrade`.");
90
+ return 1;
91
+ }
92
+ const missingInstalledRoots = workingRepo.missingInstalledSkillRoots();
93
+ if (layout === "skill" && missingInstalledRoots.length === 0 && packagedVersion === localVersion) {
94
+ console.log(`Already up to date with the bundled skill (${FRAMEWORK_VERSION} = ${localVersion}).`);
95
+ return 0;
96
+ }
97
+ copyCanonicalSkill(sourceRoot, workingRepo.root);
98
+ if (layout === "legacy") {
99
+ rmSync(join(workingRepo.root, LEGACY_FRAMEWORK_ROOT), {
100
+ recursive: true,
101
+ force: true
102
+ });
103
+ console.log(`Migrated legacy .context-tree/ layout to ${installedSkillRootsDisplay()}.`);
104
+ } else if (layout === "legacy-repo-skill") console.log(`Migrated legacy ${LEGACY_REPO_SKILL_ROOT}/ layout to ${installedSkillRootsDisplay()}.`);
105
+ else if (layout === "legacy-skill") console.log(`Migrated ${LEGACY_SKILL_ROOT}/ to ${installedSkillRootsDisplay()}.`);
106
+ else if (missingInstalledRoots.length > 0) console.log(`Repaired missing installed skill roots (${missingInstalledRoots.map((root) => `${root}/`).join(", ")}) and refreshed ${installedSkillRootsDisplay()} from the bundled first-tree package.`);
107
+ else console.log(`Refreshed ${installedSkillRootsDisplay()} from the bundled first-tree package.`);
108
+ const output = formatUpgradeTaskList(workingRepo, localVersion, packagedVersion, layout);
109
+ console.log(`\n${output}`);
110
+ writeProgress(workingRepo, output);
111
+ console.log(`Progress file written to ${workingRepo.preferredProgressPath()}`);
112
+ return 0;
113
+ }
114
+ function runUpgradeCli(args = []) {
115
+ if (args.includes("--help") || args.includes("-h")) {
116
+ console.log(UPGRADE_USAGE);
117
+ return 0;
118
+ }
119
+ let treePath;
120
+ for (let index = 0; index < args.length; index += 1) {
121
+ const arg = args[index];
122
+ if (arg === "--tree-path") {
123
+ const value = args[index + 1];
124
+ if (!value) {
125
+ console.error("Missing value for --tree-path");
126
+ console.log(UPGRADE_USAGE);
127
+ return 1;
128
+ }
129
+ treePath = value;
130
+ index += 1;
131
+ continue;
132
+ }
133
+ console.error(`Unknown upgrade option: ${arg}`);
134
+ console.log(UPGRADE_USAGE);
135
+ return 1;
136
+ }
137
+ return runUpgrade(treePath ? new Repo(resolve(process.cwd(), treePath)) : void 0);
138
+ }
139
+ //#endregion
140
+ export { runUpgradeCli as runUpgrade };
@@ -1,6 +1,6 @@
1
- import { n as Repo } from "./repo-BTJG8BU1.js";
1
+ import { S as LEGACY_SKILL_ROOT, h as LEGACY_AGENT_INSTRUCTIONS_FILE, n as Repo, r as AGENT_INSTRUCTIONS_FILE, w as SKILL_ROOT } from "./repo-DkR12VUv.js";
2
2
  import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
3
- import { join, relative } from "node:path";
3
+ import { join, relative, resolve } from "node:path";
4
4
  //#region skills/first-tree/engine/validators/members.ts
5
5
  const FRONTMATTER_RE$1 = /^---\s*\n(.*?)\n---/s;
6
6
  const VALID_TYPES = new Set([
@@ -133,7 +133,11 @@ const SOFT_LINKS_BLOCK_RE = /^soft_links:\s*\n((?:\s+-\s+.+\n?)+)/m;
133
133
  const TITLE_RE = /^title:\s*['"]?(.+?)['"]?\s*$/m;
134
134
  const GITHUB_USER_RE = /^[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,37}[a-zA-Z0-9])?$/;
135
135
  const SKIP = new Set(["node_modules", "__pycache__"]);
136
- const SKIP_FILES = new Set(["AGENT.md", "CLAUDE.md"]);
136
+ const SKIP_FILES = new Set([
137
+ AGENT_INSTRUCTIONS_FILE,
138
+ LEGACY_AGENT_INSTRUCTIONS_FILE,
139
+ "CLAUDE.md"
140
+ ]);
137
141
  const MIN_BODY_LENGTH = 20;
138
142
  var Findings = class {
139
143
  errors = [];
@@ -181,8 +185,23 @@ function setTreeRoot(root) {
181
185
  function rel(path) {
182
186
  return relative(treeRoot, path);
183
187
  }
188
+ function isInstalledSkillPath(relPath) {
189
+ return relPath === SKILL_ROOT || relPath.startsWith(`${SKILL_ROOT}/`) || relPath === LEGACY_SKILL_ROOT || relPath.startsWith(`${LEGACY_SKILL_ROOT}/`);
190
+ }
191
+ function isFrameworkContainerDir(relPath, fullPath) {
192
+ if (relPath !== "skills") return false;
193
+ try {
194
+ const entries = readdirSync(fullPath).filter((entry) => !entry.startsWith("."));
195
+ if (entries.length === 0) return false;
196
+ return entries.every((entry) => entry === "first-tree" || entry === "first-tree-cli-framework");
197
+ } catch {
198
+ return false;
199
+ }
200
+ }
184
201
  function shouldSkip(path) {
185
- return relative(treeRoot, path).split("/").some((part) => SKIP.has(part) || part.startsWith("."));
202
+ const relPath = relative(treeRoot, path);
203
+ if (relPath.split("/").some((part) => SKIP.has(part) || part.startsWith("."))) return true;
204
+ return isInstalledSkillPath(relPath) || isFrameworkContainerDir(relPath, path);
186
205
  }
187
206
  function readText(path) {
188
207
  if (!textCache.has(path)) try {
@@ -469,6 +488,12 @@ function runValidateNodes(root) {
469
488
  //#endregion
470
489
  //#region skills/first-tree/engine/verify.ts
471
490
  const UNCHECKED_RE = /^- \[ \] (.+)$/gm;
491
+ const VERIFY_USAGE = `usage: context-tree verify [--tree-path PATH]
492
+
493
+ Options:
494
+ --tree-path PATH Verify a tree repo from another working directory
495
+ --help Show this help message
496
+ `;
472
497
  function check(label, passed) {
473
498
  console.log(` ${passed ? "✓" : "✗"} [${passed ? "PASS" : "FAIL"}] ${label}`);
474
499
  return passed;
@@ -490,6 +515,10 @@ function defaultNodeValidator(root) {
490
515
  function runVerify(repo, nodeValidator) {
491
516
  const r = repo ?? new Repo();
492
517
  const validate = nodeValidator ?? defaultNodeValidator;
518
+ if (r.isLikelySourceRepo() && !r.looksLikeTreeRepo()) {
519
+ console.error("Error: no installed framework skill found here. This looks like a source/workspace repo. Run `context-tree init` to create a dedicated tree repo, or pass `--tree-path` to verify an existing tree repo.");
520
+ return 1;
521
+ }
493
522
  let allPassed = true;
494
523
  const progressPath = r.progressPath() ?? r.preferredProgressPath();
495
524
  const frameworkVersionPath = r.frameworkVersionPath();
@@ -506,7 +535,13 @@ function runVerify(repo, nodeValidator) {
506
535
  allPassed = check(`${frameworkVersionPath} exists`, r.hasFramework()) && allPassed;
507
536
  const fm = r.frontmatter("NODE.md");
508
537
  allPassed = check("Root NODE.md has valid frontmatter (title, owners)", fm !== null && fm.title !== void 0 && fm.owners !== void 0) && allPassed;
509
- allPassed = check("AGENT.md exists with framework markers", r.hasAgentMdMarkers()) && allPassed;
538
+ const hasCanonicalAgentInstructions = r.hasCanonicalAgentInstructionsFile();
539
+ const hasLegacyAgentInstructions = r.hasLegacyAgentInstructionsFile();
540
+ if (hasLegacyAgentInstructions) {
541
+ const followUp = hasCanonicalAgentInstructions ? `Remove legacy \`${LEGACY_AGENT_INSTRUCTIONS_FILE}\` after confirming its contents are in \`${AGENT_INSTRUCTIONS_FILE}\`.` : `Rename \`${LEGACY_AGENT_INSTRUCTIONS_FILE}\` to \`${AGENT_INSTRUCTIONS_FILE}\`.`;
542
+ console.log(` Legacy agent instructions detected. ${followUp}\n`);
543
+ }
544
+ allPassed = check(`AGENTS.md is the only agent instructions file and has framework markers`, hasCanonicalAgentInstructions && !hasLegacyAgentInstructions && r.hasAgentInstructionsMarkers()) && allPassed;
510
545
  const { exitCode } = validate(r.root);
511
546
  allPassed = check("Node validation passes", exitCode === 0) && allPassed;
512
547
  allPassed = check("Member validation passes", runValidateMembers(r.root).exitCode === 0) && allPassed;
@@ -515,5 +550,30 @@ function runVerify(repo, nodeValidator) {
515
550
  else console.log("Some checks failed. See above for details.");
516
551
  return allPassed ? 0 : 1;
517
552
  }
553
+ function runVerifyCli(args = []) {
554
+ if (args.includes("--help") || args.includes("-h")) {
555
+ console.log(VERIFY_USAGE);
556
+ return 0;
557
+ }
558
+ let treePath;
559
+ for (let index = 0; index < args.length; index += 1) {
560
+ const arg = args[index];
561
+ if (arg === "--tree-path") {
562
+ const value = args[index + 1];
563
+ if (!value) {
564
+ console.error("Missing value for --tree-path");
565
+ console.log(VERIFY_USAGE);
566
+ return 1;
567
+ }
568
+ treePath = value;
569
+ index += 1;
570
+ continue;
571
+ }
572
+ console.error(`Unknown verify option: ${arg}`);
573
+ console.log(VERIFY_USAGE);
574
+ return 1;
575
+ }
576
+ return runVerify(treePath ? new Repo(resolve(process.cwd(), treePath)) : void 0);
577
+ }
518
578
  //#endregion
519
- export { runVerify };
579
+ export { runVerifyCli as runVerify };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "first-tree",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "description": "CLI tools for Context Tree — the living source of truth for your organization.",
5
5
  "homepage": "https://github.com/agent-team-foundation/first-tree#readme",
6
6
  "bugs": {
@@ -19,7 +19,7 @@
19
19
  ],
20
20
  "type": "module",
21
21
  "bin": {
22
- "context-tree": "./dist/cli.js"
22
+ "context-tree": "dist/cli.js"
23
23
  },
24
24
  "imports": {
25
25
  "#skill/*": "./skills/first-tree/*",
@@ -30,6 +30,15 @@
30
30
  "dist",
31
31
  "skills/first-tree"
32
32
  ],
33
+ "scripts": {
34
+ "build": "tsdown",
35
+ "prepack": "pnpm build",
36
+ "test": "vitest run",
37
+ "eval": "vitest run --config vitest.eval.config.ts",
38
+ "typecheck": "tsc --noEmit",
39
+ "validate:skill": "python3 ./skills/first-tree/scripts/quick_validate.py ./skills/first-tree && bash ./skills/first-tree/scripts/check-skill-sync.sh"
40
+ },
41
+ "packageManager": "pnpm@10.25.0",
33
42
  "license": "Apache-2.0",
34
43
  "devDependencies": {
35
44
  "@types/node": "^25.5.0",
@@ -37,12 +46,5 @@
37
46
  "typescript": "^5.8.0",
38
47
  "vitest": "^3.2.0",
39
48
  "yaml": "^2.8.3"
40
- },
41
- "scripts": {
42
- "build": "tsdown",
43
- "test": "vitest run",
44
- "eval": "vitest run --config vitest.eval.config.ts",
45
- "typecheck": "tsc --noEmit",
46
- "validate:skill": "python3 ./skills/first-tree/scripts/quick_validate.py ./skills/first-tree && bash ./skills/first-tree/scripts/check-skill-sync.sh"
47
49
  }
48
- }
50
+ }