first-tree 0.0.7 → 0.0.8

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 (32) hide show
  1. package/README.md +11 -4
  2. package/dist/bootstrap-YRjfHJp7.js +28 -0
  3. package/dist/cli.js +10 -4
  4. package/dist/{help-BRO4mTG6.js → help-CDfaFrzl.js} +1 -1
  5. package/dist/{init-BSs0ILp_.js → init-DjSVkUeR.js} +11 -6
  6. package/dist/onboarding-BiHx2jy5.js +10 -0
  7. package/dist/onboarding-Ce033qaW.js +2 -0
  8. package/dist/publish-D0crNDjz.js +504 -0
  9. package/dist/{repo-0z7N9r17.js → repo-BeVpMoHi.js} +2 -1
  10. package/dist/{source-integration-C2iiN4k_.js → source-integration-DMxnl8Dw.js} +1 -1
  11. package/dist/{upgrade-DvBdbph3.js → upgrade-B_NTlNrx.js} +2 -2
  12. package/dist/{verify-DRt5mCqO.js → verify-Chhm1vOF.js} +1 -1
  13. package/package.json +1 -1
  14. package/skills/first-tree/SKILL.md +14 -5
  15. package/skills/first-tree/assets/framework/VERSION +1 -1
  16. package/skills/first-tree/engine/commands/publish.ts +5 -0
  17. package/skills/first-tree/engine/init.ts +11 -5
  18. package/skills/first-tree/engine/publish.ts +807 -0
  19. package/skills/first-tree/engine/runtime/asset-loader.ts +1 -0
  20. package/skills/first-tree/engine/runtime/bootstrap.ts +40 -0
  21. package/skills/first-tree/references/maintainer-build-and-distribution.md +3 -0
  22. package/skills/first-tree/references/maintainer-thin-cli.md +1 -1
  23. package/skills/first-tree/references/onboarding.md +12 -9
  24. package/skills/first-tree/references/source-map.md +3 -1
  25. package/skills/first-tree/references/source-workspace-installation.md +13 -13
  26. package/skills/first-tree/references/upgrade-contract.md +11 -0
  27. package/skills/first-tree/tests/init.test.ts +19 -0
  28. package/skills/first-tree/tests/publish.test.ts +248 -0
  29. package/skills/first-tree/tests/skill-artifacts.test.ts +12 -0
  30. package/skills/first-tree/tests/thin-cli.test.ts +1 -0
  31. package/dist/onboarding-BS8btkG4.js +0 -2
  32. package/dist/onboarding-D3hnxIie.js +0 -10
@@ -18,6 +18,7 @@ export const FRAMEWORK_PROMPTS_DIR = join(FRAMEWORK_ASSET_ROOT, "prompts");
18
18
  export const FRAMEWORK_EXAMPLES_DIR = join(FRAMEWORK_ASSET_ROOT, "examples");
19
19
  export const FRAMEWORK_HELPERS_DIR = join(FRAMEWORK_ASSET_ROOT, "helpers");
20
20
  export const INSTALLED_PROGRESS = join(SKILL_ROOT, "progress.md");
21
+ export const BOOTSTRAP_STATE = join(SKILL_ROOT, "bootstrap.json");
21
22
  export const AGENT_INSTRUCTIONS_FILE = "AGENTS.md";
22
23
  export const LEGACY_AGENT_INSTRUCTIONS_FILE = "AGENT.md";
23
24
  export const AGENT_INSTRUCTIONS_TEMPLATE = "agents.md.template";
@@ -0,0 +1,40 @@
1
+ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { BOOTSTRAP_STATE } from "#skill/engine/runtime/asset-loader.js";
4
+
5
+ export interface BootstrapState {
6
+ sourceRepoName: string;
7
+ sourceRepoPath: string;
8
+ treeRepoName: string;
9
+ }
10
+
11
+ export function bootstrapStatePath(root: string): string {
12
+ return join(root, BOOTSTRAP_STATE);
13
+ }
14
+
15
+ export function readBootstrapState(root: string): BootstrapState | null {
16
+ const path = bootstrapStatePath(root);
17
+ try {
18
+ const parsed = JSON.parse(readFileSync(path, "utf-8")) as Partial<BootstrapState>;
19
+ if (
20
+ typeof parsed.sourceRepoName !== "string"
21
+ || typeof parsed.sourceRepoPath !== "string"
22
+ || typeof parsed.treeRepoName !== "string"
23
+ ) {
24
+ return null;
25
+ }
26
+ return {
27
+ sourceRepoName: parsed.sourceRepoName,
28
+ sourceRepoPath: parsed.sourceRepoPath,
29
+ treeRepoName: parsed.treeRepoName,
30
+ };
31
+ } catch {
32
+ return null;
33
+ }
34
+ }
35
+
36
+ export function writeBootstrapState(root: string, state: BootstrapState): void {
37
+ const path = bootstrapStatePath(root);
38
+ mkdirSync(dirname(path), { recursive: true });
39
+ writeFileSync(path, `${JSON.stringify(state, null, 2)}\n`);
40
+ }
@@ -53,6 +53,9 @@ another skill reference, not only in the files themselves.
53
53
  - Default dedicated-tree-repo creation must stay local-only. It may create a
