first-tree 0.0.6 → 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 (43) hide show
  1. package/README.md +16 -5
  2. package/dist/bootstrap-YRjfHJp7.js +28 -0
  3. package/dist/cli.js +14 -5
  4. package/dist/{help-DV9-AaFp.js → help-CDfaFrzl.js} +1 -1
  5. package/dist/{init-BgGH2_yC.js → init-DjSVkUeR.js} +19 -8
  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-Cc5U4DWT.js → repo-BeVpMoHi.js} +2 -15
  10. package/dist/{source-integration-CuKjoheT.js → source-integration-DMxnl8Dw.js} +2 -6
  11. package/dist/{upgrade-BvA9oKmi.js → upgrade-B_NTlNrx.js} +2 -4
  12. package/dist/{verify-G8gNXzDX.js → verify-Chhm1vOF.js} +3 -3
  13. package/package.json +1 -1
  14. package/skills/first-tree/SKILL.md +25 -6
  15. package/skills/first-tree/agents/openai.yaml +1 -1
  16. package/skills/first-tree/assets/framework/VERSION +1 -1
  17. package/skills/first-tree/engine/commands/publish.ts +5 -0
  18. package/skills/first-tree/engine/init.ts +24 -6
  19. package/skills/first-tree/engine/publish.ts +807 -0
  20. package/skills/first-tree/engine/repo.ts +0 -8
  21. package/skills/first-tree/engine/runtime/adapters.ts +0 -2
  22. package/skills/first-tree/engine/runtime/asset-loader.ts +1 -36
  23. package/skills/first-tree/engine/runtime/bootstrap.ts +40 -0
  24. package/skills/first-tree/engine/runtime/installer.ts +0 -2
  25. package/skills/first-tree/engine/upgrade.ts +0 -11
  26. package/skills/first-tree/engine/validators/nodes.ts +2 -11
  27. package/skills/first-tree/references/maintainer-build-and-distribution.md +3 -0
  28. package/skills/first-tree/references/maintainer-thin-cli.md +1 -1
  29. package/skills/first-tree/references/onboarding.md +18 -12
  30. package/skills/first-tree/references/source-map.md +3 -1
  31. package/skills/first-tree/references/source-workspace-installation.md +25 -13
  32. package/skills/first-tree/references/upgrade-contract.md +15 -8
  33. package/skills/first-tree/scripts/check-skill-sync.sh +0 -1
  34. package/skills/first-tree/tests/asset-loader.test.ts +0 -24
  35. package/skills/first-tree/tests/helpers.ts +0 -14
  36. package/skills/first-tree/tests/init.test.ts +25 -0
  37. package/skills/first-tree/tests/publish.test.ts +248 -0
  38. package/skills/first-tree/tests/repo.test.ts +0 -25
  39. package/skills/first-tree/tests/skill-artifacts.test.ts +16 -1
  40. package/skills/first-tree/tests/thin-cli.test.ts +6 -0
  41. package/skills/first-tree/tests/upgrade.test.ts +0 -21
  42. package/dist/onboarding-D7fGGOMN.js +0 -10
  43. package/dist/onboarding-lASHHmgO.js +0 -2
@@ -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";
@@ -46,15 +47,6 @@ join(LEGACY_REPO_SKILL_ASSET_ROOT, "prompts");
46
47
  const LEGACY_REPO_SKILL_EXAMPLES_DIR = join(LEGACY_REPO_SKILL_ASSET_ROOT, "examples");
47
48
  join(LEGACY_REPO_SKILL_ASSET_ROOT, "helpers");
48
49
  const LEGACY_REPO_SKILL_PROGRESS = join(LEGACY_REPO_SKILL_ROOT, "progress.md");
49
- const LEGACY_SKILL_NAME = "first-tree-cli-framework";
50
- const LEGACY_SKILL_ROOT = join("skills", LEGACY_SKILL_NAME);
51
- const LEGACY_SKILL_ASSET_ROOT = join(LEGACY_SKILL_ROOT, "assets", "framework");
52
- const LEGACY_SKILL_VERSION = join(LEGACY_SKILL_ASSET_ROOT, "VERSION");
53
- join(LEGACY_SKILL_ASSET_ROOT, "templates");
54
- join(LEGACY_SKILL_ASSET_ROOT, "workflows");
55
- join(LEGACY_SKILL_ASSET_ROOT, "prompts");
56
- const LEGACY_SKILL_EXAMPLES_DIR = join(LEGACY_SKILL_ASSET_ROOT, "examples");
57
- const LEGACY_SKILL_PROGRESS = join(LEGACY_SKILL_ROOT, "progress.md");
58
50
  const LEGACY_FRAMEWORK_ROOT = ".context-tree";
