first-tree 0.0.2 → 0.0.4

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 (80) hide show
  1. package/README.md +116 -40
  2. package/dist/cli.js +46 -17
  3. package/dist/help-Dtdj91HJ.js +25 -0
  4. package/dist/init--VepFe6N.js +403 -0
  5. package/dist/installer-cH7N4RNj.js +47 -0
  6. package/dist/onboarding-C9cYSE6F.js +2 -0
  7. package/dist/onboarding-CPP8fF4D.js +10 -0
  8. package/dist/repo-DY57bMqr.js +318 -0
  9. package/dist/upgrade-Cgx_K2HM.js +135 -0
  10. package/dist/{verify-CSRIkuoM.js → verify-mC9ZTd1f.js} +118 -29
  11. package/package.json +33 -10
  12. package/skills/first-tree/SKILL.md +113 -0
  13. package/skills/first-tree/agents/openai.yaml +4 -0
  14. package/skills/first-tree/assets/framework/VERSION +1 -0
  15. package/skills/first-tree/assets/framework/examples/claude-code/README.md +14 -0
  16. package/skills/first-tree/assets/framework/examples/claude-code/settings.json +14 -0
  17. package/skills/first-tree/assets/framework/helpers/generate-codeowners.ts +224 -0
  18. package/skills/first-tree/assets/framework/helpers/inject-tree-context.sh +15 -0
  19. package/skills/first-tree/assets/framework/helpers/run-review.ts +193 -0
  20. package/skills/first-tree/assets/framework/manifest.json +11 -0
  21. package/skills/first-tree/assets/framework/prompts/pr-review.md +38 -0
  22. package/skills/first-tree/assets/framework/templates/agents.md.template +49 -0
  23. package/skills/first-tree/assets/framework/templates/member-node.md.template +18 -0
  24. package/skills/first-tree/assets/framework/templates/members-domain.md.template +45 -0
  25. package/skills/first-tree/assets/framework/templates/root-node.md.template +41 -0
  26. package/skills/first-tree/assets/framework/workflows/codeowners.yml +31 -0
  27. package/skills/first-tree/assets/framework/workflows/pr-review.yml +146 -0
  28. package/skills/first-tree/assets/framework/workflows/validate.yml +19 -0
  29. package/skills/first-tree/engine/commands/help.ts +32 -0
  30. package/skills/first-tree/engine/commands/init.ts +1 -0
  31. package/skills/first-tree/engine/commands/upgrade.ts +1 -0
  32. package/skills/first-tree/engine/commands/verify.ts +1 -0
  33. package/skills/first-tree/engine/init.ts +414 -0
  34. package/skills/first-tree/engine/onboarding.ts +10 -0
  35. package/skills/first-tree/engine/repo.ts +360 -0
  36. package/skills/first-tree/engine/rules/agent-instructions.ts +59 -0
  37. package/skills/first-tree/engine/rules/agent-integration.ts +19 -0
  38. package/skills/first-tree/engine/rules/ci-validation.ts +72 -0
  39. package/skills/first-tree/engine/rules/framework.ts +13 -0
  40. package/skills/first-tree/engine/rules/index.ts +41 -0
  41. package/skills/first-tree/engine/rules/members.ts +21 -0
  42. package/skills/first-tree/engine/rules/populate-tree.ts +36 -0
  43. package/skills/first-tree/engine/rules/root-node.ts +41 -0
  44. package/skills/first-tree/engine/runtime/adapters.ts +22 -0
  45. package/skills/first-tree/engine/runtime/asset-loader.ts +141 -0
  46. package/skills/first-tree/engine/runtime/installer.ts +82 -0
  47. package/skills/first-tree/engine/runtime/upgrader.ts +23 -0
  48. package/skills/first-tree/engine/upgrade.ts +233 -0
  49. package/skills/first-tree/engine/validators/members.ts +215 -0
  50. package/skills/first-tree/engine/validators/nodes.ts +559 -0
  51. package/skills/first-tree/engine/verify.ts +155 -0
  52. package/skills/first-tree/references/about.md +36 -0
  53. package/skills/first-tree/references/maintainer-architecture.md +59 -0
  54. package/skills/first-tree/references/maintainer-build-and-distribution.md +59 -0
  55. package/skills/first-tree/references/maintainer-testing.md +58 -0
  56. package/skills/first-tree/references/maintainer-thin-cli.md +38 -0
  57. package/skills/first-tree/references/onboarding.md +185 -0
  58. package/skills/first-tree/references/ownership-and-naming.md +94 -0
  59. package/skills/first-tree/references/principles.md +113 -0
  60. package/skills/first-tree/references/source-map.md +94 -0
  61. package/skills/first-tree/references/upgrade-contract.md +94 -0
  62. package/skills/first-tree/scripts/check-skill-sync.sh +133 -0
  63. package/skills/first-tree/scripts/quick_validate.py +95 -0
  64. package/skills/first-tree/scripts/run-local-cli.sh +35 -0
  65. package/skills/first-tree/tests/asset-loader.test.ts +75 -0
  66. package/skills/first-tree/tests/generate-codeowners.test.ts +94 -0
  67. package/skills/first-tree/tests/helpers.ts +169 -0
  68. package/skills/first-tree/tests/init.test.ts +250 -0
  69. package/skills/first-tree/tests/repo.test.ts +440 -0
  70. package/skills/first-tree/tests/rules.test.ts +413 -0
  71. package/skills/first-tree/tests/run-review.test.ts +155 -0
  72. package/skills/first-tree/tests/skill-artifacts.test.ts +311 -0
  73. package/skills/first-tree/tests/thin-cli.test.ts +104 -0
  74. package/skills/first-tree/tests/upgrade.test.ts +103 -0
  75. package/skills/first-tree/tests/validate-members.test.ts +224 -0
  76. package/skills/first-tree/tests/validate-nodes.test.ts +198 -0
  77. package/skills/first-tree/tests/verify.test.ts +241 -0
  78. package/dist/init-CE_944sb.js +0 -283
  79. package/dist/repo-BByc3VvM.js +0 -111
  80. package/dist/upgrade-Chr7z0CY.js +0 -82