54
54
  sibling git repo on disk, but it must not require remote repo creation or
55
55
  source-repo cloning.
56
+ - `context-tree publish` is the explicit networked second-stage command for
57
+ GitHub repo creation, submodule add-back, and optional source-repo PR
58
+ opening. Keep that remote behavior there instead of expanding default `init`.
56
59
  - If you change anything that gets copied into user repos, bump
57
60
  `assets/framework/VERSION` and keep the upgrade task text in sync.
58
61
  - If packaging changes alter what gets installed into user repos, update
@@ -33,7 +33,7 @@ These root files are shell code, not canonical knowledge stores:
33
33
  - Keep root prose short. It should point to the skill, not duplicate the skill.
34
34
  - Keep command semantics, install layout rules, and maintainer guidance in the
35
35
  skill references.
36
- - If init/upgrade semantics change for source/workspace repos versus dedicated
36
+ - If init/publish/upgrade semantics change for source/workspace repos versus dedicated
37
37
  tree repos, update `references/source-workspace-installation.md` and
38
38
  `references/upgrade-contract.md` instead of expanding root shell prose.
39
39
  - If the shell gains behavior that is not obviously mechanical, move that
@@ -55,6 +55,8 @@ Information an agent needs to **decide** on an approach — not to execute it.
55
55
 
56
56
  - A source/workspace Git repository, or an already-created dedicated tree repo
57
57
  - Node.js 18+
58
+ - GitHub CLI (`gh`) if you want `context-tree publish` to create the remote
59
+ `*-context` repo and open the source-repo PR for you
58
60
  - The npm package is `first-tree`, the installed CLI command is
59
61
  `context-tree`.
60
62
  - `context-tree init` installs the framework skill into
@@ -74,6 +76,7 @@ files are scaffolded only in the dedicated tree repo.
74
76
  cd my-org
75
77
  context-tree init
76
78
  cd ../my-org-context
