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
@@ -0,0 +1,504 @@
1
+ import { C as SKILL_ROOT, c as CLAUDE_INSTRUCTIONS_FILE, l as CLAUDE_SKILL_ROOT, n as Repo, r as AGENT_INSTRUCTIONS_FILE } from "./repo-BeVpMoHi.js";
2
+ import { t as readBootstrapState } from "./bootstrap-YRjfHJp7.js";
3
+ import { existsSync } from "node:fs";
4
+ import { execFileSync } from "node:child_process";
5
+ import { dirname, isAbsolute, join, normalize, resolve } from "node:path";
6
+ //#region skills/first-tree/engine/publish.ts
7
+ const PUBLISH_USAGE = `usage: context-tree publish [--open-pr] [--tree-path PATH] [--source-repo PATH] [--submodule-path PATH] [--source-remote NAME]
8
+
9
+ Run this from the dedicated tree repo after \`context-tree init\`. The command
10
+ creates or reuses the GitHub \`*-context\` repo, pushes the current tree
11
+ commit, adds that repo back to the source/workspace repo as a git submodule,
12
+ and prepares the source-repo branch.
13
+
14
+ Options:
15
+ --open-pr Open a PR in the source/workspace repo after pushing the branch
16
+ --tree-path PATH Publish a tree repo from another working directory
17
+ --source-repo PATH Explicit source/workspace repo path when it cannot be inferred
18
+ --submodule-path PATH Path to use inside the source/workspace repo (default: tree repo name)
19
+ --source-remote NAME Source/workspace repo remote to mirror on GitHub (default: origin)
20
+ --help Show this help message
21
+ `;
22
+ function defaultCommandRunner(command, args, options) {
23
+ try {
24
+ return execFileSync(command, args, {
25
+ cwd: options.cwd,
26
+ encoding: "utf-8",
27
+ env: {
28
+ ...process.env,
29
+ GIT_TERMINAL_PROMPT: "0"
30
+ },
31
+ stdio: [
32
+ "ignore",
33
+ "pipe",
34
+ "pipe"
35
+ ]
36
+ }).trim();
37
+ } catch (err) {
38
+ const message = err instanceof Error ? err.message : "unknown error";
39
+ throw new Error(`Command failed in ${options.cwd}: ${command} ${args.join(" ")}\n${message}`);
40
+ }
41
+ }
42
+ function commandSucceeds(runner, command, args, cwd) {
43
+ try {
44
+ runner(command, args, { cwd });
45
+ return true;
46
+ } catch {
47
+ return false;
48
+ }
49
+ }
50
+ function parseGitHubRemote(url) {
51
+ if (url.startsWith("https://") || url.startsWith("http://")) try {
52
+ const parsed = new URL(url);
53
+ if (parsed.hostname !== "github.com") return null;
54
+ const parts = parsed.pathname.replace(/^\/+/, "").replace(/\.git$/, "").split("/");
55
+ if (parts.length !== 2 || parts.some((part) => part.trim() === "")) return null;
56
+ return {
57
+ cloneStyle: "https",
58
+ owner: parts[0],
59
+ repo: parts[1],
60
+ slug: `${parts[0]}/${parts[1]}`
61
+ };
62
+ } catch {
63
+ return null;
64
+ }
65
+ if (url.startsWith("ssh://")) try {
66
+ const parsed = new URL(url);
67
+ if (parsed.hostname !== "github.com") return null;
68
+ const parts = parsed.pathname.replace(/^\/+/, "").replace(/\.git$/, "").split("/");
69
+ if (parts.length !== 2 || parts.some((part) => part.trim() === "")) return null;
70
+ return {
71
+ cloneStyle: "ssh",
72
+ owner: parts[0],
73
+ repo: parts[1],
74
+ slug: `${parts[0]}/${parts[1]}`
75
+ };
76
+ } catch {
77
+ return null;
78
+ }
79
+ const scpMatch = url.match(/^git@github\.com:(.+?)\/(.+?)(?:\.git)?$/);
80
+ if (scpMatch === null) return null;
81
+ return {
82
+ cloneStyle: "ssh",
83
+ owner: scpMatch[1],
84
+ repo: scpMatch[2],
85
+ slug: `${scpMatch[1]}/${scpMatch[2]}`
86
+ };
87
+ }
88
+ function visibilityFlag(visibility) {
89
+ switch (visibility) {
90
+ case "internal": return "--internal";
91
+ case "private": return "--private";
92
+ default: return "--public";
93
+ }
94
+ }
95
+ function buildGitHubCloneUrl(slug, cloneStyle) {
96
+ if (cloneStyle === "ssh") return `git@github.com:${slug}.git`;
97
+ return `https://github.com/${slug}.git`;
98
+ }
99
+ function readGitHubRepoMetadata(runner, slug, cwd) {
100
+ const raw = runner("gh", [
101
+ "repo",
102
+ "view",
103
+ slug,
104
+ "--json",
105
+ "defaultBranchRef,nameWithOwner,visibility"
106
+ ], { cwd });
107
+ const parsed = JSON.parse(raw);
108
+ const defaultBranch = parsed.defaultBranchRef?.name;
109
+ const nameWithOwner = parsed.nameWithOwner;
110
+ const visibility = parsed.visibility?.toLowerCase();
111
+ if (typeof defaultBranch !== "string" || typeof nameWithOwner !== "string" || visibility !== "internal" && visibility !== "private" && visibility !== "public") throw new Error(`Could not read GitHub metadata for ${slug}.`);
112
+ return {
113
+ defaultBranch,
114
+ nameWithOwner,
115
+ visibility
116
+ };
117
+ }
118
+ function readCurrentBranch(runner, root) {
119
+ return runner("git", ["branch", "--show-current"], { cwd: root }).trim();
120
+ }
121
+ function hasCommit(runner, root) {
122
+ return commandSucceeds(runner, "git", [
123
+ "rev-parse",
124
+ "--verify",
125
+ "HEAD"
126
+ ], root);
127
+ }
128
+ function hasIndexedChanges(runner, root, paths) {
129
+ const args = [
130
+ "diff",
131
+ "--cached",
132
+ "--quiet"
133
+ ];
134
+ if (paths && paths.length > 0) args.push("--", ...paths);
135
+ return !commandSucceeds(runner, "git", args, root);
136
+ }
137
+ function commitTreeState(runner, treeRepo) {
138
+ const hadCommit = hasCommit(runner, treeRepo.root);
139
+ runner("git", ["add", "-A"], { cwd: treeRepo.root });
140
+ if (!hasIndexedChanges(runner, treeRepo.root)) return false;
141
+ runner("git", [
142
+ "commit",
143
+ "-m",
144
+ hadCommit ? "chore: update context tree" : "chore: bootstrap context tree"
145
+ ], { cwd: treeRepo.root });
146
+ return true;
147
+ }
148
+ function resolveSourceRepoRoot(treeRepo, options) {
149
+ const cwd = options?.currentCwd ?? process.cwd();
150
+ if (options?.sourceRepoPath) return resolve(cwd, options.sourceRepoPath);
151
+ const bootstrap = readBootstrapState(treeRepo.root);
152
+ if (bootstrap !== null) return resolve(treeRepo.root, bootstrap.sourceRepoPath);
153
+ if (treeRepo.repoName().endsWith("-context")) return join(dirname(treeRepo.root), treeRepo.repoName().slice(0, -8));
154
+ return null;
155
+ }
156
+ function normalizeSubmodulePath(input) {
157
+ if (input.trim() === "") return null;
158
+ if (isAbsolute(input)) return null;
159
+ const normalized = normalize(input).replaceAll("\\", "/");
160
+ if (normalized === "." || normalized === "" || normalized.startsWith("../") || normalized.includes("/../")) return null;
161
+ return normalized;
162
+ }
163
+ function getGitRemoteUrl(runner, root, remote) {
164
+ try {
165
+ return runner("git", [
166
+ "remote",
167
+ "get-url",
168
+ remote
169
+ ], { cwd: root }).trim();
170
+ } catch {
171
+ return null;
172
+ }
173
+ }
174
+ function localBranchExists(runner, root, branch) {
175
+ return commandSucceeds(runner, "git", [
176
+ "rev-parse",
177
+ "--verify",
178
+ `refs/heads/${branch}`
179
+ ], root);
180
+ }
181
+ function remoteTrackingBranchExists(runner, root, remote, branch) {
182
+ return commandSucceeds(runner, "git", [
183
+ "rev-parse",
184
+ "--verify",
185
+ `refs/remotes/${remote}/${branch}`
186
+ ], root);
187
+ }
188
+ function buildPublishBranchName(treeRepoName) {
189
+ return `chore/connect-${treeRepoName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "")}`;
190
+ }
191
+ function ensureSourceBranch(runner, sourceRepo, sourceRemote, defaultBranch, treeRepoName) {
192
+ const branch = buildPublishBranchName(treeRepoName);
193
+ if (readCurrentBranch(runner, sourceRepo.root) === branch) return branch;
194
+ if (localBranchExists(runner, sourceRepo.root, branch)) {
195
+ runner("git", ["switch", branch], { cwd: sourceRepo.root });
196
+ return branch;
197
+ }
198
+ if (!remoteTrackingBranchExists(runner, sourceRepo.root, sourceRemote, defaultBranch)) runner("git", [
199
+ "fetch",
200
+ sourceRemote,
201
+ defaultBranch
202
+ ], { cwd: sourceRepo.root });
203
+ if (remoteTrackingBranchExists(runner, sourceRepo.root, sourceRemote, defaultBranch)) {
204
+ runner("git", [
205
+ "switch",
206
+ "-c",
207
+ branch,
208
+ "--track",
209
+ `${sourceRemote}/${defaultBranch}`
210
+ ], { cwd: sourceRepo.root });
211
+ return branch;
212
+ }
213
+ runner("git", [
214
+ "switch",
215
+ "-c",
216
+ branch
217
+ ], { cwd: sourceRepo.root });
218
+ return branch;
219
+ }
220
+ function isTrackedSubmodule(runner, sourceRoot, submodulePath) {
221
+ try {
222
+ return runner("git", [
223
+ "ls-files",
224
+ "--stage",
225
+ "--",
226
+ submodulePath
227
+ ], { cwd: sourceRoot }).split(/\r?\n/).some((line) => line.startsWith("160000 "));
228
+ } catch {
229
+ return false;
230
+ }
231
+ }
232
+ function ensureSubmodule(runner, sourceRepo, submodulePath, remoteUrl) {
233
+ if (isTrackedSubmodule(runner, sourceRepo.root, submodulePath)) {
234
+ runner("git", [
235
+ "submodule",
236
+ "set-url",
237
+ "--",
238
+ submodulePath,
239
+ remoteUrl
240
+ ], { cwd: sourceRepo.root });
241
+ runner("git", [
242
+ "submodule",
243
+ "sync",
244
+ "--",
245
+ submodulePath
246
+ ], { cwd: sourceRepo.root });
247
+ runner("git", [
248
+ "submodule",
249
+ "update",
250
+ "--init",
251
+ "--",
252
+ submodulePath
253
+ ], { cwd: sourceRepo.root });
254
+ return "updated";
255
+ }
256
+ if (existsSync(join(sourceRepo.root, submodulePath))) throw new Error(`Cannot add the submodule at ${submodulePath} because that path already exists in the source/workspace repo.`);
257
+ runner("git", [
258
+ "submodule",
259
+ "add",
260
+ remoteUrl,
261
+ submodulePath
262
+ ], { cwd: sourceRepo.root });
263
+ return "added";
264
+ }
265
+ function commitSourceIntegration(runner, sourceRepo, submodulePath, treeRepoName) {
266
+ const managedPaths = [
267
+ ...[
268
+ SKILL_ROOT,
269
+ CLAUDE_SKILL_ROOT,
270
+ AGENT_INSTRUCTIONS_FILE,
271
+ CLAUDE_INSTRUCTIONS_FILE
272
+ ].filter((path) => existsSync(join(sourceRepo.root, path))),
273
+ ".gitmodules",
274
+ submodulePath
275
+ ].filter((path, index, items) => items.indexOf(path) === index);
276
+ runner("git", [
277
+ "add",
278
+ "--",
279
+ ...managedPaths
280
+ ], { cwd: sourceRepo.root });
281
+ if (!hasIndexedChanges(runner, sourceRepo.root, managedPaths)) return false;
282
+ runner("git", [
283
+ "commit",
284
+ "-m",
285
+ `chore: connect ${treeRepoName} context tree`,
286
+ "--",
287
+ ...managedPaths
288
+ ], { cwd: sourceRepo.root });
289
+ return true;
290
+ }
291
+ function ensureTreeRemotePublished(runner, treeRepo, treeSlug, sourceCloneStyle, visibility) {
292
+ const existingOrigin = getGitRemoteUrl(runner, treeRepo.root, "origin");
293
+ if (existingOrigin !== null) {
294
+ runner("git", [
295
+ "push",
296
+ "-u",
297
+ "origin",
298
+ "HEAD"
299
+ ], { cwd: treeRepo.root });
300
+ return {
301
+ createdRemote: false,
302
+ remoteUrl: existingOrigin
303
+ };
304
+ }
305
+ const desiredCloneUrl = buildGitHubCloneUrl(treeSlug, sourceCloneStyle);
306
+ if (commandSucceeds(runner, "gh", [
307
+ "repo",
308
+ "view",
309
+ treeSlug,
310
+ "--json",
311
+ "nameWithOwner"
312
+ ], treeRepo.root)) {
313
+ runner("git", [
314
+ "remote",
315
+ "add",
316
+ "origin",
317
+ desiredCloneUrl
318
+ ], { cwd: treeRepo.root });
319
+ runner("git", [
320
+ "push",
321
+ "-u",
322
+ "origin",
323
+ "HEAD"
324
+ ], { cwd: treeRepo.root });
325
+ return {
326
+ createdRemote: false,
327
+ remoteUrl: desiredCloneUrl
328
+ };
329
+ }
330
+ runner("gh", [
331
+ "repo",
332
+ "create",
333
+ treeSlug,
334
+ visibilityFlag(visibility),
335
+ "--source",
336
+ treeRepo.root,
337
+ "--remote",
338
+ "origin",
339
+ "--push"
340
+ ], { cwd: treeRepo.root });
341
+ return {
342
+ createdRemote: true,
343
+ remoteUrl: getGitRemoteUrl(runner, treeRepo.root, "origin") ?? desiredCloneUrl
344
+ };
345
+ }
346
+ function buildPrBody(treeRepoName, treeSlug, submodulePath) {
347
+ return [
348
+ `Connect the published \`${treeRepoName}\` Context Tree back into this source/workspace repo.`,
349
+ "",
350
+ `- add \`${submodulePath}\` as the tracked Context Tree submodule`,
351
+ "- keep the local first-tree skill + source integration marker lines in this repo",
352
+ `- use \`${treeSlug}\` as the GitHub home for tree content`
353
+ ].join("\n");
354
+ }
355
+ function parsePublishArgs(args) {
356
+ const parsed = {};
357
+ for (let index = 0; index < args.length; index += 1) {
358
+ const arg = args[index];
359
+ switch (arg) {
360
+ case "--open-pr":
361
+ parsed.openPr = true;
362
+ break;
363
+ case "--tree-path": {
364
+ const value = args[index + 1];
365
+ if (!value) return { error: "Missing value for --tree-path" };
366
+ parsed.treePath = value;
367
+ index += 1;
368
+ break;
369
+ }
370
+ case "--source-repo": {
371
+ const value = args[index + 1];
372
+ if (!value) return { error: "Missing value for --source-repo" };
373
+ parsed.sourceRepoPath = value;
374
+ index += 1;
375
+ break;
376
+ }
377
+ case "--submodule-path": {
378
+ const value = args[index + 1];
379
+ if (!value) return { error: "Missing value for --submodule-path" };
380
+ parsed.submodulePath = value;
381
+ index += 1;
382
+ break;
383
+ }
384
+ case "--source-remote": {
385
+ const value = args[index + 1];
386
+ if (!value) return { error: "Missing value for --source-remote" };
387
+ parsed.sourceRemote = value;
388
+ index += 1;
389
+ break;
390
+ }
391
+ default: return { error: `Unknown publish option: ${arg}` };
392
+ }
393
+ }
394
+ return parsed;
395
+ }
396
+ function runPublish(repo, options) {
397
+ const cwd = options?.currentCwd ?? process.cwd();
398
+ const runner = options?.commandRunner ?? defaultCommandRunner;
399
+ const treeRepo = repo ?? new Repo(options?.treePath ? resolve(cwd, options.treePath) : void 0);
400
+ if (treeRepo.hasSourceWorkspaceIntegration() && !treeRepo.looksLikeTreeRepo()) {
401
+ console.error(`Error: this repo only has the first-tree source/workspace integration installed. Run \`context-tree publish --tree-path ../${treeRepo.repoName()}-context\` or switch into the dedicated tree repo first.`);
402
+ return 1;
403
+ }
404
+ if (!treeRepo.hasFramework() || !treeRepo.looksLikeTreeRepo()) {
405
+ console.error("Error: `context-tree publish` must run from a dedicated tree repo (or use `--tree-path` to point at one). Run `context-tree init` first.");
406
+ return 1;
407
+ }
408
+ const sourceRepoRoot = resolveSourceRepoRoot(treeRepo, options);
409
+ if (sourceRepoRoot === null) {
410
+ console.error("Error: could not determine the source/workspace repo for this tree. Re-run `context-tree init` from the source repo first, or pass `--source-repo PATH`.");
411
+ return 1;
412
+ }
413
+ const sourceRepo = new Repo(sourceRepoRoot);
414
+ if (!sourceRepo.isGitRepo()) {
415
+ console.error(`Error: the resolved source/workspace repo is not a git repository: ${sourceRepoRoot}`);
416
+ return 1;
417
+ }
418
+ if (sourceRepo.root === treeRepo.root) {
419
+ console.error("Error: the source/workspace repo and dedicated tree repo resolved to the same path. `context-tree publish` expects two separate repos.");
420
+ return 1;
421
+ }
422
+ if (!sourceRepo.hasCurrentInstalledSkill() || !sourceRepo.hasSourceWorkspaceIntegration()) {
423
+ console.error("Error: the source/workspace repo does not have the first-tree source integration installed. Run `context-tree init` from the source/workspace repo first.");
424
+ return 1;
425
+ }
426
+ const submodulePath = normalizeSubmodulePath(options?.submodulePath ?? treeRepo.repoName());
427
+ if (submodulePath === null) {
428
+ console.error("Error: `--submodule-path` must be a relative path inside the source/workspace repo.");
429
+ return 1;
430
+ }
431
+ const sourceRemoteName = options?.sourceRemote ?? "origin";
432
+ try {
433
+ console.log("Context Tree Publish\n");
434
+ console.log(` Tree repo: ${treeRepo.root}`);
435
+ console.log(` Source repo: ${sourceRepo.root}\n`);
436
+ const sourceRemoteUrl = getGitRemoteUrl(runner, sourceRepo.root, sourceRemoteName);
437
+ if (sourceRemoteUrl === null) throw new Error(`Could not read git remote \`${sourceRemoteName}\` from the source/workspace repo.`);
438
+ const sourceGitHub = parseGitHubRemote(sourceRemoteUrl);
439
+ if (sourceGitHub === null) throw new Error(`The source/workspace remote \`${sourceRemoteName}\` is not a GitHub remote: ${sourceRemoteUrl}`);
440
+ const sourceMetadata = readGitHubRepoMetadata(runner, sourceGitHub.slug, sourceRepo.root);
441
+ const treeSlug = `${sourceGitHub.owner}/${treeRepo.repoName()}`;
442
+ if (commitTreeState(runner, treeRepo)) console.log(" Committed the current tree state.");
443
+ else console.log(" Tree repo already had a committed working state.");
444
+ const treeRemote = ensureTreeRemotePublished(runner, treeRepo, treeSlug, sourceGitHub.cloneStyle, sourceMetadata.visibility);
445
+ if (treeRemote.createdRemote) console.log(` Created and pushed ${treeSlug}.`);
446
+ else console.log(` Pushed the tree repo to ${treeRemote.remoteUrl}.`);
447
+ const sourceBranch = ensureSourceBranch(runner, sourceRepo, sourceRemoteName, sourceMetadata.defaultBranch, treeRepo.repoName());
448
+ console.log(` Working on source/workspace branch \`${sourceBranch}\`.`);
449
+ const submoduleAction = ensureSubmodule(runner, sourceRepo, submodulePath, treeRemote.remoteUrl);
450
+ console.log(submoduleAction === "added" ? ` Added \`${submodulePath}\` as a git submodule.` : ` Updated the \`${submodulePath}\` submodule URL and checkout.`);
451
+ const committedSourceChanges = commitSourceIntegration(runner, sourceRepo, submodulePath, treeRepo.repoName());
452
+ if (committedSourceChanges) console.log(" Committed the source/workspace integration branch.");
453
+ else console.log(" Source/workspace integration was already up to date; no new commit was needed.");
454
+ if (committedSourceChanges || options?.openPr) {
455
+ runner("git", [
456
+ "push",
457
+ "-u",
458
+ sourceRemoteName,
459
+ sourceBranch
460
+ ], { cwd: sourceRepo.root });
461
+ console.log(` Pushed \`${sourceBranch}\` to \`${sourceRemoteName}\`.`);
462
+ }
463
+ if (options?.openPr) {
464
+ const prUrl = runner("gh", [
465
+ "pr",
466
+ "create",
467
+ "--repo",
468
+ sourceMetadata.nameWithOwner,
469
+ "--base",
470
+ sourceMetadata.defaultBranch,
471
+ "--head",
472
+ sourceBranch,
473
+ "--title",
474
+ `chore: connect ${treeRepo.repoName()} context tree`,
475
+ "--body",
476
+ buildPrBody(treeRepo.repoName(), treeSlug, submodulePath)
477
+ ], { cwd: sourceRepo.root });
478
+ console.log(` Opened PR: ${prUrl}`);
479
+ }
480
+ console.log();
481
+ console.log(`The source/workspace repo's \`${submodulePath}\` checkout is now the canonical local working copy for this tree.`);
482
+ console.log(`You can delete the temporary bootstrap checkout at ${treeRepo.root} once you no longer need it.`);
483
+ return 0;
484
+ } catch (err) {
485
+ const message = err instanceof Error ? err.message : "unknown error";
486
+ console.error(`Error: ${message}`);
487
+ return 1;
488
+ }
489
+ }
490
+ function runPublishCli(args = []) {
491
+ if (args.includes("--help") || args.includes("-h")) {
492
+ console.log(PUBLISH_USAGE);
493
+ return 0;
494
+ }
495
+ const parsed = parsePublishArgs(args);
496
+ if ("error" in parsed) {
497
+ console.error(parsed.error);
498
+ console.log(PUBLISH_USAGE);
499
+ return 1;
500
+ }
501
+ return runPublish(void 0, parsed);
502
+ }
503
+ //#endregion
504
+ export { runPublishCli as runPublish };
@@ -17,6 +17,7 @@ join(FRAMEWORK_ASSET_ROOT, "prompts");
17
17
  const FRAMEWORK_EXAMPLES_DIR = join(FRAMEWORK_ASSET_ROOT, "examples");