59
51
  const LEGACY_VERSION = join(LEGACY_FRAMEWORK_ROOT, "VERSION");
60
52
  const LEGACY_PROGRESS = join(LEGACY_FRAMEWORK_ROOT, "progress.md");
@@ -81,7 +73,6 @@ function frameworkVersionCandidates() {
81
73
  FRAMEWORK_VERSION,
82
74
  CLAUDE_FRAMEWORK_VERSION,
83
75
  LEGACY_REPO_SKILL_VERSION,
84
- LEGACY_SKILL_VERSION,
85
76
  LEGACY_VERSION
86
77
  ];
87
78
  }
@@ -90,7 +81,6 @@ function progressFileCandidates() {
90
81
  INSTALLED_PROGRESS,
91
82
  CLAUDE_INSTALLED_PROGRESS,
92
83
  LEGACY_REPO_SKILL_PROGRESS,
93
- LEGACY_SKILL_PROGRESS,
94
84
  LEGACY_PROGRESS
95
85
  ];
96
86
  }
@@ -105,7 +95,6 @@ function detectFrameworkLayout(root) {
105
95
  if (pathExists(root, FRAMEWORK_VERSION)) return "skill";
106
96
  if (pathExists(root, CLAUDE_FRAMEWORK_VERSION)) return "claude-skill";
107
97
  if (pathExists(root, LEGACY_REPO_SKILL_VERSION)) return "legacy-repo-skill";
108
- if (pathExists(root, LEGACY_SKILL_VERSION)) return "legacy-skill";
109
98
  if (pathExists(root, LEGACY_VERSION)) return "legacy";
110
99
  return null;
111
100
  }