@@ -0,0 +1,318 @@
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 SKILL_ROOT = join("skills", SKILL_NAME);
6
+ join(SKILL_ROOT, "agents");
7
+ join(SKILL_ROOT, "references");
8
+ const FRAMEWORK_ASSET_ROOT = join(SKILL_ROOT, "assets", "framework");
9
+ join(FRAMEWORK_ASSET_ROOT, "manifest.json");
10
+ const FRAMEWORK_VERSION = join(FRAMEWORK_ASSET_ROOT, "VERSION");
11
+ const FRAMEWORK_TEMPLATES_DIR = join(FRAMEWORK_ASSET_ROOT, "templates");
12
+ const FRAMEWORK_WORKFLOWS_DIR = join(FRAMEWORK_ASSET_ROOT, "workflows");
13
+ join(FRAMEWORK_ASSET_ROOT, "prompts");
14
+ const FRAMEWORK_EXAMPLES_DIR = join(FRAMEWORK_ASSET_ROOT, "examples");
15
+ join(FRAMEWORK_ASSET_ROOT, "helpers");
16
+ const INSTALLED_PROGRESS = join(SKILL_ROOT, "progress.md");
17
+ const AGENT_INSTRUCTIONS_FILE = "AGENTS.md";
18
+ const LEGACY_AGENT_INSTRUCTIONS_FILE = "AGENT.md";
19
+ const AGENT_INSTRUCTIONS_TEMPLATE = "agents.md.template";
20
+ const LEGACY_SKILL_NAME = "first-tree-cli-framework";
21
+ const LEGACY_SKILL_ROOT = join("skills", LEGACY_SKILL_NAME);
22
+ const LEGACY_SKILL_ASSET_ROOT = join(LEGACY_SKILL_ROOT, "assets", "framework");
23
+ const LEGACY_SKILL_VERSION = join(LEGACY_SKILL_ASSET_ROOT, "VERSION");
24
+ join(LEGACY_SKILL_ASSET_ROOT, "templates");
25
+ join(LEGACY_SKILL_ASSET_ROOT, "workflows");
26
+ join(LEGACY_SKILL_ASSET_ROOT, "prompts");
27
+ join(LEGACY_SKILL_ASSET_ROOT, "examples");
28
+ const LEGACY_SKILL_PROGRESS = join(LEGACY_SKILL_ROOT, "progress.md");
29
+ const LEGACY_FRAMEWORK_ROOT = ".context-tree";
30
+ const LEGACY_VERSION = join(LEGACY_FRAMEWORK_ROOT, "VERSION");
31
+ const LEGACY_PROGRESS = join(LEGACY_FRAMEWORK_ROOT, "progress.md");
32
+ join(LEGACY_FRAMEWORK_ROOT, "templates");
33
+ join(LEGACY_FRAMEWORK_ROOT, "workflows");
34
+ join(LEGACY_FRAMEWORK_ROOT, "prompts");
35
+ join(LEGACY_FRAMEWORK_ROOT, "examples");
36
+ function pathExists(root, relPath) {
37
+ const fullPath = join(root, relPath);
38
+ try {
39
+ return existsSync(fullPath);
40
+ } catch {
41
+ return false;
42
+ }
43
+ }
44
+ function frameworkVersionCandidates() {
45
+ return [
46
+ FRAMEWORK_VERSION,
47
+ LEGACY_SKILL_VERSION,
48
+ LEGACY_VERSION
49
+ ];
50
+ }
51
+ function progressFileCandidates() {
52
+ return [
53
+ INSTALLED_PROGRESS,
54
+ LEGACY_SKILL_PROGRESS,
55
+ LEGACY_PROGRESS
56
+ ];
57
+ }
58
+ function agentInstructionsFileCandidates() {
59
+ return [AGENT_INSTRUCTIONS_FILE, LEGACY_AGENT_INSTRUCTIONS_FILE];
60
+ }
61
+ function resolveFirstExistingPath(root, candidates) {
62
+ for (const candidate of candidates) if (pathExists(root, candidate)) return candidate;
63
+ return null;
64
+ }
65
+ function detectFrameworkLayout(root) {
66
+ if (pathExists(root, FRAMEWORK_VERSION)) return "skill";
67
+ if (pathExists(root, LEGACY_SKILL_VERSION)) return "legacy-skill";
68
+ if (pathExists(root, LEGACY_VERSION)) return "legacy";
69
+ return null;
70
+ }
71
+ //#endregion
72
+ //#region skills/first-tree/engine/repo.ts
73
+ const FRONTMATTER_RE = /^---\s*\n(.*?)\n---/s;
74
+ const OWNERS_RE = /^owners:\s*\[([^\]]*)\]/m;
75
+ const TITLE_RE = /^title:\s*['"]?(.+?)['"]?\s*$/m;
76
+ const EMPTY_REPO_ENTRY_ALLOWLIST = new Set([
77
+ ".DS_Store",
78
+ ".editorconfig",
79
+ ".gitattributes",
80
+ ".github",
81
+ ".gitignore",
82
+ "AGENT.md",
83
+ "AGENTS.md",
84
+ "CLAUDE.md",
85
+ "LICENSE",
86
+ "LICENSE.md",
87
+ "LICENSE.txt",
88
+ "README",
89
+ "README.md",
90
+ "README.txt"
91
+ ]);
92
+ const SOURCE_FILE_HINTS = new Set([
93
+ ".gitmodules",
94
+ "Cargo.toml",
95
+ "Dockerfile",
96
+ "Gemfile",
97
+ "Makefile",
98
+ "bun.lock",
99
+ "bun.lockb",
100
+ "docker-compose.yml",
101
+ "go.mod",
102
+ "package-lock.json",
103
+ "package.json",
104
+ "pnpm-lock.yaml",
105
+ "pyproject.toml",
106
+ "requirements.txt",
107
+ "tsconfig.json",
108
+ "uv.lock",
109
+ "vite.config.ts",
110
+ "vite.config.js"
111
+ ]);
112
+ const SOURCE_DIR_HINTS = new Set([
113
+ "app",
114
+ "apps",
115
+ "backend",
116
+ "cli",
117
+ "client",
118
+ "docs",
119
+ "e2e",
120
+ "frontend",
121
+ "lib",
122
+ "packages",
123
+ "scripts",
124
+ "server",
125
+ "src",
126
+ "test",
127
+ "tests"
128
+ ]);
129
+ const FRAMEWORK_END_MARKER = "<!-- END CONTEXT-TREE FRAMEWORK -->";
130
+ function hasGitMetadata(root) {
131
+ try {
132
+ const stat = statSync(join(root, ".git"));
133
+ return stat.isDirectory() || stat.isFile();
134
+ } catch {
135
+ return false;
136
+ }
137
+ }
138
+ function discoverGitRoot(start) {
139
+ let dir = start;
140
+ while (true) {
141
+ if (hasGitMetadata(dir)) return dir;
142
+ const parent = dirname(dir);
143
+ if (parent === dir) return null;
144
+ dir = parent;
145
+ }
146
+ }
147
+ var Repo = class {
148
+ root;
149
+ constructor(root) {
150
+ const start = resolve(root ?? process.cwd());
151
+ this.root = root === void 0 ? discoverGitRoot(start) ?? start : start;
152
+ }
153
+ pathExists(relPath) {
154
+ return existsSync(join(this.root, relPath));
155
+ }
156
+ fileContains(relPath, text) {
157
+ const fullPath = join(this.root, relPath);
158
+ try {
159
+ if (!statSync(fullPath).isFile()) return false;
160
+ return readFileSync(fullPath, "utf-8").includes(text);
161
+ } catch {
162
+ return false;
163
+ }
164
+ }
165
+ readFile(relPath) {
166
+ try {
167
+ return readFileSync(join(this.root, relPath), "utf-8");
168
+ } catch {
169
+ return null;
170
+ }
171
+ }
172
+ frontmatter(relPath) {
173
+ const text = this.readFile(relPath);
174
+ if (text === null) return null;
175
+ const m = text.match(FRONTMATTER_RE);
176
+ if (!m) return null;
177
+ const fm = m[1];
178
+ const result = {};
179
+ const titleM = fm.match(TITLE_RE);
180
+ if (titleM) result.title = titleM[1].trim();
181
+ const ownersM = fm.match(OWNERS_RE);
182
+ if (ownersM) {
183
+ const raw = ownersM[1].trim();
184
+ result.owners = raw ? raw.split(",").map((o) => o.trim()).filter(Boolean) : [];
185
+ }
186
+ return result.title !== void 0 || result.owners !== void 0 ? result : null;
187
+ }
188
+ anyAgentConfig() {
189
+ return [".claude/settings.json", ".codex/config.json"].some((c) => this.pathExists(c));
190
+ }
191
+ isGitRepo() {
192
+ return hasGitMetadata(this.root);
193
+ }
194
+ hasFramework() {
195
+ return this.frameworkLayout() !== null;
196
+ }
197
+ frameworkLayout() {
198
+ return detectFrameworkLayout(this.root);
199
+ }
200
+ readVersion() {
201
+ const versionPath = resolveFirstExistingPath(this.root, frameworkVersionCandidates());
202
+ if (versionPath === null) return null;
203
+ const text = this.readFile(versionPath);
204
+ return text ? text.trim() : null;
205
+ }
206
+ progressPath() {
207
+ return resolveFirstExistingPath(this.root, progressFileCandidates());
208
+ }
209
+ preferredProgressPath() {
210
+ const layout = this.frameworkLayout();
211
+ if (layout === "legacy") return LEGACY_PROGRESS;
212
+ if (layout === "legacy-skill") return LEGACY_SKILL_PROGRESS;
213
+ return INSTALLED_PROGRESS;
214
+ }
215
+ frameworkVersionPath() {
216
+ const layout = this.frameworkLayout();
217
+ if (layout === "legacy") return LEGACY_VERSION;
218
+ if (layout === "legacy-skill") return LEGACY_SKILL_VERSION;
219
+ return FRAMEWORK_VERSION;
220
+ }
221
+ agentInstructionsPath() {
222
+ return resolveFirstExistingPath(this.root, agentInstructionsFileCandidates());
223
+ }
224
+ hasCanonicalAgentInstructionsFile() {
225
+ return this.pathExists(AGENT_INSTRUCTIONS_FILE);
226
+ }
227
+ hasLegacyAgentInstructionsFile() {
228
+ return this.pathExists(LEGACY_AGENT_INSTRUCTIONS_FILE);
229
+ }
230
+ hasDuplicateAgentInstructionsFiles() {
231
+ return this.hasCanonicalAgentInstructionsFile() && this.hasLegacyAgentInstructionsFile();
232
+ }
233
+ readAgentInstructions() {
234
+ const relPath = this.agentInstructionsPath();
235
+ if (relPath === null) return null;
236
+ return this.readFile(relPath);
237
+ }
238
+ hasAgentInstructionsMarkers() {
239
+ const text = this.readAgentInstructions();
240
+ if (text === null) return false;
241
+ return text.includes("<!-- BEGIN CONTEXT-TREE FRAMEWORK") && text.includes("<!-- END CONTEXT-TREE FRAMEWORK -->");
242
+ }
243
+ hasMembers() {
244
+ const membersDir = join(this.root, "members");
245
+ try {
246
+ if (!statSync(membersDir).isDirectory()) return false;
247
+ } catch {
248
+ return false;
249
+ }
250
+ return existsSync(join(membersDir, "NODE.md"));
251
+ }
252
+ memberCount() {
253
+ const membersDir = join(this.root, "members");
254
+ try {
255
+ if (!statSync(membersDir).isDirectory()) return 0;
256
+ } catch {
257
+ return 0;
258
+ }
259
+ let count = 0;
260
+ const walk = (dir) => {
261
+ for (const entry of readdirSync(dir)) {
262
+ const childPath = join(dir, entry);
263
+ try {
264
+ if (!statSync(childPath).isDirectory()) continue;
265
+ } catch {
266
+ continue;
267
+ }
268
+ if (existsSync(join(childPath, "NODE.md"))) count++;
269
+ walk(childPath);
270
+ }
271
+ };
272
+ walk(membersDir);
273
+ return count;
274
+ }
275
+ hasPlaceholderNode() {
276
+ return this.fileContains("NODE.md", "<!-- PLACEHOLDER");
277
+ }
278
+ repoName() {
279
+ return basename(this.root);
280
+ }
281
+ topLevelEntries() {
282
+ try {
283
+ return readdirSync(this.root).filter((entry) => entry !== ".git");
284
+ } catch {
285
+ return [];
286
+ }
287
+ }
288
+ looksLikeTreeRepo() {
289
+ 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;
290
+ return this.progressPath() !== null || this.hasFramework() || this.hasAgentInstructionsMarkers() || this.pathExists("members/NODE.md") || this.frontmatter("NODE.md") !== null;
291
+ }
292
+ isLikelyEmptyRepo() {
293
+ return this.topLevelEntries().filter((entry) => !EMPTY_REPO_ENTRY_ALLOWLIST.has(entry)).length === 0;
294
+ }
295
+ isLikelySourceRepo() {
296
+ if (this.looksLikeTreeRepo()) return false;
297
+ const entries = this.topLevelEntries().filter((entry) => !EMPTY_REPO_ENTRY_ALLOWLIST.has(entry));
298
+ if (entries.length === 0) return false;
299
+ let directoryCount = 0;
300
+ for (const entry of entries) {
301
+ if (SOURCE_FILE_HINTS.has(entry)) return true;
302
+ if (isDirectory(this.root, entry)) {
303
+ directoryCount += 1;
304
+ if (SOURCE_DIR_HINTS.has(entry)) return true;
305
+ }
306
+ }
307
+ return directoryCount >= 2 || entries.length >= 4;
308
+ }
309
+ };
310
+ function isDirectory(root, relPath) {
311
+ try {
312
+ return statSync(join(root, relPath)).isDirectory();
313
+ } catch {
314
+ return false;
315
+ }
316
+ }
317
+ //#endregion
318
+ export { FRAMEWORK_ASSET_ROOT as a, FRAMEWORK_VERSION as c, LEGACY_AGENT_INSTRUCTIONS_FILE as d, LEGACY_FRAMEWORK_ROOT as f, SKILL_ROOT as g, SKILL_NAME as h, AGENT_INSTRUCTIONS_TEMPLATE as i, FRAMEWORK_WORKFLOWS_DIR as l, LEGACY_SKILL_ROOT as m, Repo as n, FRAMEWORK_EXAMPLES_DIR as o, LEGACY_SKILL_NAME as p, AGENT_INSTRUCTIONS_FILE as r, FRAMEWORK_TEMPLATES_DIR as s, FRAMEWORK_END_MARKER as t, INSTALLED_PROGRESS as u };
@@ -0,0 +1,135 @@
1
+ import { c as FRAMEWORK_VERSION, d as LEGACY_AGENT_INSTRUCTIONS_FILE, f as LEGACY_FRAMEWORK_ROOT, g as SKILL_ROOT, i as AGENT_INSTRUCTIONS_TEMPLATE, l as FRAMEWORK_WORKFLOWS_DIR, m as LEGACY_SKILL_ROOT, n as Repo, r as AGENT_INSTRUCTIONS_FILE, s as FRAMEWORK_TEMPLATES_DIR, u as INSTALLED_PROGRESS } from "./repo-DY57bMqr.js";
2
+ import { i as resolveCanonicalSkillRoot, r as resolveBundledPackageRoot, t as copyCanonicalSkill } from "./installer-cH7N4RNj.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 \`${SKILL_ROOT}/\` 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 \`${SKILL_ROOT}/assets/framework/examples/\` or \`${SKILL_ROOT}/assets/framework/helpers/\``,
43
+ ""
44
+ ];
45
+ const migrationTasks = [];
46
+ if (layout === "legacy") migrationTasks.push("- [ ] Remove any stale `.context-tree/` references from repo-specific docs, scripts, or workflow files");
47
+ if (layout === "legacy-skill") migrationTasks.push(`- [ ] Remove any stale \`${LEGACY_SKILL_ROOT}/\` references from repo-specific docs, scripts, workflow files, or agent config`);
48
+ 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`);
49
+ else if (repo.hasLegacyAgentInstructionsFile()) migrationTasks.push(`- [ ] Rename \`${LEGACY_AGENT_INSTRUCTIONS_FILE}\` to \`${AGENT_INSTRUCTIONS_FILE}\` to use the canonical agent instructions filename`);
50
+ if (migrationTasks.length > 0) lines.push("## Migration", ...migrationTasks, "");
51
+ 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`, "");
52
+ 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.`, "");
53
+ return lines.join("\n");
54
+ }
55
+ function runUpgrade(repo, options) {
56
+ const workingRepo = repo ?? new Repo();
57
+ if (workingRepo.isLikelySourceRepo() && !workingRepo.looksLikeTreeRepo()) {
58
+ 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.");
59
+ return 1;
60
+ }
61
+ if (!workingRepo.hasFramework()) {
62
+ console.error("Error: no installed framework skill found. Run `context-tree init` first.");
63
+ return 1;
64
+ }
65
+ const layout = workingRepo.frameworkLayout();
66
+ if (layout === null) {
67
+ console.error("Error: no installed framework skill found. Run `context-tree init` first.");
68
+ return 1;
69
+ }
70
+ const localVersion = workingRepo.readVersion() ?? "unknown";
71
+ console.log(`Local framework version: ${localVersion}\n`);
72
+ console.log("Checking the framework skill bundled with this first-tree package...");
73
+ let sourceRoot;
74
+ try {
75
+ sourceRoot = options?.sourceRoot ?? resolveBundledPackageRoot();
76
+ } catch (err) {
77
+ const message = err instanceof Error ? err.message : "unknown error";
78
+ console.error(`Error: ${message}`);
79
+ return 1;
80
+ }
81
+ const packagedVersion = readSourceVersion(sourceRoot);
82
+ if (packagedVersion === null) {
83
+ console.log("Could not read the bundled framework version. Reinstall or update `first-tree` and try again.");
84
+ return 1;
85
+ }
86
+ if (localVersion !== "unknown" && compareFrameworkVersions(localVersion, packagedVersion) > 0) {
87
+ 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`.");
88
+ return 1;
89
+ }
90
+ if (layout === "skill" && packagedVersion === localVersion) {
91
+ console.log(`Already up to date with the bundled skill (${FRAMEWORK_VERSION} = ${localVersion}).`);
92
+ return 0;
93
+ }
94
+ copyCanonicalSkill(sourceRoot, workingRepo.root);
95
+ if (layout === "legacy") {
96
+ rmSync(join(workingRepo.root, LEGACY_FRAMEWORK_ROOT), {
97
+ recursive: true,
98
+ force: true
99
+ });
100
+ console.log("Migrated legacy .context-tree/ layout to skills/first-tree/.");
101
+ } else if (layout === "legacy-skill") console.log("Migrated skills/first-tree-cli-framework/ to skills/first-tree/.");
102
+ else console.log("Refreshed skills/first-tree/ from the bundled first-tree package.");
103
+ const output = formatUpgradeTaskList(workingRepo, localVersion, packagedVersion, layout);
104
+ console.log(`\n${output}`);
105
+ writeProgress(workingRepo, output);
106
+ console.log(`Progress file written to ${workingRepo.preferredProgressPath()}`);
107
+ return 0;
108
+ }
109
+ function runUpgradeCli(args = []) {
110
+ if (args.includes("--help") || args.includes("-h")) {
111
+ console.log(UPGRADE_USAGE);
112
+ return 0;
113
+ }
114
+ let treePath;
115
+ for (let index = 0; index < args.length; index += 1) {
116
+ const arg = args[index];
117
+ if (arg === "--tree-path") {
118
+ const value = args[index + 1];
119
+ if (!value) {
120
+ console.error("Missing value for --tree-path");
121
+ console.log(UPGRADE_USAGE);
122
+ return 1;
123
+ }
124
+ treePath = value;
125
+ index += 1;
126
+ continue;
127
+ }
128
+ console.error(`Unknown upgrade option: ${arg}`);
129
+ console.log(UPGRADE_USAGE);
130
+ return 1;
131
+ }
132
+ return runUpgrade(treePath ? new Repo(resolve(process.cwd(), treePath)) : void 0);
133
+ }
134
+ //#endregion
135
+ export { runUpgradeCli as runUpgrade };
@@ -1,7 +1,7 @@
1
- import { n as Repo } from "./repo-BByc3VvM.js";
1
+ import { d as LEGACY_AGENT_INSTRUCTIONS_FILE, g as SKILL_ROOT, m as LEGACY_SKILL_ROOT, n as Repo, r as AGENT_INSTRUCTIONS_FILE } from "./repo-DY57bMqr.js";
2
2
  import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
3
- import { join, relative } from "node:path";
4
- //#region src/validators/members.ts
3
+ import { join, relative, resolve } from "node:path";
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([
7
7
  "human",
@@ -64,25 +64,51 @@ function runValidateMembers(treeRoot) {
64
64
  }
65
65
  const allErrors = [];
66
66
  let memberCount = 0;
67
- for (const child of readdirSync(membersDir).sort()) {
68
- const childPath = join(membersDir, child);
69
- try {
70
- const stat = statSync(childPath);
71
- if (stat.isFile() && child.endsWith(".md") && child !== "NODE.md") {
72
- allErrors.push(`${rel$1(childPath, treeRoot)}: member must be a directory with NODE.md, not a standalone file — use members/${child.replace(/\.md$/, "")}/NODE.md instead`);
67
+ const nameOccurrences = /* @__PURE__ */ new Map();
68
+ const members = [];
69
+ function walk(dir) {
70
+ for (const child of readdirSync(dir).sort()) {
71
+ const childPath = join(dir, child);
72
+ try {
73
+ const stat = statSync(childPath);
74
+ if (stat.isFile() && child.endsWith(".md") && child !== "NODE.md") {
75
+ allErrors.push(`${rel$1(childPath, treeRoot)}: member must be a directory with NODE.md, not a standalone file — use ${rel$1(dir, treeRoot)}/${child.replace(/\.md$/, "")}/NODE.md instead`);
76
+ continue;
77
+ }
78
+ if (!stat.isDirectory()) continue;
79
+ } catch {
73
80
  continue;
74
81
  }
75
- if (!stat.isDirectory()) continue;
76
- } catch {
77
- continue;
78
- }
79
- const nodePath = join(childPath, "NODE.md");
80
- if (!existsSync(nodePath)) {
81
- allErrors.push(`${rel$1(childPath, treeRoot)}/: directory exists but missing NODE.md`);
82
- continue;
82
+ const relPath = relative(membersDir, childPath);
83
+ const occurrences = nameOccurrences.get(child) ?? [];
84
+ occurrences.push(relPath);
85
+ nameOccurrences.set(child, occurrences);
86
+ const nodePath = join(childPath, "NODE.md");
87
+ if (!existsSync(nodePath)) {
88
+ allErrors.push(`${rel$1(childPath, treeRoot)}/: directory exists but missing NODE.md`);
89
+ walk(childPath);
90
+ continue;
91
+ }
92
+ memberCount++;
93
+ allErrors.push(...validateMember(nodePath, treeRoot));
94
+ const fm = parseFrontmatter$1(nodePath);
95
+ if (fm) members.push({
96
+ name: child,
97
+ relPath,
98
+ type: extractScalar(fm, "type"),
99
+ delegateMention: extractScalar(fm, "delegate_mention")
100
+ });
101
+ walk(childPath);
83
102
  }
84
- memberCount++;
85
- allErrors.push(...validateMember(nodePath, treeRoot));
103
+ }
104
+ walk(membersDir);
105
+ for (const [name, paths] of nameOccurrences) if (paths.length > 1) allErrors.push(`Duplicate member directory name '${name}' found at: ${paths.map((p) => `members/${p}`).join(", ")} — directory names must be unique across all levels under members/`);
106
+ const memberByName = new Map(members.map((m) => [m.name, m]));
107
+ for (const member of members) {
108
+ if (!member.delegateMention) continue;
109
+ const target = memberByName.get(member.delegateMention);
110
+ if (!target) allErrors.push(`members/${member.relPath}/NODE.md: delegate_mention '${member.delegateMention}' references non-existent member — the target must be a directory under members/`);
111
+ else if (target.type !== "personal_assistant") allErrors.push(`members/${member.relPath}/NODE.md: delegate_mention '${member.delegateMention}' must reference a member with type 'personal_assistant', but '${target.name}' has type '${target.type}'`);
86
112
  }
87
113
  if (allErrors.length > 0) {
88
114
  console.log(`Found ${allErrors.length} member validation error(s):\n`);
@@ -99,7 +125,7 @@ function runValidateMembers(treeRoot) {
99
125
  };
100
126
  }
101
127
  //#endregion
102
- //#region src/validators/nodes.ts
128
+ //#region skills/first-tree/engine/validators/nodes.ts
103
129
  const FRONTMATTER_RE = /^---\s*\n(.*?)\n---/s;
104
130
  const OWNERS_RE = /^owners:\s*\[([^\]]*)\]/m;
105
131
  const SOFT_LINKS_INLINE_RE = /^soft_links:\s*\[([^\]]*)\]/m;
@@ -107,7 +133,11 @@ const SOFT_LINKS_BLOCK_RE = /^soft_links:\s*\n((?:\s+-\s+.+\n?)+)/m;
107
133
  const TITLE_RE = /^title:\s*['"]?(.+?)['"]?\s*$/m;
108
134
  const GITHUB_USER_RE = /^[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,37}[a-zA-Z0-9])?$/;
109
135
  const SKIP = new Set(["node_modules", "__pycache__"]);
110
- 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
+ ]);
111
141
  const MIN_BODY_LENGTH = 20;
112
142
  var Findings = class {
113
143
  errors = [];
@@ -155,8 +185,23 @@ function setTreeRoot(root) {
155
185
  function rel(path) {
156
186
  return relative(treeRoot, path);
157
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
+ }
158
201
  function shouldSkip(path) {
159
- 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);
160
205
  }
161
206
  function readText(path) {
162
207
  if (!textCache.has(path)) try {
@@ -441,14 +486,21 @@ function runValidateNodes(root) {
441
486
  };
442
487
  }
443
488
  //#endregion
444
- //#region src/verify.ts
489
+ //#region skills/first-tree/engine/verify.ts
445
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
+ `;
446
497
  function check(label, passed) {
447
498
  console.log(` ${passed ? "✓" : "✗"} [${passed ? "PASS" : "FAIL"}] ${label}`);
448
499
  return passed;
449
500
  }
450
501
  function checkProgress(repo) {
451
- const text = repo.readFile(".context-tree/progress.md");
502
+ const progressPath = repo.progressPath();
503
+ const text = progressPath === null ? null : repo.readFile(progressPath);
452
504
  if (text === null) return [];
453
505
  const matches = [];
454
506
  let m;
@@ -463,21 +515,33 @@ function defaultNodeValidator(root) {
463
515
  function runVerify(repo, nodeValidator) {
464
516
  const r = repo ?? new Repo();
465
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
+ }
466
522
  let allPassed = true;
523
+ const progressPath = r.progressPath() ?? r.preferredProgressPath();
524
+ const frameworkVersionPath = r.frameworkVersionPath();
467
525
  console.log("Context Tree Verification\n");
468
526
  const unchecked = checkProgress(r);
469
527
  if (unchecked.length > 0) {
470
- console.log(" Unchecked items in .context-tree/progress.md:\n");
528
+ console.log(` Unchecked items in ${progressPath}:\n`);
471
529
  for (const item of unchecked) console.log(` - [ ] ${item}`);
472
530
  console.log();
473
- console.log(" Verify each step above and check it off in progress.md before running verify again.\n");
531
+ console.log(` Verify each step above and check it off in ${progressPath} before running verify again.\n`);
474
532
  allPassed = false;
475
533
  }
476
534
  console.log(" Checks:\n");
477
- allPassed = check(".context-tree/VERSION exists", r.hasFramework()) && allPassed;
535
+ allPassed = check(`${frameworkVersionPath} exists`, r.hasFramework()) && allPassed;
478
536
  const fm = r.frontmatter("NODE.md");
479
537
  allPassed = check("Root NODE.md has valid frontmatter (title, owners)", fm !== null && fm.title !== void 0 && fm.owners !== void 0) && allPassed;
480
- 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;
481
545
  const { exitCode } = validate(r.root);
482
546
  allPassed = check("Node validation passes", exitCode === 0) && allPassed;
483
547
  allPassed = check("Member validation passes", runValidateMembers(r.root).exitCode === 0) && allPassed;
@@ -486,5 +550,30 @@ function runVerify(repo, nodeValidator) {
486
550
  else console.log("Some checks failed. See above for details.");
487
551
  return allPassed ? 0 : 1;
488
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
+ }
489
578
  //#endregion
490
- export { runVerify };
579
+ export { runVerifyCli as runVerify };