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,224 @@
1
+ import {
2
+ existsSync,
3
+ mkdirSync,
4
+ readdirSync,
5
+ readFileSync,
6
+ statSync,
7
+ writeFileSync,
8
+ } from "node:fs";
9
+ import { dirname, join, relative } from "node:path";
10
+
11
+ const FRONTMATTER_RE = /^---\s*\n(.*?)\n---/s;
12
+ const OWNERS_RE = /^owners:\s*\[([^\]]*)\]/m;
13
+ const SKIP = new Set(["node_modules", "__pycache__"]);
14
+
15
+ export function parseOwners(path: string): string[] | null {
16
+ let text: string;
17
+ try {
18
+ text = readFileSync(path, "utf-8");
19
+ } catch {
20
+ return null;
21
+ }
22
+ const fm = text.match(FRONTMATTER_RE);
23
+ if (!fm) return null;
24
+ const m = fm[1].match(OWNERS_RE);
25
+ if (!m) return null;
26
+ const raw = m[1].trim();
27
+ if (!raw) return []; // owners: [] — will inherit
28
+ return raw
29
+ .split(",")
30
+ .map((o) => o.trim())
31
+ .filter(Boolean);
32
+ }
33
+
34
+ export function resolveNodeOwners(
35
+ folder: string,
36
+ treeRoot: string,
37
+ cache: Map<string, string[]>,
38
+ ): string[] {
39
+ if (cache.has(folder)) return cache.get(folder)!;
40
+
41
+ const nodeMd = join(folder, "NODE.md");
42
+ const owners = parseOwners(nodeMd);
43
+
44
+ let resolved: string[];
45
+ if (owners === null || owners.length === 0) {
46
+ const parent = dirname(folder);
47
+ if (parent.length >= treeRoot.length && parent !== folder) {
48
+ resolved = resolveNodeOwners(parent, treeRoot, cache);
49
+ } else {
50
+ resolved = [];
51
+ }
52
+ } else {
53
+ resolved = owners;
54
+ }
55
+
56
+ cache.set(folder, resolved);
57
+ return resolved;
58
+ }
59
+
60
+ function isWildcard(owners: string[] | null): boolean {
61
+ return owners !== null && owners.includes("*");
62
+ }
63
+
64
+ function codeownersPath(path: string, treeRoot: string): string {
65
+ const r = relative(treeRoot, path).replace(/\\/g, "/");
66
+ try {
67
+ if (statSync(path).isDirectory()) return `/${r}/`;
68
+ } catch {
69
+ // not a dir
70
+ }
71
+ return `/${r}`;
72
+ }
73
+
74
+ export function formatOwners(owners: string[]): string {
75
+ const seen = new Set<string>();
76
+ const result: string[] = [];
77
+ for (const o of owners) {
78
+ if (!seen.has(o)) {
79
+ seen.add(o);
80
+ result.push(`@${o}`);
81
+ }
82
+ }
83
+ return result.join(" ");
84
+ }
85
+
86
+ export function collectEntries(
87
+ root: string,
88
+ ): [string, string[]][] {
89
+ const nodeCache = new Map<string, string[]>();
90
+ const entries: [string, string[]][] = [];
91
+
92
+ function walk(dir: string): void {
93
+ let names: string[];
94
+ try {
95
+ names = readdirSync(dir).sort();
96
+ } catch {
97
+ return;
98
+ }
99
+ for (const name of names) {
100
+ const full = join(dir, name);
101
+ const parts = relative(root, full).split("/");
102
+ if (parts.some((p) => SKIP.has(p) || p.startsWith("."))) continue;
103
+ try {
104
+ if (!statSync(full).isDirectory()) continue;
105
+ } catch {
106
+ continue;
107
+ }
108
+ if (!existsSync(join(full, "NODE.md"))) {
109
+ walk(full);
110
+ continue;
111
+ }
112
+
113
+ const folderOwners = resolveNodeOwners(full, root, nodeCache);
114
+
115
+ if (folderOwners.length > 0 && !isWildcard(folderOwners)) {
116
+ entries.push([codeownersPath(full, root), folderOwners]);
117
+ }
118
+
119
+ // Leaf files
120
+ for (const child of readdirSync(full).sort()) {
121
+ const childPath = join(full, child);
122
+ try {
123
+ if (
124
+ !statSync(childPath).isFile() ||
125
+ !child.endsWith(".md") ||
126
+ child === "NODE.md"
127
+ )
128
+ continue;
129
+ } catch {
130
+ continue;
131
+ }
132
+ const leafOwners = parseOwners(childPath);
133
+ if (isWildcard(leafOwners)) continue;
134
+ if (leafOwners && leafOwners.length > 0) {
135
+ const nonWildcardFolder = folderOwners.filter((o) => o !== "*");
136
+ const combined = [
137
+ ...nonWildcardFolder,
138
+ ...leafOwners.filter((o) => !nonWildcardFolder.includes(o)),
139
+ ];
140
+ if (combined.length > 0) {
141
+ entries.push([codeownersPath(childPath, root), combined]);
142
+ }
143
+ }
144
+ }
145
+
146
+ walk(full);
147
+ }
148
+ }
149
+
150
+ walk(root);
151
+
152
+ // Root-level leaf files
153
+ const rootOwners = resolveNodeOwners(root, root, nodeCache);
154
+ for (const child of readdirSync(root).sort()) {
155
+ const childPath = join(root, child);
156
+ try {
157
+ if (
158
+ !statSync(childPath).isFile() ||
159
+ !child.endsWith(".md") ||
160
+ child === "NODE.md"
161
+ )
162
+ continue;
163
+ } catch {
164
+ continue;
165
+ }
166
+ const leafOwners = parseOwners(childPath);
167
+ if (isWildcard(leafOwners)) continue;
168
+ if (leafOwners && leafOwners.length > 0) {
169
+ const combined = [
170
+ ...rootOwners,
171
+ ...leafOwners.filter((o) => !rootOwners.includes(o)),
172
+ ];
173
+ entries.push([codeownersPath(childPath, root), combined]);
174
+ }
175
+ }
176
+
177
+ // Root entry (catch-all)
178
+ if (rootOwners.length > 0) {
179
+ entries.unshift(["/*", rootOwners]);
180
+ }
181
+
182
+ return entries;
183
+ }
184
+
185
+ export function generate(
186
+ treeRoot: string,
187
+ opts?: { check?: boolean },
188
+ ): number {
189
+ const check = opts?.check ?? false;
190
+ const entries = collectEntries(treeRoot);
191
+ const codeownersFile = join(treeRoot, ".github", "CODEOWNERS");
192
+
193
+ const lines = ["# Auto-generated from Context Tree. Do not edit manually.", ""];
194
+ for (const [pattern, owners] of entries) {
195
+ if (owners.length > 0) {
196
+ lines.push(`${pattern.padEnd(50)} ${formatOwners(owners)}`);
197
+ }
198
+ }
199
+ lines.push(""); // trailing newline
200
+ const content = lines.join("\n");
201
+
202
+ if (check) {
203
+ if (existsSync(codeownersFile) && readFileSync(codeownersFile, "utf-8") === content) {
204
+ console.log("CODEOWNERS is up-to-date.");
205
+ return 0;
206
+ }
207
+ console.log(
208
+ "CODEOWNERS is out-of-date. Run: npx tsx skills/first-tree/assets/framework/helpers/generate-codeowners.ts",
209
+ );
210
+ return 1;
211
+ }
212
+
213
+ mkdirSync(dirname(codeownersFile), { recursive: true });
214
+ writeFileSync(codeownersFile, content);
215
+ console.log(`Wrote ${relative(treeRoot, codeownersFile)}`);
216
+ return 0;
217
+ }
218
+
219
+ const isDirectRun =
220
+ process.argv[1]?.endsWith("generate-codeowners.ts") ||
221
+ process.argv[1]?.endsWith("generate-codeowners.js");
222
+ if (isDirectRun) {
223
+ process.exit(generate(process.cwd()));
224
+ }
@@ -0,0 +1,15 @@
1
+ #!/bin/bash
2
+ # Injects NODE.md content as additionalContext at session start.
3
+
4
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
5
+ TREE_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
6
+ NODE_MD="$TREE_ROOT/NODE.md"
7
+
8
+ if [ -f "$NODE_MD" ]; then
9
+ # Escape for JSON: backslashes, double quotes, newlines, tabs, carriage returns
10
+ CONTENT=$(sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' -e 's/ /\\t/g' -e 's/\r/\\r/g' "$NODE_MD" | awk '{printf "%s\\n", $0}' | sed 's/\\n$//')
11
+ echo "{\"hookSpecificOutput\":{\"hookEventName\":\"SessionStart\",\"additionalContext\":\"${CONTENT}\"}}"
12
+ exit 0
13
+ fi
14
+
15
+ exit 0
@@ -0,0 +1,179 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Run Claude Code review and extract structured JSON output.
4
+ *
5
+ * Builds the review prompt, invokes Claude Code with stream-json output,
6
+ * extracts text from the stream, parses the review JSON, and retries up
7
+ * to 3 times on failure. Writes the validated review JSON to /tmp/review.json.
8
+ */
9
+
10
+ import { execFileSync } from "node:child_process";
11
+ import { readFileSync, writeFileSync } from "node:fs";
12
+ import { homedir } from "node:os";
13
+ import { join } from "node:path";
14
+
15
+ const CLAUDE_BIN = join(homedir(), ".local", "bin", "claude");
16
+ const MAX_ATTEMPTS = 3;
17
+ // Per-invocation budget cap. Worst case is $1.50 total (3 × $0.50),
18
+ // though retries are cheap in practice due to cached context via --continue.
19
+ const MAX_BUDGET_USD = 0.5;
20
+
21
+ function buildPrompt(diffPath: string): string {
22
+ const parts: string[] = [];
23
+ const files: [string, string][] = [
24
+ ["AGENT.md", "AGENT.md"],
25
+ ["Root NODE.md", "NODE.md"],
26
+ [
27
+ "Review Instructions",
28
+ "skills/first-tree/assets/framework/prompts/pr-review.md",
29
+ ],
30
+ ];
31
+ for (const [heading, path] of files) {
32
+ const content = readFileSync(path, "utf-8");
33
+ parts.push(`## ${heading}\n\n${content}`);
34
+ }
35
+ const diff = readFileSync(diffPath, "utf-8");
36
+ parts.push(`## PR Diff\n\n\`\`\`\n${diff}\`\`\``);
37
+ return parts.join("\n\n");
38
+ }
39
+
40
+ export function extractStreamText(jsonl: string): string {
41
+ const textParts: string[] = [];
42
+ let resultText = "";
43
+ for (const line of jsonl.split("\n")) {
44
+ const trimmed = line.trim();
45
+ if (!trimmed) continue;
46
+ let msg: Record<string, unknown>;
47
+ try {
48
+ msg = JSON.parse(trimmed);
49
+ } catch {
50
+ continue;
51
+ }
52
+ if (msg.type === "assistant") {
53
+ const message = msg.message as Record<string, unknown> | undefined;
54
+ const content = message?.content as Array<Record<string, unknown>> | undefined;
55
+ if (content) {
56
+ for (const block of content) {
57
+ if (block.type === "text" && typeof block.text === "string") {
58
+ textParts.push(block.text);
59
+ }
60
+ }
61
+ }
62
+ }
63
+ if (msg.type === "result") {
64
+ const r = msg.result;
65
+ if (typeof r === "string" && r) {
66
+ resultText = r;
67
+ }
68
+ }
69
+ }
70
+ // Prefer assistant text blocks; fall back to result field
71
+ return textParts.length > 0 ? textParts.join("") : resultText;
72
+ }
73
+
74
+ function runClaude(opts: { prompt?: string; continueSession?: boolean }): string {
75
+ const cmd = [
76
+ "-p",
77
+ "--dangerously-skip-permissions",
78
+ "--output-format", "stream-json",
79
+ "--verbose",
80
+ "--max-budget-usd", String(MAX_BUDGET_USD),
81
+ ];
82
+ if (opts.continueSession) {
83
+ cmd.push("--continue");
84
+ }
85
+
86
+ try {
87
+ const stdout = execFileSync(CLAUDE_BIN, cmd, {
88
+ input: opts.prompt,
89
+ encoding: "utf-8",
90
+ stdio: ["pipe", "pipe", "inherit"],
91
+ maxBuffer: 50 * 1024 * 1024,
92
+ });
93
+ return extractStreamText(stdout);
94
+ } catch (err: unknown) {
95
+ const code = (err as { status?: number }).status ?? 1;
96
+ console.error(`::error::Claude exited with code ${code}`);
97
+ process.exit(1);
98
+ }
99
+ }
100
+
101
+ export interface Review {
102
+ verdict: string;
103
+ summary?: string;
104
+ inline_comments?: Array<{ file: string; line: number; comment: string }>;
105
+ }
106
+
107
+ export function extractReviewJson(text: string): Review | null {
108
+ if (!text.trim()) return null;
109
+ // Strip markdown fences
110
+ let cleaned = text.replace(/```json\s*/g, "").replace(/```\s*/g, "");
111
+ const match = cleaned.match(/\{[\s\S]*\}/);
112
+ if (!match) return null;
113
+ let obj: Record<string, unknown>;
114
+ try {
115
+ obj = JSON.parse(match[0]);
116
+ } catch {
117
+ return null;
118
+ }
119
+ if (!obj.verdict) return null;
120
+ return obj as unknown as Review;
121
+ }
122
+
123
+ function main(): void {
124
+ const prompt = buildPrompt("/tmp/pr-diff.txt");
125
+ console.log(`=== Prompt size: ${Buffer.byteLength(prompt)} bytes ===`);
126
+
127
+ let text = runClaude({ prompt });
128
+
129
+ for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
130
+ if (text.trim()) {
131
+ console.log(`=== Attempt ${attempt}: Raw output ===`);
132
+ console.log(text);
133
+ console.log("=== End raw output ===");
134
+ } else {
135
+ console.log(`=== Attempt ${attempt}: Empty output ===`);
136
+ }
137
+
138
+ const review = extractReviewJson(text);
139
+ if (review) {
140
+ console.log(
141
+ `Valid JSON with verdict='${review.verdict}' extracted on attempt ${attempt}`,
142
+ );
143
+ writeFileSync("/tmp/review.json", JSON.stringify(review));
144
+ return;
145
+ }
146
+
147
+ if (attempt === MAX_ATTEMPTS) {
148
+ console.error(
149
+ `::error::Failed to extract valid review JSON after ${MAX_ATTEMPTS} attempts`,
150
+ );
151
+ process.exit(1);
152
+ }
153
+
154
+ let retryMsg: string;
155
+ if (text.trim()) {
156
+ retryMsg =
157
+ "Your previous output could not be parsed as valid review JSON. " +
158
+ "Please output ONLY a valid JSON object matching the required schema " +
159
+ "(with verdict, optional summary, optional inline_comments). " +
160
+ "No other text, no markdown fences.";
161
+ } else {
162
+ retryMsg =
163
+ "You did not produce any visible text output. " +
164
+ "Please output ONLY the review as a valid JSON object with " +
165
+ "verdict (required), summary (optional), and inline_comments (optional). " +
166
+ "No other text, no markdown fences.";
167
+ }
168
+
169
+ console.log(`::warning::Attempt ${attempt} failed, asking Claude to retry...`);
170
+ text = runClaude({ prompt: retryMsg, continueSession: true });
171
+ }
172
+ }
173
+
174
+ const isDirectRun =
175
+ process.argv[1]?.endsWith("run-review.ts") ||
176
+ process.argv[1]?.endsWith("run-review.js");
177
+ if (isDirectRun) {
178
+ main();
179
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "skillName": "first-tree",
4
+ "runtimeAssetRoot": "assets/framework",
5
+ "versionFile": "VERSION",
6
+ "templatesDir": "templates",
7
+ "workflowsDir": "workflows",
8
+ "promptsDir": "prompts",
9
+ "examplesDir": "examples",
10
+ "helpersDir": "helpers"
11
+ }
@@ -0,0 +1,38 @@
1
+ You are a code reviewer for the context tree.
2
+
3
+ Read the NODE.md of every domain touched by the PR.
4
+ Read the leaf nodes in affected domains to check for conflicts or redundancy.
5
+ Follow soft_links to check for conflicts with related domains.
6
+
7
+ Check for:
8
+ 1. Tree structure conventions (NODE.md in folders, frontmatter with title/owners)
9
+ 2. Ownership — are the right owners declared?
10
+ 3. Principles compliance — design in tree, execution in code
11
+ 4. Soft links for cross-domain dependencies
12
+ 5. Consistency with existing nodes
13
+ 6. Clarity for agent consumption
14
+
15
+ After reading the relevant tree files, output your review as a single JSON object in EXACTLY this schema:
16
+
17
+ ```json
18
+ {
19
+ "verdict": "APPROVE" | "REQUEST_CHANGES" | "COMMENT",
20
+ "summary": "<1-3 sentence overall assessment>",
21
+ "inline_comments": [
22
+ {
23
+ "file": "<path>",
24
+ "line": <number>,
25
+ "comment": "<comment text>"
26
+ }
27
+ ]
28
+ }
29
+ ```
30
+
31
+ - `verdict` (required): one of `APPROVE`, `REQUEST_CHANGES`, or `COMMENT`.
32
+ - `summary` (optional): concise overall assessment.
33
+ - `inline_comments` (optional): omit the field entirely if there are none.
34
+
35
+ Rules:
36
+ - Output ONLY the JSON object, no other text.
37
+ - CRITICAL: The `line` number MUST be a line that appears in the diff (a changed or added line). GitHub only allows inline comments on lines that are part of the diff. Do NOT comment on unchanged lines — if you need to reference an unchanged line, include it in the `summary` instead.
38
+ - Only flag real problems in inline comments. Do NOT post positive feedback, praise, or "looks good" comments on individual lines. Do NOT suggest tiny wording improvements or stylistic nitpicks. If a change is correct, say nothing about it — silence means approval.
@@ -0,0 +1,48 @@
1
+ <!-- BEGIN CONTEXT-TREE FRAMEWORK — do not edit this section -->
2
+ # Agent Instructions for Context Tree
3
+
4
+ You are working in a **Context Tree** — the living source of truth for decisions across the organization. Read and follow this before doing anything.
5
+
6
+ ## Principles
7
+
8
+ 1. **Source of truth for decisions, not execution.** The tree captures the *what* and *why* — strategic choices, cross-domain relationships, constraints. Execution details stay in source systems. If an agent needs this information to *decide* on an approach, it belongs in the tree. If the agent only needs it to *execute*, it stays in the source system.
9
+
10
+ 2. **Agents are first-class participants.** The tree is designed to be navigated and updated by agents, not just humans. Domains are organized by concern — what an agent needs to know to act — not by repo, team, or org chart.
11
+
12
+ 3. **Transparency by default.** All information is readable by everyone. Writing requires owner approval; reading is open.
13
+
14
+ 4. **Git-native tree structure.** Each node is a file; each domain is a directory. Soft links allow cross-references without the complexity of a full graph. History, ownership, and review follow Git conventions.
15
+
16
+ See `skills/first-tree/references/principles.md` for detailed explanations and examples.
17
+
18
+ ## Before Every Task
19
+
20
+ 1. **Read the root NODE.md** to understand the domain map.
21
+ 2. **Read the NODE.md of every domain relevant to your task.** If unsure which domains are relevant, start from root and follow the structure — it's organized by concern, not by repo.
22
+ 3. **Follow soft_links.** If a node declares `soft_links` in its frontmatter, read those linked nodes too. They exist because the domains are related.
23
+ 4. **Read leaf nodes that match your task.** NODE.md tells you what exists in each domain — scan it and read the leaves that are relevant.
24
+
25
+ Do not skip this. The tree is already a compression of expensive knowledge — cross-domain relationships, strategic decisions, constraints. An agent that skips the tree will produce decisions that conflict with existing ones.
26
+
27
+ ## During the Task
28
+
29
+ - **Decide in the tree, execute in source systems.** If the task involves a decision (not just a bug fix), draft or update the relevant tree node before executing.
30
+ - **The tree is not for execution details.** Function signatures, DB schemas, API endpoints, ad copy — those live in source systems. The tree captures the *why* and *how things connect*.
31
+ - **Respect ownership.** Each node declares owners in its frontmatter. If your changes touch a domain you don't own, flag it — the owner needs to review.
32
+
33
+ ## After Every Task
34
+
35
+ Ask yourself: **Does the tree need updating?**
36
+
37
+ - Did you discover something the tree didn't capture? (A cross-domain dependency, a new constraint, a decision that future agents would need.)
38
+ - Did you find the tree was wrong or outdated? That's a tree bug — fix it.
39
+ - Not every task changes the tree, but the question must always be asked.
40
+
41
+ ## Reference
42
+
43
+ For ownership rules, tree structure, and key files, see [NODE.md](NODE.md) and `skills/first-tree/references/ownership-and-naming.md`.
44
+ <!-- END CONTEXT-TREE FRAMEWORK -->
45
+
46
+ # Project-Specific Instructions
47
+
48
+ <!-- Add your project-specific agent instructions below this line. -->
@@ -0,0 +1,18 @@
1
+ ---
2
+ title: "<Display Name>"
3
+ owners: [<github-username>]
4
+ type: "<human | personal_assistant | autonomous_agent>"
5
+ role: "<role title>"
6
+ domains:
7
+ - "<domain>"
8
+ ---
9
+
10
+ # <Display Name>
11
+
12
+ ## About
13
+
14
+ <!-- Who you are and what you bring to the team. -->
15
+
16
+ ## Current Focus
17
+
18
+ <!-- What you're actively working on. -->
@@ -0,0 +1,45 @@
1
+ ---
2
+ title: Members
3
+ owners: []
4
+ ---
5
+
6
+ # Members
7
+
8
+ Member definitions, work scope, and personal node specifications.
9
+
10
+ Members are **humans and AI agents**. Both are first-class participants in the organization — they own nodes, make decisions, and collaborate through the tree.
11
+
12
+ ---
13
+
14
+ ## Joining
15
+
16
+ 1. Create a personal directory under `members/` with a `NODE.md` (e.g., `members/alice/NODE.md`)
17
+ 2. Follow the required personal node format — CI will reject PRs with missing fields
18
+ 3. Be assigned as owner of relevant domain nodes
19
+
20
+ ---
21
+
22
+ ## Personal Node Format
23
+
24
+ Each member is a **directory** under `members/` containing a `NODE.md`. Required frontmatter fields:
25
+
26
+ ```yaml
27
+ ---
28
+ title: "<display name>"
29
+ owners: [<github-username>]
30
+ type: "<human | personal_assistant | autonomous_agent>"
31
+ role: "<role in the organization>"
32
+ domains:
33
+ - "<high-level domain or direction>"
34
+ ---
35
+ ```
36
+
37
+ See `skills/first-tree/assets/framework/templates/member-node.md.template` for a full scaffold.
38
+
39
+ ---
40
+
41
+ ## Principles
42
+
43
+ **Keep your node fresh.** Your personal node is how other members understand you. Stale information leads to poor decisions.
44
+
45
+ **Trust the tree.** Communicate through the tree, not around it.
@@ -0,0 +1,38 @@
1
+ ---
2
+ title: "<Your Organization>"
3
+ owners: [<your-github-username>]
4
+ ---
5
+
6
+ # <Your Organization>
7
+
8
+ <!-- PLACEHOLDER: Replace this with a description of your organization and what this tree captures. -->
9
+
10
+ The living source of truth for your organization. A structured knowledge base that agents and humans build and maintain together.
11
+
12
+ ---
13
+
14
+ ## Domains
15
+
16
+ <!-- Add your domains here. Each domain is a top-level directory with a NODE.md.
17
+ Organize by concern — what an agent needs to know to act — not by repo or team.
18
+
19
+ Examples:
20
+ - **[engineering/](engineering/NODE.md)** — Backend, frontend, infrastructure decisions.
21
+ - **[product/](product/NODE.md)** — Product strategy, roadmap, user research.
22
+ - **[marketing/](marketing/NODE.md)** — Brand, positioning, campaigns.
23
+ - **[members/](members/NODE.md)** — Team member definitions and responsibilities.
24
+ -->
25
+
26
+ - **[members/](members/NODE.md)** — Team member definitions and responsibilities.
27
+
28
+ ---
29
+
30
+ ## Working with the Tree
31
+
32
+ See [AGENT.md](AGENT.md) for agent instructions — the before/during/after workflow, ownership model, and tree maintenance.
33
+
34
+ See [about.md](skills/first-tree/references/about.md) for background — the problem, the idea, and who it's for.
35
+
36
+ See the installed framework documentation:
37
+ - [principles.md](skills/first-tree/references/principles.md) — core principles with examples
38
+ - [ownership-and-naming.md](skills/first-tree/references/ownership-and-naming.md) — node naming and ownership model
@@ -0,0 +1,31 @@
1
+ name: Update CODEOWNERS
2
+
3
+ on:
4
+ pull_request:
5
+ types: [opened, synchronize]
6
+
7
+ jobs:
8
+ update:
9
+ runs-on: ubuntu-latest
10
+ permissions:
11
+ contents: write
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+ with:
15
+ ref: ${{ github.head_ref }}
16
+ token: ${{ github.token }}
17
+
18
+ - uses: actions/setup-node@v4
19
+ with:
20
+ node-version: "22"
21
+
22
+ - run: npx tsx skills/first-tree/assets/framework/helpers/generate-codeowners.ts
23
+
24
+ - name: Commit if changed
25
+ run: |
26
+ git add .github/CODEOWNERS
27
+ git diff --cached --quiet .github/CODEOWNERS && exit 0
28
+ git config user.name "github-actions[bot]"
29
+ git config user.email "github-actions[bot]@users.noreply.github.com"
30
+ git commit -m "chore: update CODEOWNERS from tree ownership"
31
+ git push