feat(context-tree): adopt the external context-tree CLI behind context_tree.repository - #2383
feat(context-tree): adopt the external context-tree CLI behind context_tree.repository#2383ggordonhall wants to merge 6 commits into
Conversation
|
All contributors are covered by the First Tree CLA. |
yuezengwu
left a comment
There was a problem hiding this comment.
Reviewing exact head f2c44d594d0d1151f39052312333ffe0123a56dd.
The targeted tests and typecheck are green, but focused packaging/runtime QA found three release-blocking integration failures that the unit coverage does not exercise:
-
[P1] Adding the dependency activates unusable external Skills even when external mode is unset.
@first-tree-ai/context-treehas a global-installpostinstall. A scratchnpm install --globalof a parent package with this dependency (the same lifecycle shape as a global First Tree install) copied all sixcontext-tree-*Skills into both~/.claude/skillsand~/.codex/skillswith nocontext_tree.repositoryconfigured. At the same time, npm exposed the dependency bin only at the parent's nestednode_modules/.bin/context-tree; there was nocontext-treein the global prefixbin. Every installed Skill starts by runningcontext-tree ..., so the default/unset path both makes the two Skill families live and leaves the new one unable to execute. TheensureContextTreeSkills()config gate cannot prevent a dependency lifecycle script that already ran during package installation. -
[P1] A connection made by
loginis invisible inside the Codex runtime. In a scratch home,connect first-tree-ai/opentag-context-tree --project-path <workspace>created<host-home>/.context-tree/connections.json, andresolvewith that hostHOMEsucceeded. Re-running the same packaged CLI with First Tree's real Codex app-server environment shape (HOME=<workspace>,CODEX_HOME=<host-home>/.codex, as built inworkspace-sandbox.ts) returnedNO_CONNECTION. The external CLI derives all state fromos.homedir(), so login and the agent do not share the same state root; the advertised pre-link is ineffective and setup would start creating a second state root under the workspace. -
[P1] External mode removes the working First Tree Skills for providers the external installer does not support. The bundled CLI's
install --host allsupports onlyclaudeandcodex. With only.cursor/.grok/.kimihost directories present, its real JSON result wasinstalled: []and onlyclaude/codexskips. HoweveractiveCoreSkillNames("external")removes Read/Write/Seed for every First Tree runtime provider, and the new test even exercisescursorwhile asserting only that the old Skills are absent. Cursor, Grok, Kimi, OpenCode, Amp, DeepSeek, and Pi can therefore receive a briefing that directs them tocontext-tree-readwhile no such discoverable Skill exists.
There is also a validation mismatch: First Tree accepts values such as owner/.. under its current OWNER/REPO regex, while the bundled CLI rejects them as not an explicit GitHub repository identity. Since the config value alone switches the runtime to external mode, this can stand down the working Skills for a repository the integration can never connect.
Validation completed: git diff --check; pnpm check (exit 0; existing warnings only); pnpm typecheck (9/9); targeted Shared 4/4, Client 70/70, and CLI 22/22 tests. Focused CLI QA also confirmed symlinked project paths and shell-like repository inputs fail closed without writes. Please add packaging/runtime-boundary coverage: an actual globally installed First Tree artifact, the effective provider environment, and at least one non-Claude/Codex provider.
Review on #2383 found three real integration defects that the unit coverage did not reach. Two are fixed here, one is scoped and documented. Put `context-tree` on the Agent's PATH. Every installed Skill invokes `context-tree` by name, but npm links only a top-level package's bin — never a transitive dependency's — so external mode was non-functional in a packaged install even when correctly configured. `login` now writes a shim beside the channel CLI binary, which is the directory the Agent's PATH is built from. A `context-tree` this CLI did not write is never clobbered and never removed, so a real global install is left alone. An unwritable bin directory is reported with its remedy rather than failing the login. Make the switch work in both directions. A global install runs the bundled dependency's own postinstall, which places the Skills in ~/.claude/skills and ~/.codex/skills regardless of `context_tree.repository` — so "unset behaves exactly as today" did not hold and both Skill families went live. The external CLI has no uninstall, so installs are now recorded in a ledger and `login` with the key unset removes exactly what was recorded. No ledger means remove nothing, which is what keeps a hand-run `context-tree install` untouched. Gate the stand-down to the hosts the installer supports. `context-tree install` accepts only `claude` and `codex`; `activeCoreSkillNames` was dropping Read/Write/Seed for every provider, leaving Cursor/Grok/Kimi/OpenCode/Amp/ DeepSeek/Pi with no Context Tree Skills at all and a briefing naming Skills that were never installed. `effectiveContextSourceKind` narrows `external` to the supported providers, and projection, briefing, and admission all derive the kind through it so they cannot disagree. Enforce the repository pattern on the read path. `resolveConfigReadonly` returns file values unvalidated, so the field regex never applied where it mattered; the owner segment now follows GitHub's own rule and `.`/`..` are rejected. Codex's workspace-only sandbox sets HOME to the workspace, so a login-time connection is invisible there and the first in-session `context-tree-setup` resolves it. That is scoped to one opt-in mode and is documented, not fixed. No server, database, or Web changes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Thanks — this was a good catch on all three. I verified each against source before acting. Two are fixed, one is scoped and documented, and the validation nit turned out to be a real read-path bug rather than a cosmetic one. Pushed as P1 #1 — fixed, and it was worse than filedYou filed the missing bin under "unusable when unset". It is also unusable when set, which makes it the most serious item in the review. Every installed Skill invokes bare
P1 #3 — fixedConfirmed: the bundled installer's host table contains exactly You were also right that the old test asserted too little. It exercised The validation mismatch — fixed, and not cosmeticI had this filed as cosmetic until I checked P1 #2 — real mechanism, narrower blast radius; documented rather than fixed
On the packaging coverage requestI have not added a global-install CI matrix. Standing up a real globally installed artifact plus the effective provider environment is a fair ask for a release gate, but disproportionate for this MVP, and I would rather not add slow packaging infrastructure to land it. What I did add is narrow and fast: the shim landing in a scratch bin dir, the non-clobber guard, ledger-scoped removal (including the property that a Skill absent from the ledger survives), provider gating across all ten providers, and the regex table. Verification is The end-to-end path this cannot cover — One more note for the record, since it explains why this survived local testing: the machine I developed on has had a global |
yuezengwu
left a comment
There was a problem hiding this comment.
Requesting changes on exact head 653bbf2048caaf4cd9f0338973025254b450e0c8.
Two release blockers remain in the new fix:
-
The default/unset path is still not reversible after a global First Tree install. The transitive dependency postinstall runs before
first-tree login, installs all sixcontext-tree-*Skills into both Claude and Codex, and creates no First Tree ledger.removeInstalledContextTreeSkills()deliberately removes nothing without that ledger, so an existing authenticated user who upgrades (or even reruns login with the key unset) keeps the unconfigured external Skills indefinitely. I reproduced the packaged postinstall in a scratch home: 6 Claude + 6 Codex Skills,ledgerExists: false. The new “removes nothing when there is no ledger” test confirms the cleanup cannot distinguish this dependency-owned state from a manual install. The unconditional dependency lifecycle side effect must be prevented or recorded at install time; a ledger created only after external mode is enabled cannot repair it. -
CONTEXT_TREE_REPOSITORY_PATTERNstill does not match the bundled CLI schema. First Tree acceptsowner/-repo,owner/_repo,owner/.hidden, andowner/foo.git; the bundledcontext-tree connectrejects every one as not an explicit GitHub OWNER/REPO identity. These values still switch the runtime into external mode and can stand down the working Skills. Please use the same repository-name contract as the dependency (leading alphanumeric, max 100, and no.gitsuffix) and add the boundary cases.
The current exact-head GitHub CI also has deterministic integration failures that need reconciliation before merge: Client/Web fails because the shipped briefing now has four welcome rows while first-tree-welcome still requires exactly three, and Lint & Type Check fails because the current PR merge ref leaves the Antigravity SessionContext fixture missing a required field. The Server failure appears unrelated/flaky, but the two above are direct merge-state failures.
Local validation on the detached head: targeted Shared/Client/CLI suites 42/42, repository typecheck 9/9, pnpm check exit 0 (existing warnings), and git diff --check pass. The packaged postinstall boundary probe and the real bundled CLI rejection probe produced the failures above.
|
I have read the CLA Document and I hereby sign the CLA |
|
recheck |
yuezengwu
left a comment
There was a problem hiding this comment.
Reviewed exact head 896d572cacbf8efd8057178a0cd6a6dd2a7be95c. Context baseline: first-tree-ai/opentag-context-tree@d0abc83bc2a07d7a0d4e393ec45a57d37feccd65.
[P1] An ordinary login with external mode never enabled deletes independently installed user Skills. At apps/cli/src/core/context-tree-cli.ts:377-382, an absent or unusable setting unconditionally reaches uninstall --host all (:319). The external CLI's prefix-based uninstall is not evidence that First Tree installed those directories or owns the user's edits.
I reproduced this against the bundled 0.1.9 CLI and the actual ensureContextTreeSkills() function in an isolated home: the only config was the server URL, no installation ledger existed, and external mode had never been enabled. A user-authored .claude/skills/context-tree-personal/SKILL.md and a customized, manually installed .codex/skills/context-tree-read/SKILL.md were both removed; .claude/skills/personal-helper/SKILL.md remained. The function returned status: removed. No real account home or login service was used.
Please make never-enabled/unconfigured operation a no-op for independently owned files, and restrict rollback to verifiably First Tree-installed, unmodified artifacts. Any migration that deliberately removes manual installations needs an explicit operator action/consent, not a successful login. The PR description and tests asserting prefix ownership do not establish that consent. Keep the no-ledger/manual-install case as a preservation regression test rather than asserting deletion.
The previous repository-validation finding is fixed. The leading punctuation and repeated-suffix cases are rejected, supported URL forms normalize to OWNER/REPO, and config tests pass. The 0.1.9 postinstall now also has a direct-global-install guard: I exercised its exact script in an npm-shaped nested dependency path with the global flag set; it printed the manual-install instruction and created no Skills. That guard does not justify the unconditional login cleanup above.
Current CI failure is separate and base-originated. Lint & Type Check checked merge commit 0be974c17f494f33030838a029a3ac9997e5c372 (parents 38752415320c97c0bd122aa4afb13c3ff60b1b06 and this PR head). The error is TS2322 at packages/client/src/providers/antigravity/__tests__/handler.test.ts:84: its SessionContext fixture lacks required noteTurnStart. Both that fixture and the defining runtime/handler.ts are identical between current main and the CI merge tree; the Antigravity test is absent from the PR head itself. This is not a Context Tree regression. It still needs a green merge check after the base-side fix; I did not rerun CI or modify either branch.
Fresh local validation on this head: diff-check and pnpm check pass; uncached CLI/dependency build 3/3 and monorepo typecheck 9/9 pass; shared config 35/35, CLI Context Tree/doctor/login 47/47, client source/briefing/preparation 89/89, and no-model welcome floor 21/21 pass. The deletion probe establishes the blocker despite those green assertions. No model-backed eval or full live-provider QA is claimed.
yuezengwu
left a comment
There was a problem hiding this comment.
Exact-head re-review of 550e86ebcb1b7a90ca333f025268d21883d17e09.
The new comparison preserves hand-authored prefix-sharing skills and edited packaged skills, but it does not establish who installed an unmodified official copy. The inline ownership finding remains blocking.
Independent reproduction on this head, with the actual pinned Context Tree 0.1.9 CLI and an isolated account/First Tree home/PATH:
- Leave
context_tree.repositoryunset and never invoke First Tree's external-mode installer. - Independently run the packaged
context-tree install --host allas an operator; both host homes receive the six official skills. - Invoke the actual
ensureContextTreeSkills()used by ordinary login. - Result:
status=removed, all 12 independently installed official skill directories are removed. A separate hand-authoredcontext-tree-personalcontrol and a foreign global CLI binary are preserved.
Content identity is not installer provenance. This can still disable an independently managed Context Tree installation for other projects just by logging into First Tree. Please preserve/report unowned official copies unless the operator explicitly requests cleanup; remove only artifacts with First Tree ownership evidence. Any legacy orphan cleanup needs its own explicit scope rather than treating every identical official copy as an orphan. Add a regression case for this independent, unmodified official install, not only for edited copies.
Verification: git diff --check, pnpm check, uncached build (5 tasks), uncached typecheck (9 tasks), and 215 focused tests passed (CLI 70, client 89, shared 35, no-model skill floor 21). No branch changes were made; this is not a claim of full live/provider QA.
CI is separately still red: Lint & Type Check reports the Antigravity SessionContext fixture missing noteTurnStart at packages/client/src/providers/antigravity/__tests__/handler.test.ts:84. The job checked out synthetic merge ede9539c9815c996defdb88d70e4dd0b30149b88, combining main 38752415320c97c0bd122aa4afb13c3ff60b1b06 with this PR head. The fixture blob is identical in that merge and its main parent (431c36a360b71d7cd93df362f97a7200b7cc476c) and is absent from the pure PR head. This is a main-side integration blocker, not a failure introduced by this two-file revision, and a green PR-head typecheck does not make the merge gate green.
Non-blocking documentation follow-up: the PR description and external-mode CLI reference still describe prefix-wide uninstall --host all, which no longer matches this implementation.
| if (!packagedNames.includes(entry.name) || !directoriesEqual(target, join(packagedRoot, entry.name))) { | ||
| preservedSkillPaths.push(target); | ||
| continue; | ||
| } | ||
| try { | ||
| rmSync(target, { recursive: true, force: true }); |
There was a problem hiding this comment.
[P1] Do not equate identical official payloads with First Tree ownership
An operator can independently install the official Context Tree 0.1.9 skills, never enable context_tree.repository, and then lose all six skills in both Claude and Codex homes on an ordinary First Tree login. I reproduced exactly that with the real package: this byte comparison succeeds for an independent official install, and the following rmSync removes all 12 directories. Preserving edited/prefix-only copies fixes part of the issue, but matching official bytes still proves payload identity, not that First Tree installed or owns the files. Require First Tree provenance or explicit cleanup consent; otherwise preserve/report the copies and test the independent unmodified-install case.
…t_tree.repository
Bundle `@first-tree-ai/context-tree` with the CLI and make
`context_tree.repository` a single switch into external Context Tree mode.
Set it and `login` runs `context-tree install`, which writes the
`context-tree-{setup,create,connect,read,write,publish}` Skills into the host
Skill directories, then `context-tree connect <OWNER/REPO>` for each existing
agent workspace. Leave it unset and the machine behaves exactly as before.
The two Skill families are never live at the same time. A new `ContextSource`
kind `external` drives both halves of the switch from one discriminant: the
managed-skills projection stops projecting `first-tree-{read,write,seed}` plus
`context-tree-{review,audit}`, and the briefing routes to the `context-tree-*`
Skills instead. Review and Audit are stood down because both act on a Team
binding this mode bypasses and both route through the Skills above.
`activeCoreSkillNames()` is the single source for both `buildDesiredSkills` and
`verifyManagedSkillsProjectionForAdmission`, so a workspace can never project a
ledger its own admission proof then rejects. `ALL_KNOWN_CORE_SKILL_NAMES` stays
whole, so the existing prune path removes already-installed copies on the next
session.
Nothing is deleted: `core/context-integration/`, the `first-tree context`
commands, and the `skills/first-tree-*` payloads all stay and keep building.
`FIRST_TREE_CORE_SKILL_NAMES` is untouched — it doubles as the reserved
Team-Skill slug set. Reverting is one `config set` away.
No server, database, or Web changes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review on #2383 found three real integration defects that the unit coverage did not reach. Two are fixed here, one is scoped and documented. Put `context-tree` on the Agent's PATH. Every installed Skill invokes `context-tree` by name, but npm links only a top-level package's bin — never a transitive dependency's — so external mode was non-functional in a packaged install even when correctly configured. `login` now writes a shim beside the channel CLI binary, which is the directory the Agent's PATH is built from. A `context-tree` this CLI did not write is never clobbered and never removed, so a real global install is left alone. An unwritable bin directory is reported with its remedy rather than failing the login. Make the switch work in both directions. A global install runs the bundled dependency's own postinstall, which places the Skills in ~/.claude/skills and ~/.codex/skills regardless of `context_tree.repository` — so "unset behaves exactly as today" did not hold and both Skill families went live. The external CLI has no uninstall, so installs are now recorded in a ledger and `login` with the key unset removes exactly what was recorded. No ledger means remove nothing, which is what keeps a hand-run `context-tree install` untouched. Gate the stand-down to the hosts the installer supports. `context-tree install` accepts only `claude` and `codex`; `activeCoreSkillNames` was dropping Read/Write/Seed for every provider, leaving Cursor/Grok/Kimi/OpenCode/Amp/ DeepSeek/Pi with no Context Tree Skills at all and a briefing naming Skills that were never installed. `effectiveContextSourceKind` narrows `external` to the supported providers, and projection, briefing, and admission all derive the kind through it so they cannot disagree. Enforce the repository pattern on the read path. `resolveConfigReadonly` returns file values unvalidated, so the field regex never applied where it mattered; the owner segment now follows GitHub's own rule and `.`/`..` are rejected. Codex's workspace-only sandbox sets HOME to the workspace, so a login-time connection is invisible there and the first in-session `context-tree-setup` resolves it. That is scoped to one opt-in mode and is documented, not fixed. No server, database, or Web changes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…is off The unset/unusable path of ensureContextTreeSkills previously ran the external CLI's prefix-based `uninstall --host all`, which deletes every `context-tree-*` directory regardless of origin. An ordinary login on a machine that never enabled external mode could therefore delete a user-authored or customized skill sharing that prefix. Remove only provably first-tree-installed, unmodified artifacts: a `context-tree-*` skill directory is deleted solely when its name matches a packaged skill AND its contents are byte-identical to the bundled payload (what both the dependency postinstall and `context-tree install` write). Anything else is preserved and reported via a new preservedSkillPaths field. Genuine packaged leftovers from a dependency postinstall are still cleaned up, keeping the switch reversible in both directions.
550e86e to
44925a1
Compare
yuezengwu
left a comment
There was a problem hiding this comment.
Reviewed exact head 44925a1315dc7547d9769a253120c25a506bd48d against current main 09d7401034bb02dd688b69354c9dd2c569ae76d1 in a clean isolated worktree.
The previous cleanup blocker is fixed: the actual 0.1.12 installer plus this login helper preserves all 16 independently installed official Skill directories when external mode was never enabled, and after an enable/disable cycle. Managed rollback removes 15 unchanged directories while retaining an edited control.
One ownership/data-loss blocker remains on the enabled login path, documented inline: a repeated login overwrites customized content in a Skill that the new ledger explicitly does not own. The cleanup record prevents later deletion but does not protect the earlier installation step.
Fresh validation: diff-check, pnpm check (warnings only), uncached CLI/dependency build (3 tasks), uncached typecheck (9 tasks), and 219 focused tests pass (CLI 74, client 89, shared 35, no-model skill floor 21). Current CI is green and the current-base merge is clean. The independent repeated-login probe reproduces the content loss despite those checks. No source changes, full live/provider QA, model-backed eval, merge, or deployment were performed.
Non-blocking: please update the PR description, which still describes 0.1.9 and prefix-wide uninstall; the CLI reference now correctly describes ownership-scoped cleanup.
| return { ...base, status: "failed", reason: String(error) }; | ||
| } | ||
|
|
||
| const install = await runContextTreeCommand(["install", "--host", "all"]); |
There was a problem hiding this comment.
[P1] Preserve unowned customized Skills before login-time installation
Every login with external mode enabled calls install --host all here. The pinned 0.1.12 installer recursively removes each existing packaged-name destination before copying its payload; the ownership set is not used to gate that replacement. I reproduced this with the actual helper and CLI in an isolated home: independently install the official Skills, enable external mode once (the ownership ledger correctly has zero paths), customize .claude/skills/context-tree-read/SKILL.md, then repeat the login helper with unchanged configuration. It returns installed and discards the custom content; later rollback even reports all 16 directories as preserved. Please preserve/report unowned or edited copies before this destructive install, or require an explicitly scoped replacement action, and add the repeated-login regression. No real account home or login service was used.
yuezengwu
left a comment
There was a problem hiding this comment.
Re-reviewed current head 4cee7742c7b2badee65dd5444be8181b196eea20 against main 09d7401034bb02dd688b69354c9dd2c569ae76d1. My previous request for changes is withdrawn; I found no remaining in-scope blocker.
The login-time replacement defect is fixed. With the actual bundled 0.1.12 CLI and ensureContextTreeSkills() in isolated homes, repeated enabled login preserved customized independent installations and customized First Tree-owned installations, including additional user files. A host containing just one unowned packaged-name Skill was skipped in its entirety, while the other host installed successfully. Ownership was recorded only for the eight newly created directories in that scenario, and rollback preserved the independent copy.
Fresh rollback probes also preserved all 16 independently installed official Skill directories and removed only the 15 unchanged managed directories when one managed Skill was customized. These checks cover both the original ownership/deletion finding and the repeated-login overwrite finding.
Validation at this head: pnpm check passed with 36 warnings and 22 informational diagnostics; uncached CLI dependency builds (3 tasks) and monorepo typecheck (9 tasks) passed. All 221 focused tests passed (CLI 76, Client 89, Shared 35, no-model floor 21), together with seven independent real-CLI/helper scenarios. The current head merges cleanly with the stated main revision. Current technical CI is green.
This is test-only review evidence. I did not run the full monorepo test suite, tests on the synthetic merge result, live provider sessions, or model-backed evals. The documented workspace-only Codex HOME limitation remains; the included cross-surface QA case describes the provider validation still needed before release.
Bundles
@first-tree-ai/context-tree^0.1.12 and usescontext_tree.repositoryto select external Context Tree skills for Claude and Codex. Other providers retain First Tree’s skills. Login installs the external skills, creates an accessible CLI shim, and connects existing workspaces.Installation and rollback respect ownership. Login skips a host’s entire installation if any packaged-name skill is unowned or edited, reports the preserved paths, and continues with other hosts. Disabling external mode removes only recorded First Tree-created directories that still match the packaged payload. Independent official installs, customizations, and legacy copies without ownership evidence remain intact. The dependency’s transitive postinstall creates no home skills.
Repository inputs normalize to the dependency’s OWNER/REPO contract. Doctor reports invalid settings. Connections and managed trees survive rollback; only a First Tree-marked CLI shim is removed.
Codex workspace-only HOME isolation remains a documented limitation: login’s host-home connection is not visible in that environment. A cross-surface QA case is included at
packages/qa/cases/cross-surface/external-context-tree-adoption.md; live provider QA has not been run.Validation:
pnpm check,pnpm typecheck, and 68 focused CLI tests pass for the latest fix, including repeated-login preservation for both independent and First Tree-owned customized skills. The full monorepo suite passed on the preceding revision using Node 24 and en_US.UTF-8; it was not rerun for this localized follow-up. No model-backed evals were run.