79
+ context-tree publish --open-pr
77
80
  ```
78
81
 
79
82
  If you already created a dedicated tree repo manually, initialize it in place:
@@ -93,21 +96,20 @@ Either way, the framework installs into `.agents/skills/first-tree/` and
93
96
  `members/NODE.md`), and generates a task list in
94
97
  `.agents/skills/first-tree/progress.md`.
95
98
 
96
- Publishing tip: keep the tree repo in the same GitHub organization as the
97
- source repo unless you have a reason not to.
98
-
99
99
  Hard boundary: do **not** create `NODE.md`, `members/`, or tree-scoped
100
100
  `AGENTS.md` in the source/workspace repo. Those files belong only in the
101
101
  dedicated `*-context` repo.
102
102
 
103
103
  Default agent workflow after initialization:
104
104
 
105
- 1. Create and push the dedicated `*-context` repo in the same GitHub
106
- organization as the source repo.
107
- 2. Add the dedicated tree repo back to the source/workspace repo as a `git submodule`.
108
- 3. Open a PR against the source/workspace repo's default branch for the local
109
- skill integration plus the new submodule pointer. Do not merge it
110
- automatically.
105
+ 1. Draft the initial tree version in the dedicated `*-context` repo.
106
+ 2. Run `context-tree publish --open-pr` from that dedicated tree repo. It will
107
+ create or reuse the GitHub `*-context` repo in the same owner/org as the
108
+ source repo, add it back to the source/workspace repo as a `git submodule`,
109
+ and open the source-repo PR.
110
+ 3. After publish succeeds, treat the source repo's submodule checkout as the
111
+ canonical local working copy for the tree. The temporary sibling bootstrap
112
+ checkout can be deleted when you no longer need it.
111
113
 
112
114
  ### Step 2: Work Through the Task List
113
115
 
@@ -182,6 +184,7 @@ The tree doesn't duplicate source code — it captures what connects things and
182
184
  | Command | Description |
183
185
  |---------|-------------|
184
186
  | `context-tree init` | Install local source/workspace integration and create or refresh a dedicated tree repo. By default, running in a source/workspace repo creates a sibling `<repo>-context`; use `--here` only when you are already inside the dedicated tree repo. |
187
+ | `context-tree publish` | Publish a dedicated tree repo to GitHub, add it back to the source/workspace repo as a submodule, and optionally open the source-repo PR. |
185
188
  | `context-tree verify` | Check the installed progress file for unchecked items + run deterministic validation. Use `--tree-path` when invoking from another working directory. |
186
189
  | `context-tree upgrade` | Refresh the installed framework skill from the currently running `first-tree` npm package and generate follow-up tasks. Use `--tree-path` when invoking from another working directory. |
187
190
  | `context-tree help onboarding` | Print this onboarding guide. |
@@ -42,12 +42,13 @@ These skill-owned files implement the framework behavior.
42
42
  | Path | Purpose |
43
43
  | --- | --- |
44
44
  | `engine/commands/` | Stable command entrypoints that the thin CLI imports |
45
- | `engine/init.ts` / `engine/verify.ts` / `engine/upgrade.ts` | Command implementations for install, verify, and upgrade |
45
+ | `engine/init.ts` / `engine/publish.ts` / `engine/verify.ts` / `engine/upgrade.ts` | Command implementations for install, publish, verify, and upgrade |
46
46
  | `engine/onboarding.ts` | Canonical onboarding text loader |
47
47
  | `engine/repo.ts` | Repo inspection, source-vs-tree heuristics, and worktree-aware git-root helpers |
48
48
  | `engine/rules/` | Situation-aware task generation after `init` |
49
49
  | `engine/validators/` | Deterministic tree and member validation |
50
50
  | `engine/runtime/asset-loader.ts` | Path constants plus legacy-layout detection |
51
+ | `engine/runtime/bootstrap.ts` | Dedicated-tree bootstrap metadata for the publish workflow |
51
52
  | `engine/runtime/installer.ts` | Bundled-package discovery, skill copy, and template-render helpers |
52
53
  | `engine/runtime/upgrader.ts` | Packaged-skill version comparison helpers |
53
54
  | `engine/runtime/adapters.ts` | Agent-integration path helpers |
@@ -74,6 +75,7 @@ not become the only place important maintainer knowledge lives.
74
75
  | Path | Coverage |
75
76
  | --- | --- |
76
77
  | `tests/init.test.ts` | Init scaffolding behavior |
78
+ | `tests/publish.test.ts` | Publish workflow orchestration |
77
79
  | `tests/verify.test.ts` | Verification and progress gating |
78
80
  | `tests/rules.test.ts` | Task generation text |
79
81
  | `tests/asset-loader.test.ts` | Layout detection and path precedence |
@@ -38,20 +38,20 @@ existing source or workspace repository.
38
38
  When an agent is asked to install first-tree for a source/workspace repo, the
39
39
  default workflow is:
40
40
 
41
- 1. Install the bundled first-tree skill into the current repo.
42
- 2. Upsert the `FIRST-TREE-SOURCE-INTEGRATION:` line in root `AGENTS.md` and
43
- `CLAUDE.md`.
44
- 3. Create a sibling dedicated tree repo named `<repo>-context`.
45
- 4. Prefer creating and pushing that repo in the same GitHub organization as
46
- the source repo, matching the source repo's default visibility unless the
47
- user asks for something else.
48
- 5. Add the dedicated tree repo back to the source/workspace repo as a `git submodule`.
49
- 6. Run `context-tree init --here` inside the dedicated tree repo.
50
- 7. Draft the first tree version from the real codebase, docs, and ownership
41
+ 1. Run `context-tree init` from the current source/workspace repo.
42
+ 2. Switch into the sibling dedicated tree repo named `<repo>-context`.
43
+ 3. Draft the first tree version from the real codebase, docs, and ownership
51
44
  signals.
52
- 8. Open a PR against the source/workspace repo's default branch for the local
53
- skill integration plus the new submodule pointer. Do not merge it
54
- automatically.
45
+ 4. Run `context-tree publish --open-pr` from the dedicated tree repo. It will:
46
+ create or reuse the GitHub `*-context` repo in the same owner/org as the
47
+ source repo, push the tree, add it back to the source/workspace repo as a
48
+ `git submodule`, and open the source-repo PR.
49
+ 5. After publish succeeds, treat the source repo's submodule checkout as the
50
+ canonical local working copy for the tree. The temporary sibling bootstrap
51
+ checkout can be deleted when you no longer need it.
52
+
53
+ If the dedicated tree repo was initialized manually with `context-tree init --here`
54
+ and publish cannot infer the source repo, pass `--source-repo PATH`.
55
55
 
56
56
  ## Verification And Upgrade
57
57
 
@@ -59,6 +59,9 @@ For a dedicated tree repo, the tree content still lives outside the skill:
59
59
  - `NODE.md`
60
60
  - `AGENTS.md`
61
61
  - `members/`
62
+ - `.agents/skills/first-tree/bootstrap.json` when `context-tree init` was run
63
+ from a separate source/workspace repo and the publish workflow needs to
64
+ remember that source repo path
62
65
 
63
66
  The repo-owned `.agents/skills/first-tree/` path is the primary installed root
64
67
  for progress state, workflow references, and helper scripts. The matching
@@ -83,6 +86,14 @@ skill discovery and hooks.
83
86
  - validates root/frontmatter/agent markers
84
87
  - runs node and member validators
85
88
  - must reject source/workspace repos that carry only local integration
89
+ - `context-tree publish`
90
+ - is the explicit second-stage command for publishing a dedicated tree repo
91
+ to GitHub after local bootstrap
92
+ - reads dedicated-tree bootstrap metadata from
93
+ `.agents/skills/first-tree/bootstrap.json` when available
94
+ - may create or reuse the GitHub `*-context` repo, push tree commits, add it
95
+ back to the source/workspace repo as a git submodule, and optionally open
96
+ the source-repo PR
86
97
  - `context-tree upgrade`
87
98
  - compares the installed skill payload version to the skill bundled with the
88
99
  currently running `first-tree` package
@@ -11,6 +11,7 @@ import {
11
11
  import { Repo } from "#skill/engine/repo.js";
12
12
  import {
13
13
  AGENT_INSTRUCTIONS_FILE,
14
+ BOOTSTRAP_STATE,
14
15
  CLAUDE_INSTRUCTIONS_FILE,
15
16
  FRAMEWORK_VERSION,
16
17
  INSTALLED_PROGRESS,
@@ -80,6 +81,17 @@ describe("formatTaskList", () => {
80
81
  expect(output).toContain("# Context Tree Init");
81
82
  expect(output).toContain("## Verification");
82
83
  });
84
+
85
+ it("documents the publish workflow for dedicated tree repos", () => {
86
+ const output = formatTaskList([], {
87
+ dedicatedTreeRepo: true,
88
+ sourceRepoName: "ADHD",
89
+ sourceRepoPath: "../ADHD",
90
+ });
91
+
92
+ expect(output).toContain("context-tree publish --open-pr");
93
+ expect(output).toContain("canonical local working copy");
94
+ });
83
95
  });
84
96
 
85
97
  // --- writeProgress ---
@@ -234,6 +246,13 @@ describe("runInit", () => {
234
246
  expect(existsSync(join(treeRepo, AGENT_INSTRUCTIONS_FILE))).toBe(true);
235
247
  expect(existsSync(join(treeRepo, "members", "NODE.md"))).toBe(true);
236
248
  expect(existsSync(join(treeRepo, INSTALLED_PROGRESS))).toBe(true);
249
+ expect(
250
+ JSON.parse(readFileSync(join(treeRepo, BOOTSTRAP_STATE), "utf-8")),
251
+ ).toEqual({
252
+ sourceRepoName: basename(sourceRepoDir.path),
253
+ sourceRepoPath: `../${basename(sourceRepoDir.path)}`,
254
+ treeRepoName: basename(treeRepo),
255
+ });
237
256
  expect(existsSync(join(sourceRepoDir.path, "NODE.md"))).toBe(false);
238
257
  expect(existsSync(join(sourceRepoDir.path, "members", "NODE.md"))).toBe(false);
239
258
  expect(existsSync(join(sourceRepoDir.path, INSTALLED_PROGRESS))).toBe(false);
@@ -0,0 +1,248 @@
1
+ import { join, relative } from "node:path";
2
+ import { writeFileSync } from "node:fs";
3
+ import { describe, expect, it } from "vitest";
4
+ import { Repo } from "#skill/engine/repo.js";
5
+ import {
6
+ PUBLISH_USAGE,
7
+ parsePublishArgs,
8
+ runPublish,
9
+ type CommandRunner,
10
+ } from "#skill/engine/publish.js";
11
+ import { writeBootstrapState } from "#skill/engine/runtime/bootstrap.js";
12
+ import {
13
+ AGENT_INSTRUCTIONS_FILE,
14
+ CLAUDE_INSTRUCTIONS_FILE,
15
+ SOURCE_INTEGRATION_MARKER,
16
+ } from "#skill/engine/runtime/asset-loader.js";
17
+ import {
18
+ makeAgentsMd,
19
+ makeFramework,
20
+ makeGitRepo,
21
+ makeMembers,
22
+ makeNode,
23
+ makeSourceRepo,
24
+ useTmpDir,
25
+ } from "./helpers.js";
26
+
27
+ interface RecordedCommand {
28
+ args: string[];
29
+ command: string;
30
+ cwd: string;
31
+ }
32
+
33
+ function makeTreeRepo(root: string): void {
34
+ makeGitRepo(root);
35
+ makeFramework(root, "0.2.0");
36
+ makeNode(root);
37
+ makeAgentsMd(root, { markers: true });
38
+ makeMembers(root);
39
+ }
40
+
41
+ function makeSourceIntegration(root: string): void {
42
+ writeFileSync(
43
+ join(root, AGENT_INSTRUCTIONS_FILE),
44
+ `${SOURCE_INTEGRATION_MARKER} installed\n`,
45
+ );
46
+ writeFileSync(
47
+ join(root, CLAUDE_INSTRUCTIONS_FILE),
48
+ `${SOURCE_INTEGRATION_MARKER} installed\n`,
49
+ );
50
+ }
51
+
52
+ function createRunner(
53
+ sourceRoot: string,
54
+ treeRoot: string,
55
+ ): { calls: RecordedCommand[]; runner: CommandRunner } {
56
+ const calls: RecordedCommand[] = [];
57
+ let treeOriginExists = false;
58
+ const runner: CommandRunner = (command, args, options) => {
59
+ calls.push({ command, args, cwd: options.cwd });
60
+
61
+ if (command === "git" && args[0] === "remote" && args[1] === "get-url") {
62
+ if (options.cwd === sourceRoot) {
63
+ return "git@github.com:acme/ADHD.git";
64
+ }
65
+ if (!treeOriginExists) {
66
+ throw new Error("missing origin");
67
+ }
68
+ return "git@github.com:acme/ADHD-context.git";
69
+ }
70
+
71
+ if (command === "gh" && args[0] === "repo" && args[1] === "view") {
72
+ if (args[2] === "acme/ADHD") {
73
+ return JSON.stringify({
74
+ defaultBranchRef: { name: "main" },
75
+ nameWithOwner: "acme/ADHD",
76
+ visibility: "PRIVATE",
77
+ });
78
+ }
79
+ throw new Error("not found");
80
+ }
81
+
82
+ if (
83
+ command === "git"
84
+ && args[0] === "rev-parse"
85
+ && args[1] === "--verify"
86
+ && args[2] === "HEAD"
87
+ && options.cwd === treeRoot
88
+ ) {
89
+ throw new Error("missing HEAD");
90
+ }
91
+
92
+ if (
93
+ command === "git"
94
+ && args[0] === "diff"
95
+ && args[1] === "--cached"
96
+ && args[2] === "--quiet"
97
+ ) {
98
+ throw new Error("changes present");
99
+ }
100
+
101
+ if (command === "git" && args[0] === "branch" && args[1] === "--show-current") {
102
+ return "main";
103
+ }
104
+
105
+ if (
106
+ command === "git"
107
+ && args[0] === "rev-parse"
108
+ && args[1] === "--verify"
109
+ && args[2] === "refs/remotes/origin/main"
110
+ ) {
111
+ return "origin/main";
112
+ }
113
+
114
+ if (
115
+ command === "git"
116
+ && args[0] === "rev-parse"
117
+ && args[1] === "--verify"
118
+ && args[2].startsWith("refs/heads/")
119
+ ) {
120
+ throw new Error("missing local branch");
121
+ }
122
+
123
+ if (command === "git" && args[0] === "ls-files") {
124
+ return "";
125
+ }
126
+
127
+ if (command === "gh" && args[0] === "pr" && args[1] === "create") {
128
+ return "https://github.com/acme/ADHD/pull/123";
129
+ }
130
+
131
+ if (command === "gh" && args[0] === "repo" && args[1] === "create") {
132
+ treeOriginExists = true;
133
+ return "";
134
+ }
135
+
136
+ if (command === "git" && args[0] === "remote" && args[1] === "add") {
137
+ treeOriginExists = true;
138
+ return "";
139
+ }
140
+
141
+ return "";
142
+ };
143
+
144
+ return { calls, runner };
145
+ }
146
+
147
+ describe("parsePublishArgs", () => {
148
+ it("documents the publish command", () => {
149
+ expect(PUBLISH_USAGE).toContain("context-tree publish");
150
+ expect(PUBLISH_USAGE).toContain("--open-pr");
151
+ expect(PUBLISH_USAGE).toContain("--source-repo PATH");
152
+ });
153
+
154
+ it("parses supported publish flags", () => {
155
+ expect(
156
+ parsePublishArgs([
157
+ "--open-pr",
158
+ "--tree-path",
159
+ "../ADHD-context",
160
+ "--source-repo",
161
+ "../ADHD",
162
+ "--submodule-path",
163
+ "ADHD-context",
164
+ "--source-remote",
165
+ "origin",
166
+ ]),
167
+ ).toEqual({
168
+ openPr: true,
169
+ sourceRemote: "origin",
170
+ sourceRepoPath: "../ADHD",
171
+ submodulePath: "ADHD-context",
172
+ treePath: "../ADHD-context",
173
+ });
174
+ });
175
+ });
176
+
177
+ describe("runPublish", () => {
178
+ it("publishes the tree repo, adds the submodule, and opens the source PR", () => {
179
+ const rootDir = useTmpDir();
180
+ const sourceRoot = join(rootDir.path, "ADHD");
181
+ const treeRoot = join(rootDir.path, "ADHD-context");
182
+
183
+ makeSourceRepo(sourceRoot);
184
+ makeFramework(sourceRoot, "0.2.0");
185
+ makeSourceIntegration(sourceRoot);
186
+ makeTreeRepo(treeRoot);
187
+ writeBootstrapState(treeRoot, {
188
+ sourceRepoName: "ADHD",
189
+ sourceRepoPath: relative(treeRoot, sourceRoot),
190
+ treeRepoName: "ADHD-context",
191
+ });
192
+
193
+ const { calls, runner } = createRunner(sourceRoot, treeRoot);
194
+ const result = runPublish(new Repo(treeRoot), {
195
+ commandRunner: runner,
196
+ openPr: true,
197
+ });
198
+
199
+ expect(result).toBe(0);
200
+ expect(
201
+ calls.some(
202
+ (call) =>
203
+ call.command === "gh"
204
+ && call.args[0] === "repo"
205
+ && call.args[1] === "create"
206
+ && call.args[2] === "acme/ADHD-context",
207
+ ),
208
+ ).toBe(true);
209
+ expect(
210
+ calls.some(
211
+ (call) =>
212
+ call.command === "git"
213
+ && call.args[0] === "submodule"
214
+ && call.args[1] === "add"
215
+ && call.args[2] === "git@github.com:acme/ADHD-context.git"
216
+ && call.args[3] === "ADHD-context",
217
+ ),
218
+ ).toBe(true);
219
+ expect(
220
+ calls.some(
221
+ (call) =>
222
+ call.command === "git"
223
+ && call.args[0] === "switch"
224
+ && call.args[1] === "-c"
225
+ && call.args[2] === "chore/connect-adhd-context",
226
+ ),
227
+ ).toBe(true);
228
+ expect(
229
+ calls.some(
230
+ (call) =>
231
+ call.command === "gh"
232
+ && call.args[0] === "pr"
233
+ && call.args[1] === "create"
234
+ && call.args.includes("--repo")
235
+ && call.args.includes("acme/ADHD"),
236
+ ),
237
+ ).toBe(true);
238
+ });
239
+
240
+ it("errors when the source repo cannot be inferred", () => {
241
+ const treeRoot = useTmpDir();
242
+ makeTreeRepo(treeRoot.path);
243
+
244
+ const result = runPublish(new Repo(treeRoot.path));
245
+
246
+ expect(result).toBe(1);
247
+ });
248
+ });
@@ -230,6 +230,8 @@ describe("skill artifacts", () => {
230
230
  expect(read("README.md")).toContain("dedicated tree repo");
231
231
  expect(read("README.md")).toContain("FIRST-TREE-SOURCE-INTEGRATION:");
232
232
  expect(read("README.md")).toContain("`first-tree` skill");
233
+ expect(read("README.md")).toContain("context-tree publish --open-pr");
234
+ expect(read("README.md")).toContain("canonical local working copy");
233
235
  expect(read("README.md")).toContain("Only use `--here` after you have already switched into the dedicated tree repo.");
234
236
  expect(read("AGENTS.md")).toContain("references/source-map.md");
235
237
  expect(read("AGENTS.md")).toContain("source-workspace-installation.md");
@@ -249,6 +251,7 @@ describe("skill artifacts", () => {
249
251
  expect(onboarding).toContain(".agents/skills/first-tree/");
250
252
  expect(onboarding).toContain(".claude/skills/first-tree/");
251
253
  expect(onboarding).toContain("FIRST-TREE-SOURCE-INTEGRATION:");
254
+ expect(onboarding).toContain("context-tree publish --open-pr");
252
255
  expect(onboarding).toContain("source/workspace repo");
253
256
  expect(onboarding).toContain("git submodule");
254
257
  expect(onboarding).toContain("Only use `--here` after you have already switched into the dedicated tree repo.");
@@ -266,6 +269,7 @@ describe("skill artifacts", () => {
266
269
  expect(skillMd).toContain(".claude/skills/first-tree/");
267
270
  expect(skillMd).toContain("source-workspace-installation.md");
268
271
  expect(skillMd).toContain("FIRST-TREE-SOURCE-INTEGRATION:");
272
+ expect(skillMd).toContain("context-tree publish --open-pr");
269
273
  expect(skillMd).toContain("Never run `context-tree init --here` in a source/workspace repo");
270
274
  expect(skillMd).not.toContain("canonical eval harness");
271
275
 
@@ -277,6 +281,8 @@ describe("skill artifacts", () => {
277
281
  expect(sourceMap).toContain("maintainer-thin-cli.md");
278
282
  expect(sourceMap).toContain("maintainer-build-and-distribution.md");
279
283
  expect(sourceMap).toContain("maintainer-testing.md");
284
+ expect(sourceMap).toContain("engine/publish.ts");
285
+ expect(sourceMap).toContain("tests/publish.test.ts");
280
286
  expect(sourceMap).toContain("engine/commands/");
281
287
  expect(sourceMap).toContain("engine/runtime/asset-loader.ts");
282
288
  expect(sourceMap).toContain("tests/init.test.ts");
@@ -291,6 +297,7 @@ describe("skill artifacts", () => {
291
297
  );
292
298
  expect(sourceWorkspaceInstall).toContain("FIRST-TREE-SOURCE-INTEGRATION:");
293
299
  expect(sourceWorkspaceInstall).toContain("git submodule");
300
+ expect(sourceWorkspaceInstall).toContain("context-tree publish --open-pr");
294
301
  expect(sourceWorkspaceInstall).toContain("Do not run `context-tree verify`");
295
302
  expect(sourceWorkspaceInstall).toContain("Do not run `context-tree init --here` in the source/workspace repo");
296
303
 
@@ -300,6 +307,11 @@ describe("skill artifacts", () => {
300
307
  expect(maintainerArchitecture).toContain("maintainer-only developer tooling");
301
308
  expect(maintainerArchitecture).toContain("`evals/`");
302
309
  expect(maintainerArchitecture).not.toContain("tests, and evals");
310
+
311
+ const buildAndDistribution = read(
312
+ "skills/first-tree/references/maintainer-build-and-distribution.md",
313
+ );
314
+ expect(buildAndDistribution).toContain("context-tree publish");
303
315
  });
304
316
 
305
317
  it("keeps public OSS entrypoints and package metadata in place", () => {
@@ -41,6 +41,7 @@ afterEach(() => {
41
41
  describe("thin CLI shell", () => {
42
42
  it("documents the dedicated-repo meaning of --here", () => {
43
43
  expect(USAGE).toContain("git init && context-tree init --here");
44
+ expect(USAGE).toContain("context-tree publish --open-pr");
44
45
  expect(USAGE).toContain("`--here` is for when the current repo is already the dedicated tree repo.");
45
46
  });
46
47
 
@@ -1,2 +0,0 @@
1
- import { t as runOnboarding } from "./onboarding-D3hnxIie.js";
2
- export { runOnboarding };
@@ -1,10 +0,0 @@
1
- //#region skills/first-tree/references/onboarding.md
2
- var onboarding_default = "# Context Tree Onboarding\n\nYou are setting up a **Context Tree** — the living source of truth for an organization. This document tells you what it is and how to bootstrap one.\n\n---\n\n## What Is a Context Tree\n\nA Context Tree is a Git repository where every directory is a **domain** and every file is a **node**. Each node captures decisions, designs, and cross-domain relationships — the knowledge that would otherwise scatter across PRs, documents, and people's heads.\n\nKey properties:\n\n- **Nodes are markdown files.** Each directory has a `NODE.md` that describes the domain. Leaf `.md` files capture specific decisions or designs.\n- **Every node has an owner.** Declared in YAML frontmatter. Owners approve changes to their nodes.\n- **Organized by concern, not by repo or team.** An agent working on \"add SSO\" finds all auth context in one place — not split across 4 repos.\n- **The tree is never a snapshot — it's the current state.** When decisions change, the tree updates. Stale nodes are bugs.\n\n### Frontmatter Format\n\nEvery node has frontmatter:\n\n```yaml\n---\ntitle: \"Auth Architecture\"\nowners: [alice, bob]\nsoft_links: [/infrastructure/deployments]\n---\n```\n\n- `owners` — who can approve changes. `owners: []` inherits from parent. `owners: [*]` means anyone.\n- `soft_links` — cross-references to related nodes in other domains.\n\n### What Belongs in the Tree\n\nInformation an agent needs to **decide** on an approach — not to execute it.\n\n**Yes:** \"Auth spans 4 repos: backend issues JWTs, frontend uses Better Auth, extension uses OAuth popup, desktop uses localhost callback.\"\n\n**No:** The function signature of `auth_service.verify()` — that's in the code.\n\n---\n\n## Four Principles\n\n1. **Source of truth for decisions, not execution.** The tree captures the *what* and *why*. Execution details stay in source systems.\n2. **Agents are first-class participants.** The tree is designed for agents to navigate and update.\n3. **Transparency by default.** Reading is open to all. Writing requires owner approval.\n4. **Git-native.** Nodes are files, domains are directories. History, ownership, and review follow Git conventions.\n\n---\n\n## How to Set Up a Context Tree\n\n### Prerequisites\n\n- A source/workspace Git repository, or an already-created dedicated tree repo\n- Node.js 18+\n- The npm package is `first-tree`, the installed CLI command is\n `context-tree`.\n- `context-tree init` installs the framework skill into\n `.agents/skills/first-tree/` and `.claude/skills/first-tree/`.\n- Use `npx first-tree init` for one-off runs, or `npm install -g first-tree`\n to add the `context-tree` command to your PATH\n\n### Step 1: Initialize\n\nRecommended workflow: run `context-tree init` from your source or workspace repo.\nThe CLI will install the bundled skill in the current repo, update root\n`AGENTS.md` and `CLAUDE.md` with a `FIRST-TREE-SOURCE-INTEGRATION:` line, and\ncreate a sibling dedicated tree repo named `<repo>-context` by default. Tree\nfiles are scaffolded only in the dedicated tree repo.\n\n```bash\ncd my-org\ncontext-tree init\ncd ../my-org-context\n```\n\nIf you already created a dedicated tree repo manually, initialize it in place:\n\n```bash\nmkdir my-org-context && cd my-org-context\ngit init\ncontext-tree init --here\n```\n\nOnly use `--here` after you have already switched into the dedicated tree repo.\nDo not use it inside the source/workspace repo unless you intentionally want\nthat repo itself to become the Context Tree.\n\nEither way, the framework installs into `.agents/skills/first-tree/` and\n`.claude/skills/first-tree/`, renders scaffolding (`NODE.md`, `AGENTS.md`,\n`members/NODE.md`), and generates a task list in\n`.agents/skills/first-tree/progress.md`.\n\nPublishing tip: keep the tree repo in the same GitHub organization as the\nsource repo unless you have a reason not to.\n\nHard boundary: do **not** create `NODE.md`, `members/`, or tree-scoped\n`AGENTS.md` in the source/workspace repo. Those files belong only in the\ndedicated `*-context` repo.\n\nDefault agent workflow after initialization:\n\n1. Create and push the dedicated `*-context` repo in the same GitHub\n organization as the source repo.\n2. Add the dedicated tree repo back to the source/workspace repo as a `git submodule`.\n3. Open a PR against the source/workspace repo's default branch for the local\n skill integration plus the new submodule pointer. Do not merge it\n automatically.\n\n### Step 2: Work Through the Task List\n\nRead `.agents/skills/first-tree/progress.md`. It contains a checklist tailored\nto the current state of the repo. Complete each task:\n\n- Fill in `NODE.md` with your organization name, owners, and domains\n- Add project-specific instructions to `AGENTS.md` below the framework markers\n- Create member nodes under `members/`\n- Optionally configure agent integration (for Claude Code, the installed hook\n assets live under `.claude/skills/first-tree/`)\n- Copy validation workflows from\n `.agents/skills/first-tree/assets/framework/workflows/` to\n `.github/workflows/`\n\nAs you complete each task, check it off in\n`.agents/skills/first-tree/progress.md` by changing `- [ ]` to `- [x]`.\n\n### Step 3: Verify\n\n```bash\ncontext-tree verify\n```\n\nOr, from your source/workspace repo:\n\n```bash\ncontext-tree verify --tree-path ../my-org-context\n```\n\nThis fails if any items in `.agents/skills/first-tree/progress.md` remain\nunchecked, and runs deterministic checks (valid frontmatter, node structure,\nmember nodes exist).\n\nDo not run `context-tree verify` in the source/workspace repo itself. That repo\nonly carries the installed skill plus the\n`FIRST-TREE-SOURCE-INTEGRATION:` line.\n\n### Step 4: Design Your Domains\n\nCreate top-level directories for your organization's primary concerns. Each needs a `NODE.md`:\n\n```\nmy-org-tree/\n NODE.md # root — lists all domains\n engineering/\n NODE.md # decisions about architecture, infra, tooling\n product/\n NODE.md # strategy, roadmap, user research\n marketing/\n NODE.md # positioning, campaigns\n members/\n NODE.md # team members and agents\n alice/\n NODE.md # individual member node\n```\n\n### Step 5: Populate from Existing Work\n\nFor each domain, extract knowledge from existing repos, docs, and systems:\n\n- Decisions and their rationale\n- Cross-domain relationships and dependencies\n- Constraints that aren't obvious from the code\n\nThe tree doesn't duplicate source code — it captures what connects things and why they were built that way.\n\n---\n\n## CLI Reference\n\n| Command | Description |\n|---------|-------------|\n| `context-tree init` | Install local source/workspace integration and create or refresh a dedicated tree repo. By default, running in a source/workspace repo creates a sibling `<repo>-context`; use `--here` only when you are already inside the dedicated tree repo. |\n| `context-tree verify` | Check the installed progress file for unchecked items + run deterministic validation. Use `--tree-path` when invoking from another working directory. |\n| `context-tree upgrade` | Refresh the installed framework skill from the currently running `first-tree` npm package and generate follow-up tasks. Use `--tree-path` when invoking from another working directory. |\n| `context-tree help onboarding` | Print this onboarding guide. |\n\n---\n\n## Upgrading the Framework\n\nWhen the framework updates:\n\n```bash\ncontext-tree upgrade\n```\n\n`context-tree upgrade` refreshes `.agents/skills/first-tree/` and\n`.claude/skills/first-tree/` from the skill bundled with the currently running\n`first-tree` npm package, preserves your tree content, and generates follow-up\ntasks in `.agents/skills/first-tree/progress.md`.\n\nIf you run `context-tree upgrade` in the source/workspace repo, it refreshes\nonly the local installed skill plus the `FIRST-TREE-SOURCE-INTEGRATION:` lines.\nRun `context-tree upgrade --tree-path ../my-org-context` to upgrade the\ndedicated tree repo itself.\n\nIf your repo still uses the older `skills/first-tree/` or `.context-tree/` layouts,\n`context-tree upgrade` will migrate it to the current installed layout first.\n\nTo pick up a newer framework release, first run a newer package version, for\nexample `npx first-tree@latest upgrade`, or update your global `first-tree`\ninstall before running `context-tree upgrade`.\n\n---\n\n## Further Reading\n\n- `.agents/skills/first-tree/references/principles.md` — Core principles with detailed examples\n- `.agents/skills/first-tree/references/source-workspace-installation.md` — Source/workspace install contract\n- `.agents/skills/first-tree/references/ownership-and-naming.md` — How nodes are named and owned\n- `AGENTS.md` in your tree — The before/during/after workflow for every task\n";
3
- //#endregion
4
- //#region skills/first-tree/engine/onboarding.ts
5
- function runOnboarding(output = console.log) {
6
- output(onboarding_default);
7
- return 0;
8
- }
9
- //#endregion
10
- export { onboarding_default as n, runOnboarding as t };