18
18
  join(FRAMEWORK_ASSET_ROOT, "helpers");
19
19
  const INSTALLED_PROGRESS = join(SKILL_ROOT, "progress.md");
20
+ const BOOTSTRAP_STATE = join(SKILL_ROOT, "bootstrap.json");
20
21
  const AGENT_INSTRUCTIONS_FILE = "AGENTS.md";
21
22
  const LEGACY_AGENT_INSTRUCTIONS_FILE = "AGENT.md";
22
23
  const AGENT_INSTRUCTIONS_TEMPLATE = "agents.md.template";
@@ -372,4 +373,4 @@ function isDirectory(root, relPath) {
372
373
  }
373
374
  }
374
375
  //#endregion
375
- export { SOURCE_INTEGRATION_FILES as C, SKILL_ROOT as S, installedSkillRootsDisplay as T, LEGACY_EXAMPLES_DIR as _, BUNDLED_SKILL_ROOT as a, LEGACY_REPO_SKILL_ROOT as b, CLAUDE_SKILL_ROOT as c, FRAMEWORK_TEMPLATES_DIR as d, FRAMEWORK_VERSION as f, LEGACY_AGENT_INSTRUCTIONS_FILE as g, INSTALLED_SKILL_ROOTS as h, AGENT_INSTRUCTIONS_TEMPLATE as i, FRAMEWORK_ASSET_ROOT as l, INSTALLED_PROGRESS as m, Repo as n, CLAUDE_FRAMEWORK_EXAMPLES_DIR as o, FRAMEWORK_WORKFLOWS_DIR as p, AGENT_INSTRUCTIONS_FILE as r, CLAUDE_INSTRUCTIONS_FILE as s, FRAMEWORK_END_MARKER as t, FRAMEWORK_EXAMPLES_DIR as u, LEGACY_FRAMEWORK_ROOT as v, SOURCE_INTEGRATION_MARKER as w, SKILL_NAME as x, LEGACY_REPO_SKILL_EXAMPLES_DIR as y };
376
+ export { SKILL_ROOT as C, installedSkillRootsDisplay as E, SKILL_NAME as S, SOURCE_INTEGRATION_MARKER as T, LEGACY_AGENT_INSTRUCTIONS_FILE as _, BOOTSTRAP_STATE as a, LEGACY_REPO_SKILL_EXAMPLES_DIR as b, CLAUDE_INSTRUCTIONS_FILE as c, FRAMEWORK_EXAMPLES_DIR as d, FRAMEWORK_TEMPLATES_DIR as f, INSTALLED_SKILL_ROOTS as g, INSTALLED_PROGRESS as h, AGENT_INSTRUCTIONS_TEMPLATE as i, CLAUDE_SKILL_ROOT as l, FRAMEWORK_WORKFLOWS_DIR as m, Repo as n, BUNDLED_SKILL_ROOT as o, FRAMEWORK_VERSION as p, AGENT_INSTRUCTIONS_FILE as r, CLAUDE_FRAMEWORK_EXAMPLES_DIR as s, FRAMEWORK_END_MARKER as t, FRAMEWORK_ASSET_ROOT as u, LEGACY_EXAMPLES_DIR as v, SOURCE_INTEGRATION_FILES as w, LEGACY_REPO_SKILL_ROOT as x, LEGACY_FRAMEWORK_ROOT as y };
@@ -1,4 +1,4 @@
1
- import { C as SOURCE_INTEGRATION_FILES, a as BUNDLED_SKILL_ROOT, b as LEGACY_REPO_SKILL_ROOT, h as INSTALLED_SKILL_ROOTS, w as SOURCE_INTEGRATION_MARKER } from "./repo-0z7N9r17.js";
1
+ import { T as SOURCE_INTEGRATION_MARKER, g as INSTALLED_SKILL_ROOTS, o as BUNDLED_SKILL_ROOT, w as SOURCE_INTEGRATION_FILES, x as LEGACY_REPO_SKILL_ROOT } from "./repo-BeVpMoHi.js";
2
2
  import { copyFileSync, cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { dirname, join } from "node:path";
@@ -1,5 +1,5 @@
1
- import { S as SKILL_ROOT, T as installedSkillRootsDisplay, b as LEGACY_REPO_SKILL_ROOT, c as CLAUDE_SKILL_ROOT, d as FRAMEWORK_TEMPLATES_DIR, f as FRAMEWORK_VERSION, g as LEGACY_AGENT_INSTRUCTIONS_FILE, i as AGENT_INSTRUCTIONS_TEMPLATE, m as INSTALLED_PROGRESS, n as Repo, p as FRAMEWORK_WORKFLOWS_DIR, r as AGENT_INSTRUCTIONS_FILE, s as CLAUDE_INSTRUCTIONS_FILE, v as LEGACY_FRAMEWORK_ROOT, w as SOURCE_INTEGRATION_MARKER } from "./repo-0z7N9r17.js";
2
- import { a as resolveCanonicalSkillRoot, i as resolveBundledPackageRoot, n as copyCanonicalSkill, t as upsertSourceIntegrationFiles } from "./source-integration-C2iiN4k_.js";
1
+ import { C as SKILL_ROOT, E as installedSkillRootsDisplay, T as SOURCE_INTEGRATION_MARKER, _ as LEGACY_AGENT_INSTRUCTIONS_FILE, c as CLAUDE_INSTRUCTIONS_FILE, f as FRAMEWORK_TEMPLATES_DIR, h as INSTALLED_PROGRESS, i as AGENT_INSTRUCTIONS_TEMPLATE, l as CLAUDE_SKILL_ROOT, m as FRAMEWORK_WORKFLOWS_DIR, n as Repo, p as FRAMEWORK_VERSION, r as AGENT_INSTRUCTIONS_FILE, x as LEGACY_REPO_SKILL_ROOT, y as LEGACY_FRAMEWORK_ROOT } from "./repo-BeVpMoHi.js";
2
+ import { a as resolveCanonicalSkillRoot, i as resolveBundledPackageRoot, n as copyCanonicalSkill, t as upsertSourceIntegrationFiles } from "./source-integration-DMxnl8Dw.js";
3
3
  import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
4
4
  import { dirname, join, resolve } from "node:path";
5
5
  //#region skills/first-tree/engine/runtime/upgrader.ts
@@ -1,4 +1,4 @@
1
- import { S as SKILL_ROOT, g as LEGACY_AGENT_INSTRUCTIONS_FILE, n as Repo, r as AGENT_INSTRUCTIONS_FILE, x as SKILL_NAME } from "./repo-0z7N9r17.js";
1
+ import { C as SKILL_ROOT, S as SKILL_NAME, _ as LEGACY_AGENT_INSTRUCTIONS_FILE, n as Repo, r as AGENT_INSTRUCTIONS_FILE } from "./repo-BeVpMoHi.js";
2
2
  import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
3
3
  import { join, relative, resolve } from "node:path";
4
4
  //#region skills/first-tree/engine/validators/members.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "first-tree",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
4
4
  "description": "CLI tools for Context Tree — the living source of truth for your organization.",
5
5
  "homepage": "https://github.com/agent-team-foundation/first-tree#readme",
6
6
  "bugs": {
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: first-tree
3
- description: Maintain the canonical `first-tree` skill and the thin `context-tree` CLI distributed by the `first-tree` npm package. Use when modifying `context-tree` commands (`init`, `verify`, `upgrade`, `help onboarding`), the installed skill payload under `assets/framework/`, maintainer references, or the build, packaging, test, and CI wiring that supports the framework.
3
+ description: Maintain the canonical `first-tree` skill and the thin `context-tree` CLI distributed by the `first-tree` npm package. Use when modifying `context-tree` commands (`init`, `publish`, `verify`, `upgrade`, `help onboarding`), the installed skill payload under `assets/framework/`, maintainer references, or the build, packaging, test, and CI wiring that supports the framework.
4
4
  ---
5
5
 
6
6
  # First Tree
@@ -72,16 +72,25 @@ repos.
72
72
  into the source/workspace repo and scaffolds tree files only in the
73
73
  dedicated tree repo. Use `--here` to initialize the current repo in place
74
74
  when you are already inside the tree repo.
75
+ - `context-tree publish --open-pr` is the default second-stage command after
76
+ `init` for source/workspace installs. Run it from the dedicated tree repo
77
+ once the initial tree version is ready to push.
75
78
  - Never run `context-tree init --here` in a source/workspace repo unless the
76
79
  user explicitly wants that repo itself to become the dedicated Context Tree.
77
80
  `--here` is for when you have already switched into the `*-context` repo.
78
81
  - `context-tree init` installs this skill into the target tree repo and
79
82
  scaffolds `.agents/skills/first-tree/`, `.claude/skills/first-tree/`,
80
83
  `NODE.md`, `AGENTS.md`, and `members/NODE.md`.
81
- - The default source/workspace workflow is: create or reuse `<repo>-context`,
82
- prefer pushing it in the same GitHub organization as the source repo, add it
83
- back to the source/workspace repo as a git submodule, and open a PR to the
84
- source/workspace repo's default branch instead of merging automatically.
84
+ - The default source/workspace workflow is: run `context-tree init` from the
85
+ source repo, draft the first tree version in `<repo>-context`, then run
86
+ `context-tree publish --open-pr` from that dedicated tree repo.
87
+ - After `context-tree publish` succeeds, treat the source/workspace repo's
88
+ submodule checkout as the canonical local working copy for the tree. The
89
+ temporary sibling bootstrap checkout can be deleted when you no longer need
90
+ it.
91
+ - If the dedicated tree repo was initialized manually with `context-tree init --here`
92
+ and does not have bootstrap metadata yet, pass `--source-repo PATH` to
93
+ `context-tree publish`.
85
94
  - If permissions, auth, or local filesystem constraints block the dedicated
86
95
  repo workflow, stop and report the blocker. Do not fall back to in-place tree
87
96
  bootstrap in the source/workspace repo.
@@ -1 +1 @@
1
- 0.2.1
1
+ 0.2.2
@@ -0,0 +1,5 @@
1
+ export {
2
+ PUBLISH_USAGE,
3
+ parsePublishArgs,
4
+ runPublishCli as runPublish,
5
+ } from "#skill/engine/publish.js";
@@ -16,6 +16,7 @@ import {
16
16
  renderTemplateFile,
17
17
  resolveBundledPackageRoot,
18
18
  } from "#skill/engine/runtime/installer.js";
19
+ import { writeBootstrapState } from "#skill/engine/runtime/bootstrap.js";
19
20
  import {
20
21
  AGENT_INSTRUCTIONS_FILE,
21
22
  AGENT_INSTRUCTIONS_TEMPLATE,
@@ -122,13 +123,10 @@ export function formatTaskList(
122
123
  );
123
124
  lines.push("## Source Workspace Workflow");
124
125
  lines.push(
125
- `- [ ] Publish this dedicated tree repo to the same GitHub organization as \`${context.sourceRepoName}\``,
126
+ `- [ ] When this initial tree version is ready, run \`context-tree publish --open-pr\` from this dedicated tree repo. It will create or reuse the GitHub \`*-context\` repo, add it back to \`${context.sourceRepoName}\` as a git submodule, and open the source/workspace PR.`,
126
127
  );
127
128
  lines.push(
128
- `- [ ] Add this tree repo back to \`${context.sourceRepoName}\` as a git submodule after the remote exists`,
129
- );
130
- lines.push(
131
- `- [ ] Open a PR against the source/workspace repo's default branch with only the installed skill, the \`${SOURCE_INTEGRATION_MARKER}\` marker lines in \`${AGENT_INSTRUCTIONS_FILE}\` / \`${CLAUDE_INSTRUCTIONS_FILE}\`, and the new submodule pointer`,
129
+ `- [ ] After publish succeeds, treat the source/workspace repo's \`${context.sourceRepoName}\` submodule checkout as the canonical local working copy for this tree. The temporary sibling bootstrap repo can be deleted when you no longer need it.`,
132
130
  );
133
131
  lines.push("");
134
132
  }
@@ -299,6 +297,14 @@ export function runInit(repo?: Repo, options?: InitOptions): number {
299
297
  }
300
298
  }
301
299
 
300
+ if (initTarget.dedicatedTreeRepo) {
301
+ writeBootstrapState(r.root, {
302
+ sourceRepoName: sourceRepo.repoName(),
303
+ sourceRepoPath: relativePathFrom(r.root, sourceRepo.root),
304
+ treeRepoName: r.repoName(),
305
+ });
306
+ }
307
+
302
308
  console.log(ONBOARDING_TEXT);
303
309
  console.log("---\n");
304
310