first-tree 0.0.2 → 0.0.3

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 +73 -39
  2. package/dist/cli.js +27 -13
  3. package/dist/help-xEI-s9iN.js +25 -0
  4. package/dist/init-DtOjj0wc.js +253 -0
  5. package/dist/installer-rcZpGLnM.js +47 -0
  6. package/dist/onboarding-6Fr5Gkrk.js +2 -0
  7. package/dist/onboarding-B9zPGvvG.js +10 -0
  8. package/dist/repo-BTJG8BU1.js +187 -0
  9. package/dist/upgrade-COGgI7Rj.js +96 -0
  10. package/dist/{verify-CSRIkuoM.js → verify-CxN6JiV9.js} +53 -24
  11. package/package.json +33 -10
  12. package/skills/first-tree/SKILL.md +109 -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 +179 -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/agent.md.template +48 -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 +38 -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 +145 -0
  34. package/skills/first-tree/engine/onboarding.ts +10 -0
  35. package/skills/first-tree/engine/repo.ts +184 -0
  36. package/skills/first-tree/engine/rules/agent-instructions.ts +37 -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 +134 -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 +176 -0
  49. package/skills/first-tree/engine/validators/members.ts +215 -0
  50. package/skills/first-tree/engine/validators/nodes.ts +514 -0
  51. package/skills/first-tree/engine/verify.ts +97 -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 +56 -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 +162 -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 +85 -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 +149 -0
  68. package/skills/first-tree/tests/init.test.ts +153 -0
  69. package/skills/first-tree/tests/repo.test.ts +362 -0
  70. package/skills/first-tree/tests/rules.test.ts +394 -0
  71. package/skills/first-tree/tests/run-review.test.ts +155 -0
  72. package/skills/first-tree/tests/skill-artifacts.test.ts +307 -0
  73. package/skills/first-tree/tests/thin-cli.test.ts +59 -0
  74. package/skills/first-tree/tests/upgrade.test.ts +89 -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 +142 -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,198 @@