@@ -261,7 +250,6 @@ var Repo = class {
261
250
  preferredProgressPath() {
262
251
  const layout = this.frameworkLayout();
263
252
  if (layout === "legacy") return LEGACY_PROGRESS;
264
- if (layout === "legacy-skill") return LEGACY_SKILL_PROGRESS;
265
253
  if (layout === "legacy-repo-skill") return LEGACY_REPO_SKILL_PROGRESS;
266
254
  if (layout === "claude-skill") return CLAUDE_INSTALLED_PROGRESS;
267
255
  return INSTALLED_PROGRESS;
@@ -269,7 +257,6 @@ var Repo = class {
269
257
  frameworkVersionPath() {
270
258
  const layout = this.frameworkLayout();
271
259
  if (layout === "legacy") return LEGACY_VERSION;
272
- if (layout === "legacy-skill") return LEGACY_SKILL_VERSION;
273
260
  if (layout === "legacy-repo-skill") return LEGACY_REPO_SKILL_VERSION;
274
261
  if (layout === "claude-skill") return CLAUDE_FRAMEWORK_VERSION;
275
262
  return FRAMEWORK_VERSION;
@@ -386,4 +373,4 @@ function isDirectory(root, relPath) {
386
373
  }
387
374
  }
388
375
  //#endregion
389
- export { LEGACY_SKILL_ROOT as C, SOURCE_INTEGRATION_MARKER as D, SOURCE_INTEGRATION_FILES as E, installedSkillRootsDisplay as O, LEGACY_SKILL_NAME as S, SKILL_ROOT 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, SKILL_NAME as w, LEGACY_SKILL_EXAMPLES_DIR 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 LEGACY_SKILL_ROOT, D as SOURCE_INTEGRATION_MARKER, E as SOURCE_INTEGRATION_FILES, a as BUNDLED_SKILL_ROOT, b as LEGACY_REPO_SKILL_ROOT, h as INSTALLED_SKILL_ROOTS } from "./repo-Cc5U4DWT.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";
@@ -22,11 +22,7 @@ function resolveCanonicalSkillRoot(sourceRoot) {
22
22
  }
23
23
  function copyCanonicalSkill(sourceRoot, targetRoot) {
24
24
  const src = resolveCanonicalSkillRoot(sourceRoot);
25
- for (const relPath of [
26
- ...INSTALLED_SKILL_ROOTS,
27
- LEGACY_REPO_SKILL_ROOT,
28
- LEGACY_SKILL_ROOT
29
- ]) {
25
+ for (const relPath of [...INSTALLED_SKILL_ROOTS, LEGACY_REPO_SKILL_ROOT]) {
30
26
  const fullPath = join(targetRoot, relPath);
31
27
  if (existsSync(fullPath)) rmSync(fullPath, {
32
28
  recursive: true,
@@ -1,5 +1,5 @@
1
- import { C as LEGACY_SKILL_ROOT, D as SOURCE_INTEGRATION_MARKER, O as installedSkillRootsDisplay, T as SKILL_ROOT, 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 } from "./repo-Cc5U4DWT.js";
2
- import { a as resolveCanonicalSkillRoot, i as resolveBundledPackageRoot, n as copyCanonicalSkill, t as upsertSourceIntegrationFiles } from "./source-integration-CuKjoheT.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
@@ -46,7 +46,6 @@ function formatUpgradeTaskList(repo, localVersion, packagedVersion, layout) {
46
46
  const migrationTasks = [];
47
47
  if (layout === "legacy") migrationTasks.push("- [ ] Remove any stale `.context-tree/` references from repo-specific docs, scripts, or workflow files");
48
48
  if (layout === "legacy-repo-skill") lines.push("## Migration", `- [ ] Remove any stale \`${LEGACY_REPO_SKILL_ROOT}/\` references from repo-specific docs, scripts, workflow files, or agent config`, "");
49
- if (layout === "legacy-skill") migrationTasks.push(`- [ ] Remove any stale \`${LEGACY_SKILL_ROOT}/\` references from repo-specific docs, scripts, workflow files, or agent config`);
50
49
  if (repo.hasCanonicalAgentInstructionsFile() && repo.hasLegacyAgentInstructionsFile()) migrationTasks.push(`- [ ] Merge any remaining user-authored content from \`${LEGACY_AGENT_INSTRUCTIONS_FILE}\` into \`${AGENT_INSTRUCTIONS_FILE}\`, then delete the legacy file`);
51
50
  else if (repo.hasLegacyAgentInstructionsFile()) migrationTasks.push(`- [ ] Rename \`${LEGACY_AGENT_INSTRUCTIONS_FILE}\` to \`${AGENT_INSTRUCTIONS_FILE}\` to use the canonical agent instructions filename`);
52
51
  if (migrationTasks.length > 0) lines.push("## Migration", ...migrationTasks, "");
@@ -125,7 +124,6 @@ function runUpgrade(repo, options) {
125
124
  });
126
125
  console.log(`Migrated legacy .context-tree/ layout to ${installedSkillRootsDisplay()}.`);
127
126
  } else if (layout === "legacy-repo-skill") console.log(`Migrated legacy ${LEGACY_REPO_SKILL_ROOT}/ layout to ${installedSkillRootsDisplay()}.`);
128
- else if (layout === "legacy-skill") console.log(`Migrated ${LEGACY_SKILL_ROOT}/ to ${installedSkillRootsDisplay()}.`);
129
127
  else if (missingInstalledRoots.length > 0) console.log(`Repaired missing installed skill roots (${missingInstalledRoots.map((root) => `${root}/`).join(", ")}) and refreshed ${installedSkillRootsDisplay()} from the bundled first-tree package.`);
130
128
  else console.log(`Refreshed ${installedSkillRootsDisplay()} from the bundled first-tree package.`);
131
129
  const output = formatUpgradeTaskList(workingRepo, localVersion, packagedVersion, layout);
@@ -1,4 +1,4 @@
1
- import { C as LEGACY_SKILL_ROOT, T as SKILL_ROOT, g as LEGACY_AGENT_INSTRUCTIONS_FILE, n as Repo, r as AGENT_INSTRUCTIONS_FILE } from "./repo-Cc5U4DWT.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
@@ -186,14 +186,14 @@ function rel(path) {
186
186
  return relative(treeRoot, path);
187
187
  }
188
188
  function isInstalledSkillPath(relPath) {
189
- return relPath === SKILL_ROOT || relPath.startsWith(`${SKILL_ROOT}/`) || relPath === LEGACY_SKILL_ROOT || relPath.startsWith(`${LEGACY_SKILL_ROOT}/`);
189
+ return relPath === SKILL_ROOT || relPath.startsWith(`${SKILL_ROOT}/`);
190
190
  }
191
191
  function isFrameworkContainerDir(relPath, fullPath) {
192
192
  if (relPath !== "skills") return false;
193
193
  try {
194
194
  const entries = readdirSync(fullPath).filter((entry) => !entry.startsWith("."));
195
195
  if (entries.length === 0) return false;
196
- return entries.every((entry) => entry === "first-tree" || entry === "first-tree-cli-framework");
196
+ return entries.every((entry) => entry === SKILL_NAME);
197
197
  } catch {
198
198
  return false;
199
199
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "first-tree",
3
- "version": "0.0.6",
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": {