1
+ import { mkdirSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { afterEach, describe, expect, it } from "vitest";
4
+ import {
5
+ Findings,
6
+ parseFrontmatter,
7
+ parseSoftLinks,
8
+ validateOwners,
9
+ validateFolders,
10
+ validateEmptyNodes,
11
+ validateTitleMismatch,
12
+ setTreeRoot,
13
+ } from "#skill/engine/validators/nodes.js";
14
+ import { useTmpDir } from "./helpers.js";
15
+
16
+ function write(root: string, relPath: string, content: string): string {
17
+ const p = join(root, relPath);
18
+ mkdirSync(join(p, ".."), { recursive: true });
19
+ writeFileSync(p, content);
20
+ return p;
21
+ }
22
+
23
+ function setup(tmp: { path: string }): string {
24
+ setTreeRoot(tmp.path);
25
+ return tmp.path;
26
+ }
27
+
28
+ // --- parseFrontmatter ---
29
+
30
+ describe("parseFrontmatter", () => {
31
+ it("parses valid frontmatter", () => {
32
+ const tmp = useTmpDir();
33
+ const root = setup(tmp);
34
+ const p = write(root, "NODE.md", "---\ntitle: Hello\nowners: [alice]\n---\n# Hello\n");
35
+ const fm = parseFrontmatter(p);
36
+ expect(fm).not.toBeNull();
37
+ expect(fm).toContain("title: Hello");
38
+ });
39
+
40
+ it("returns null for missing frontmatter", () => {
41
+ const tmp = useTmpDir();
42
+ const root = setup(tmp);
43
+ const p = write(root, "NODE.md", "# No frontmatter here\n");
44
+ expect(parseFrontmatter(p)).toBeNull();
45
+ });
46
+
47
+ it("returns null for malformed frontmatter", () => {
48
+ const tmp = useTmpDir();
49
+ const root = setup(tmp);
50
+ const p = write(root, "NODE.md", "---\ntitle: Oops\nNo closing fence\n");
51
+ expect(parseFrontmatter(p)).toBeNull();
52
+ });
53
+ });
54
+
55
+ // --- parseSoftLinks ---
56
+
57
+ describe("parseSoftLinks", () => {
58
+ it("parses inline format", () => {
59
+ expect(parseSoftLinks("owners: [alice]\nsoft_links: [/a, /b]")).toEqual(["/a", "/b"]);
60
+ });
61
+
62
+ it("parses block format", () => {
63
+ expect(parseSoftLinks("owners: [alice]\nsoft_links:\n - /x\n - /y\n")).toEqual(["/x", "/y"]);
64
+ });
65
+
66
+ it("handles empty inline", () => {
67
+ expect(parseSoftLinks("owners: [alice]\nsoft_links: []")).toEqual([]);
68
+ });
69
+
70
+ it("returns null when missing", () => {
71
+ expect(parseSoftLinks("owners: [alice]")).toBeNull();
72
+ });
73
+ });
74
+
75
+ // --- validateOwners ---
76
+
77
+ describe("validateOwners", () => {
78
+ it("accepts valid owners", () => {
79
+ const tmp = useTmpDir();
80
+ const root = setup(tmp);
81
+ const p = write(root, "NODE.md", "---\nowners: [alice, bob]\n---\n");
82
+ const fm = parseFrontmatter(p)!;
83
+ const f = new Findings();
84
+ validateOwners(fm, p, f);
85
+ expect(f.errors).toEqual([]);
86
+ });
87
+
88
+ it("accepts wildcard", () => {
89
+ const tmp = useTmpDir();
90
+ const root = setup(tmp);
91
+ const p = write(root, "NODE.md", "---\nowners: [*]\n---\n");
92
+ const fm = parseFrontmatter(p)!;
93
+ const f = new Findings();
94
+ validateOwners(fm, p, f);
95
+ expect(f.errors).toEqual([]);
96
+ });
97
+
98
+ it("accepts empty inheritance", () => {
99
+ const tmp = useTmpDir();
100
+ const root = setup(tmp);
101
+ const p = write(root, "NODE.md", "---\nowners: []\n---\n");
102
+ const fm = parseFrontmatter(p)!;
103
+ const f = new Findings();
104
+ validateOwners(fm, p, f);
105
+ expect(f.errors).toEqual([]);
106
+ });
107
+
108
+ it("rejects invalid username", () => {
109
+ const tmp = useTmpDir();
110
+ const root = setup(tmp);
111
+ const p = write(root, "NODE.md", "---\nowners: [not valid!]\n---\n");
112
+ const fm = parseFrontmatter(p)!;
113
+ const f = new Findings();
114
+ validateOwners(fm, p, f);
115
+ expect(f.errors).toHaveLength(1);
116
+ expect(f.errors[0]).toContain("invalid owner");
117
+ });
118
+
119
+ it("rejects mixed wildcard", () => {
120
+ const tmp = useTmpDir();
121
+ const root = setup(tmp);
122
+ const p = write(root, "NODE.md", "---\nowners: [alice, *]\n---\n");
123
+ const fm = parseFrontmatter(p)!;
124
+ const f = new Findings();
125
+ validateOwners(fm, p, f);
126
+ expect(f.errors).toHaveLength(1);
127
+ expect(f.errors[0]).toContain("wildcard");
128
+ });
129
+ });
130
+
131
+ // --- validateFolders ---
132
+
133
+ describe("validateFolders", () => {
134
+ it("reports missing NODE.md", () => {
135
+ const tmp = useTmpDir();
136
+ const root = setup(tmp);
137
+ mkdirSync(join(root, "domain"));
138
+ const f = new Findings();
139
+ validateFolders(f);
140
+ expect(f.errors.some((e) => e.includes("missing NODE.md"))).toBe(true);
141
+ });
142
+
143
+ it("passes with valid folder", () => {
144
+ const tmp = useTmpDir();
145
+ const root = setup(tmp);
146
+ mkdirSync(join(root, "domain"));
147
+ writeFileSync(join(root, "domain", "NODE.md"), "---\nowners: [a]\n---\n# D\n");
148
+ const f = new Findings();
149
+ validateFolders(f);
150
+ expect(f.errors).toEqual([]);
151
+ });
152
+ });
153
+
154
+ // --- validateEmptyNodes ---
155
+
156
+ describe("validateEmptyNodes", () => {
157
+ it("flags short body", () => {
158
+ const tmp = useTmpDir();
159
+ const root = setup(tmp);
160
+ const p = write(root, "NODE.md", "---\nowners: [a]\n---\n\n");
161
+ const f = new Findings();
162
+ validateEmptyNodes([p], f);
163
+ expect(f.warnings.some((w) => w.includes("little or no body content"))).toBe(true);
164
+ });
165
+
166
+ it("passes with adequate body", () => {
167
+ const tmp = useTmpDir();
168
+ const root = setup(tmp);
169
+ const body = "This is a meaningful body with enough content to pass the threshold easily.";
170
+ const p = write(root, "NODE.md", `---\nowners: [a]\n---\n${body}\n`);
171
+ const f = new Findings();
172
+ validateEmptyNodes([p], f);
173
+ expect(f.warnings).toEqual([]);
174
+ });
175
+ });
176
+
177
+ // --- validateTitleMismatch ---
178
+
179
+ describe("validateTitleMismatch", () => {
180
+ it("passes with matching title", () => {
181
+ const tmp = useTmpDir();
182
+ const root = setup(tmp);
183
+ const p = write(root, "NODE.md", "---\ntitle: Hello World\nowners: [a]\n---\n# Hello World\n");
184
+ const f = new Findings();
185
+ validateTitleMismatch([p], f);
186
+ expect(f.warnings).toEqual([]);
187
+ });
188
+
189
+ it("flags mismatched title", () => {
190
+ const tmp = useTmpDir();
191
+ const root = setup(tmp);
192
+ const p = write(root, "NODE.md", "---\ntitle: Hello\nowners: [a]\n---\n# Goodbye\n");
193
+ const f = new Findings();
194
+ validateTitleMismatch([p], f);
195
+ expect(f.warnings).toHaveLength(1);
196
+ expect(f.warnings[0]).toContain("differs from");
197
+ });
198
+ });
@@ -0,0 +1,142 @@
1
+ import { mkdirSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { describe, expect, it } from "vitest";
4
+ import { check, checkProgress, runVerify } from "#skill/engine/verify.js";
5
+ import { Repo } from "#skill/engine/repo.js";
6
+ import {
7
+ INSTALLED_PROGRESS,
8
+ LEGACY_PROGRESS,
9
+ } from "#skill/engine/runtime/asset-loader.js";
10
+ import {
11
+ useTmpDir,
12
+ makeFramework,
13
+ makeLegacyFramework,
14
+ makeNode,
15
+ makeAgentMd,
16
+ makeMembers,
17
+ } from "./helpers.js";
18
+
19
+ // --- check ---
20
+
21
+ describe("check", () => {
22
+ it("returns true on pass", () => {
23
+ expect(check("my check", true)).toBe(true);
24
+ });
25
+
26
+ it("returns false on fail", () => {
27
+ expect(check("my check", false)).toBe(false);
28
+ });
29
+ });
30
+
31
+ // --- checkProgress ---
32
+
33
+ describe("checkProgress", () => {
34
+ it("returns empty for no progress file", () => {
35
+ const tmp = useTmpDir();
36
+ const repo = new Repo(tmp.path);
37
+ expect(checkProgress(repo)).toEqual([]);
38
+ });
39
+
40
+ it("returns empty when all checked", () => {
41
+ const tmp = useTmpDir();
42
+ mkdirSync(join(tmp.path, "skills", "first-tree"), { recursive: true });
43
+ writeFileSync(
44
+ join(tmp.path, INSTALLED_PROGRESS),
45
+ "# Progress\n- [x] Task one\n- [x] Task two\n",
46
+ );
47
+ const repo = new Repo(tmp.path);
48
+ expect(checkProgress(repo)).toEqual([]);
49
+ });
50
+
51
+ it("returns unchecked items", () => {
52
+ const tmp = useTmpDir();
53
+ mkdirSync(join(tmp.path, "skills", "first-tree"), { recursive: true });
54
+ writeFileSync(
55
+ join(tmp.path, INSTALLED_PROGRESS),
56
+ "# Progress\n- [x] Done task\n- [ ] Pending task\n- [ ] Another pending\n",
57
+ );
58
+ const repo = new Repo(tmp.path);
59
+ expect(checkProgress(repo)).toEqual(["Pending task", "Another pending"]);
60
+ });
61
+
62
+ it("returns empty for empty progress", () => {
63
+ const tmp = useTmpDir();
64
+ mkdirSync(join(tmp.path, "skills", "first-tree"), { recursive: true });
65
+ writeFileSync(join(tmp.path, INSTALLED_PROGRESS), "");
66
+ const repo = new Repo(tmp.path);
67
+ expect(checkProgress(repo)).toEqual([]);
68
+ });
69
+
70
+ it("falls back to the legacy progress file", () => {
71
+ const tmp = useTmpDir();
72
+ makeLegacyFramework(tmp.path);
73
+ writeFileSync(
74
+ join(tmp.path, LEGACY_PROGRESS),
75
+ "# Progress\n- [ ] Legacy task\n",
76
+ );
77
+ const repo = new Repo(tmp.path);
78
+ expect(checkProgress(repo)).toEqual(["Legacy task"]);
79
+ });
80
+ });
81
+
82
+ // --- helpers for building a full repo ---
83
+
84
+ function buildFullRepo(root: string): void {
85
+ mkdirSync(join(root, ".git"));
86
+ makeFramework(root);
87
+ writeFileSync(
88
+ join(root, "NODE.md"),
89
+ "---\ntitle: My Org\nowners: [alice]\n---\n# Content\n",
90
+ );
91
+ writeFileSync(
92
+ join(root, "AGENT.md"),
93
+ "<!-- BEGIN CONTEXT-TREE FRAMEWORK -->\nstuff\n<!-- END CONTEXT-TREE FRAMEWORK -->\n",
94
+ );
95
+ makeMembers(root, 1);
96
+ }
97
+
98
+ const passValidator = () => ({ exitCode: 0 });
99
+ const failValidator = () => ({ exitCode: 1 });
100
+
101
+ // --- runVerify — all passing ---
102
+
103
+ describe("runVerify all passing", () => {
104
+ it("returns 0 when all checks pass", () => {
105
+ const tmp = useTmpDir();
106
+ buildFullRepo(tmp.path);
107
+ const repo = new Repo(tmp.path);
108
+ const ret = runVerify(repo, passValidator);
109
+ expect(ret).toBe(0);
110
+ });
111
+ });
112
+
113
+ // --- runVerify — failing checks ---
114
+
115
+ describe("runVerify failing", () => {
116
+ it("fails on empty repo", () => {
117
+ const tmp = useTmpDir();
118
+ const repo = new Repo(tmp.path);
119
+ const ret = runVerify(repo, passValidator);
120
+ expect(ret).toBe(1);
121
+ });
122
+
123
+ it("fails when AGENT.md is missing", () => {
124
+ const tmp = useTmpDir();
125
+ makeFramework(tmp.path);
126
+ writeFileSync(
127
+ join(tmp.path, "NODE.md"),
128
+ "---\ntitle: My Org\nowners: [alice]\n---\n",
129
+ );
130
+ const repo = new Repo(tmp.path);
131
+ const ret = runVerify(repo, passValidator);
132
+ expect(ret).toBe(1);
133
+ });
134
+
135
+ it("fails when node validation returns non-zero", () => {
136
+ const tmp = useTmpDir();
137
+ buildFullRepo(tmp.path);
138
+ const repo = new Repo(tmp.path);
139
+ const ret = runVerify(repo, failValidator);
140
+ expect(ret).toBe(1);
141
+ });
142
+ });
@@ -1,283 +0,0 @@
1
- import { n as Repo, t as FRAMEWORK_END_MARKER } from "./repo-BByc3VvM.js";
2
- import { execFileSync } from "node:child_process";
3
- import { copyFileSync, cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
4
- import { dirname, join } from "node:path";
5
- import { tmpdir } from "node:os";
6
- //#region \0rolldown/runtime.js
7
- var __defProp = Object.defineProperty;
8
- var __exportAll = (all, no_symbols) => {
9
- let target = {};
10
- for (var name in all) __defProp(target, name, {
11
- get: all[name],
12
- enumerable: true
13
- });
14
- if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
15
- return target;
16
- };
17
- //#endregion
18
- //#region src/rules/agent-instructions.ts
19
- var agent_instructions_exports = /* @__PURE__ */ __exportAll({ evaluate: () => evaluate$5 });
20
- function evaluate$5(repo) {
21
- const tasks = [];
22
- if (!repo.pathExists("AGENT.md")) tasks.push("AGENT.md is missing — create from `.context-tree/templates/agent.md.template`");
23
- else if (!repo.hasAgentMdMarkers()) tasks.push("AGENT.md exists but is missing framework markers — add `<!-- BEGIN CONTEXT-TREE FRAMEWORK -->` and `<!-- END CONTEXT-TREE FRAMEWORK -->` sections");
24
- else {
25
- const afterMarker = (repo.readFile("AGENT.md") ?? "").split(FRAMEWORK_END_MARKER);
26
- if (afterMarker.length > 1) {
27
- if (afterMarker[1].trim().split("\n").filter((l) => l.trim() && !l.trim().startsWith("#") && !l.trim().startsWith("<!--")).length === 0) tasks.push("Add your project-specific instructions below the framework markers in AGENT.md");
28
- }
29
- }
30
- return {
31
- group: "Agent Instructions",
32
- order: 3,
33
- tasks
34
- };
35
- }
36
- //#endregion
37
- //#region src/rules/agent-integration.ts
38
- var agent_integration_exports = /* @__PURE__ */ __exportAll({ evaluate: () => evaluate$4 });
39
- function evaluate$4(repo) {
40
- const tasks = [];
41
- if (repo.pathExists(".claude/settings.json")) {
42
- if (!repo.fileContains(".claude/settings.json", "inject-tree-context")) tasks.push("Add SessionStart hook to `.claude/settings.json` (see `.context-tree/examples/claude-code/`)");
43
- } else if (!repo.anyAgentConfig()) tasks.push("No agent configuration detected. Configure your agent to load tree context at session start. See `.context-tree/examples/` for supported agents. You can skip this and set it up later.");
44
- return {
45
- group: "Agent Integration",
46
- order: 5,
47
- tasks
48
- };
49
- }
50
- //#endregion
51
- //#region src/rules/ci-validation.ts
52
- var ci_validation_exports = /* @__PURE__ */ __exportAll({ evaluate: () => evaluate$3 });
53
- function evaluate$3(repo) {
54
- const tasks = [];
55
- let hasValidation = false;
56
- const workflowsDir = join(repo.root, ".github", "workflows");
57
- try {
58
- if (statSync(workflowsDir).isDirectory()) for (const name of readdirSync(workflowsDir)) {
59
- if (!name.endsWith(".yml") && !name.endsWith(".yaml")) continue;
60
- const fullPath = join(workflowsDir, name);
61
- try {
62
- if (!statSync(fullPath).isFile()) continue;
63
- const content = readFileSync(fullPath, "utf-8");
64
- if (content.includes("validate_nodes") || content.includes("validate_members")) {
65
- hasValidation = true;
66
- break;
67
- }
68
- } catch {
69
- continue;
70
- }
71
- }
72
- } catch {}
73
- if (!hasValidation) tasks.push("No validation workflow found — copy `.context-tree/workflows/validate.yml` to `.github/workflows/validate.yml`");
74
- return {
75
- group: "CI / Validation",
76
- order: 6,
77
- tasks
78
- };
79
- }
80
- //#endregion
81
- //#region src/rules/framework.ts
82
- var framework_exports = /* @__PURE__ */ __exportAll({ evaluate: () => evaluate$2 });
83
- const SEED_TREE_URL$1 = "https://github.com/agent-team-foundation/seed-tree";
84
- function evaluate$2(repo) {
85
- const tasks = [];
86
- if (!repo.hasFramework()) tasks.push(`\`.context-tree/\` not found — run \`context-tree init\` to clone the framework from ${SEED_TREE_URL$1}`);
87
- return {
88
- group: "Framework",
89
- order: 1,
90
- tasks
91
- };
92
- }
93
- //#endregion
94
- //#region src/rules/members.ts
95
- var members_exports = /* @__PURE__ */ __exportAll({ evaluate: () => evaluate$1 });
96
- function evaluate$1(repo) {
97
- const tasks = [];
98
- if (!repo.pathExists("members")) tasks.push("`members/` directory is missing — create it with a NODE.md");
99
- else if (!repo.pathExists("members/NODE.md")) tasks.push("`members/NODE.md` is missing — create it from the template");
100
- if (repo.hasMembers() && repo.memberCount() === 0) tasks.push("Add at least one member node for a team member or agent under `members/`");
101
- else if (!repo.hasMembers()) tasks.push("Add at least one member node for a team member or agent under `members/`");
102
- return {
103
- group: "Members",
104
- order: 4,
105
- tasks
106
- };
107
- }
108
- //#endregion
109
- //#region src/rules/root-node.ts
110
- var root_node_exports = /* @__PURE__ */ __exportAll({ evaluate: () => evaluate });
111
- function evaluate(repo) {
112
- const tasks = [];
113
- if (!repo.pathExists("NODE.md")) tasks.push("NODE.md is missing — create from `.context-tree/templates/root-node.md.template`, fill in your project's domains");
114
- else {
115
- const fm = repo.frontmatter("NODE.md");
116
- if (fm === null) tasks.push("NODE.md exists but has no frontmatter — add frontmatter with title and owners fields");
117
- else {
118
- if (!fm.title || fm.title.startsWith("<")) tasks.push("NODE.md has a placeholder title — replace with your organization name");
119
- if (!fm.owners || fm.owners.length === 0 || fm.owners.length === 1 && fm.owners[0].startsWith("<")) tasks.push("NODE.md has placeholder owners — set owners to your GitHub username(s)");
120
- }
121
- if (repo.hasPlaceholderNode()) tasks.push("NODE.md has placeholder content — fill in your project's domains and description");
122
- }
123
- return {
124
- group: "Root Node",
125
- order: 2,
126
- tasks
127
- };
128
- }
129
- //#endregion
130
- //#region src/rules/index.ts
131
- const ALL_RULES = [
132
- framework_exports,
133
- root_node_exports,
134
- agent_instructions_exports,
135
- members_exports,
136
- agent_integration_exports,
137
- ci_validation_exports
138
- ];
139
- function evaluateAll(repo) {
140
- const results = [];
141
- for (const rule of ALL_RULES) {
142
- const result = rule.evaluate(repo);
143
- if (result.tasks.length > 0) results.push(result);
144
- }
145
- return results.sort((a, b) => a.order - b.order);
146
- }
147
- //#endregion
148
- //#region src/init.ts
149
- const SEED_TREE_URL = "https://github.com/agent-team-foundation/seed-tree";
150
- const FRAMEWORK_DIR = ".context-tree";
151
- const TEMPLATE_MAP = [
152
- ["root-node.md.template", "NODE.md"],
153
- ["agent.md.template", "AGENT.md"],
154
- ["members-domain.md.template", "members/NODE.md"]
155
- ];
156
- function cloneSeedTree() {
157
- const tmp = mkdtempSync(join(tmpdir(), "context-tree-"));
158
- console.log(`Cloning seed-tree from ${SEED_TREE_URL}...`);
159
- try {
160
- execFileSync("git", [
161
- "clone",
162
- "--depth",
163
- "1",
164
- SEED_TREE_URL,
165
- tmp
166
- ], {
167
- encoding: "utf-8",
168
- stdio: "pipe"
169
- });
170
- } catch (err) {
171
- const message = err instanceof Error ? err.message : "unknown error";
172
- console.error(`Failed to clone seed-tree: ${message}`);
173
- rmSync(tmp, {
174
- recursive: true,
175
- force: true
176
- });
177
- process.exit(1);
178
- }
179
- return tmp;
180
- }
181
- function copyFramework(source, target) {
182
- const src = join(source, FRAMEWORK_DIR);
183
- const dst = join(target, FRAMEWORK_DIR);
184
- if (existsSync(dst)) rmSync(dst, {
185
- recursive: true,
186
- force: true
187
- });
188
- cpSync(src, dst, { recursive: true });
189
- console.log(` Copied ${FRAMEWORK_DIR}/`);
190
- }
191
- function renderTemplates(frameworkDir, target) {
192
- const templatesDir = join(frameworkDir, "templates");
193
- for (const [templateName, targetPath] of TEMPLATE_MAP) {
194
- const src = join(templatesDir, templateName);
195
- const dst = join(target, targetPath);
196
- if (existsSync(dst)) console.log(` Skipped ${targetPath} (already exists)`);
197
- else if (existsSync(src)) {
198
- mkdirSync(dirname(dst), { recursive: true });
199
- copyFileSync(src, dst);
200
- console.log(` Created ${targetPath}`);
201
- }
202
- }
203
- }
204
- function addUpstreamRemote(target) {
205
- try {
206
- if (!execFileSync("git", ["remote"], {
207
- cwd: target,
208
- encoding: "utf-8"
209
- }).split(/\s+/).includes("context-tree-upstream")) {
210
- execFileSync("git", [
211
- "remote",
212
- "add",
213
- "context-tree-upstream",
214
- SEED_TREE_URL
215
- ], {
216
- cwd: target,
217
- encoding: "utf-8",
218
- stdio: "pipe"
219
- });
220
- console.log(` Added git remote 'context-tree-upstream' -> ${SEED_TREE_URL}`);
221
- }
222
- } catch {}
223
- }
224
- function formatTaskList(groups) {
225
- const lines = ["# Context Tree Init\n"];
226
- for (const group of groups) {
227
- lines.push(`## ${group.group}`);
228
- for (const task of group.tasks) lines.push(`- [ ] ${task}`);
229
- lines.push("");
230
- }
231
- lines.push("## Verification");
232
- lines.push("After completing the tasks above, run `context-tree verify` to confirm:");
233
- lines.push("- [ ] `.context-tree/VERSION` exists");
234
- lines.push("- [ ] Root NODE.md has valid frontmatter (title, owners)");
235
- lines.push("- [ ] AGENT.md exists with framework markers");
236
- lines.push("- [ ] `context-tree verify` passes with no errors");
237
- lines.push("- [ ] At least one member node exists");
238
- lines.push("");
239
- lines.push("---");
240
- lines.push("");
241
- lines.push("**Important:** As you complete each task, check it off in `.context-tree/progress.md` by changing `- [ ]` to `- [x]`. Run `context-tree verify` when done — it will fail if any items remain unchecked.");
242
- lines.push("");
243
- return lines.join("\n");
244
- }
245
- function writeProgress(repo, content) {
246
- const progressPath = join(repo.root, ".context-tree", "progress.md");
247
- mkdirSync(dirname(progressPath), { recursive: true });
248
- writeFileSync(progressPath, content);
249
- }
250
- function runInit(repo) {
251
- const r = repo ?? new Repo();
252
- if (!r.isGitRepo()) {
253
- console.error("Error: not a git repository. Initialize one first:\n git init");
254
- return 1;
255
- }
256
- if (!r.hasFramework()) {
257
- const seed = cloneSeedTree();
258
- try {
259
- console.log("Copying framework and scaffolding...");
260
- copyFramework(seed, r.root);
261
- renderTemplates(join(r.root, FRAMEWORK_DIR), r.root);
262
- addUpstreamRemote(r.root);
263
- } finally {
264
- rmSync(seed, {
265
- recursive: true,
266
- force: true
267
- });
268
- }
269
- console.log();
270
- }
271
- const groups = evaluateAll(r);
272
- if (groups.length === 0) {
273
- console.log("All checks passed. Your context tree is set up.");
274
- return 0;
275
- }
276
- const output = formatTaskList(groups);
277
- console.log(output);
278
- writeProgress(r, output);
279
- console.log("Progress file written to .context-tree/progress.md");
280
- return 0;
281
- }
282
- //#endregion
283
- export { runInit };