| name | first-tree-seed |
| version | 0.4.0 |
| cliCompat | {"first-tree":">=0.5.16 <0.6.0"} |
| description | Bootstrap a team's Context Tree from readable source repos — for an onboarding "build / set up the Context Tree" task on a tree that has no domain structure yet: either no tree exists (creates and binds it) or a bound-but-empty tree (fills it). Supports clean explicit-Team invocation without a Workspace manifest, managed briefing, or prior setup-chat transcript, while preserving managed-workspace compatibility. Proposes an initial top-level + second-level domain structure for approval, then drafts initial leaf content — each as a reviewable PR/MR. Refuses unrelated re-seeding once the tree has domain structure and recovers Phase 2 from durable merged Tree state plus the same explicit sources. |
First Tree — Seed
Read this skill before drafting the first content into a Context Tree
that has no domain structure yet. It owns the bootstrap from "the tree has
no domain structure — either none exists, or a bound-but-empty one" to "the
tree has real top + second level structure plus initial leaf nodes drawn
from readable source repos". It first resolves the tree's state, then — for
a tree that needs building — creates the repo with first-tree tree init
when none exists (see Resolve the tree's state). Every subsequent write —
one PR/MR at a time, one decision at a time — belongs to first-tree-write,
not here.
When To Use This Skill
Use first-tree-seed | Use a different skill |
|---|
| The team has no tree yet, or a bound tree with no normal domain structure per the generated policy's content classes | The tree already has a normal domain structure → first-tree-write (incremental source-driven write) |
| First content pass on resolved readable sources | Broad maintenance or drift-audit work on an existing tree |
| Invoked by name — by a human, an agent, or an onboarding kickoff prompt (see Resolve the tree's state) | Not an org admin, or local gh is unauthenticated, and the team has no tree — current tree init creation is GitHub-only; source reads separately use the CLI for their detected forge |
The skill is single-shot per tree setup: once the tree has domain
structure, a new or unrelated seed request refuses and routes further work
through first-tree-write or a focused maintenance task. The one exception is
a verified Phase 2 continuation recovered from the merged Tree's durable Seed
progress and exact source ledger. It may run in a new process, agent, or chat;
it is completion of the same seed, not a second seed.
Policy Baseline
Before drafting anything, load this skill and use the generated Context Tree
Policy already present in AGENTS.md / CLAUDE.md as the content baseline.
Every node this skill creates must satisfy that policy: durable decision,
current truth, rationale, source-system boundary, Double Test, smallest correct
edit, no implementation detail, no history, and no actionable future work in
normal tree content.
Invocation modes and live authority
Seed has two compatible entry modes:
- Portable / clean invocation. The task supplies one explicit
selectedTeamId, one task-local treePath, and at least one explicit readable
source input. Do not require or reconstruct a Workspace manifest, managed
briefing, setup-chat transcript, Web selection, or account default/current
Team. If the Team or every source input is missing, ask only for the missing
input and stop before touching local or remote state.
- Managed workspace. Keep using the generated workspace paths and declared
sources when they exist. A non-empty manifest source list remains
authoritative. Managed state is a path/source convenience, not authority for
role or binding, and no prior setup-chat transcript is required for recovery.
When an explicit Team is available, start every invocation with this online,
read-only admission check:
first-tree tree seed --team "<selectedTeamId>" --json
The response is the only current admission view: the Server resolves active
membership, active Admin role, and that exact Team's current binding. Do not
cache the Admin result or substitute an owner, prior task, local manifest, Web
selection, or another Team. An ordinary active member receives stable
CONTEXT_TREE_SEED_NEEDS_ADMIN; report the same selected Team and ask an active
Admin of that Team to continue. Do not fall back to a default Team and do not
run gh, create a branch/repo/PR, or change a binding.
Repeat the same preflight immediately before every remote mutation: tree init,
the Phase 1 push, Phase 1 PR/MR creation, the Phase 2 push, and Phase 2 PR/MR
creation. tree init --team also repeats it internally before GitHub creation
and before final binding. A failure before remote mutation creates no remote
state. A failure after a branch, repository, binding, or PR/MR may exist must
report and query that real state; never claim rollback. On retry, inspect the
selected Team's current binding, the deterministic Seed branch, and any existing
PR/MR by head branch before creating anything.
Before authoring Phase 1 or evaluating any populated tree for Phase 2, read
references/durable-progress.md completely.
It owns the exact marker, canonical source-ledger shape, fixed-commit recovery
algorithm, and fail-closed mismatch outcomes. Do not invent a second progress
format in this file, chat, or local cache.
Resolve the tree's state (three states; stop on refuse)
This skill runs whenever a human or another agent invokes it — the tree's
state is the gate, not the prompt's origin. Before reading any source,
resolve which of three states the team's Context Tree is in and act
accordingly: create it when none exists, fill it when it is bound but empty,
and refuse when it already has domain structure (state C below), routing
that work to first-tree-write or a focused maintenance task.
Check durable Phase 2 recovery before classifying state C. Continue the same
Seed into Phase 2, including from a new process, agent, or chat, only when all of
these are true:
- a fresh Seed preflight says the selected Team is still bound, and a strict
fetch of that exact repo/branch succeeds;
- the fetched default branch contains
.first-tree/progress.md with the exact
<!-- first-tree-seed-progress:v1 --> marker and checked Phase 1 item defined
in the durable-progress reference, but no checked Phase 2 item;
- the same file's canonical source ledger matches the freshly re-resolved
explicit source identities entry-for-entry, and every recorded exact commit
is still readable; and
- the approved Phase 1 top-level structure exists on that fetched branch.
When all four hold, enter Phase 2 through its trigger below even though domain
directories now exist. The old Chat transcript and private local cache are
irrelevant. A checked Phase 2 item means Seed is complete. A populated tree
without the durable marker is state C, not a continuation. A missing source,
identity mismatch, unreadable recorded commit, or changed binding fails closed
and names the exact mismatch; it never starts another Seed.
A — No tree yet. In portable mode, the fresh Seed preflight says the
explicit Team is unbound. In managed mode, the workspace manifest has no
non-empty tree field and the compatible Team binding path is unbound. This is
the agent-driven creation path. First resolve readable sources using the
section below and detect each source forge from its supplied URL or origin.
Source access may use gh for GitHub or glab for GitLab, but current
first-tree tree init provisioning is GitHub-only and always uses local gh;
do not substitute glab for this command:
first-tree tree init --team "<selectedTeamId>" --title "<team display name>" \
--owner "<tree-github-owner>" --dir "<treePath>"
first-tree tree init --title "<team display name>" \
--owner "<tree-github-owner>" --dir "<workspaceRoot>/<manifest.tree>"
Keep source identity separate from the tree host. For each declared or
explicit local source, run git -C <source> remote get-url origin (or use the
explicit forge URL), then classify the host and parse its identity:
- GitHub: keep the owner segment, such as
acme in
github.com/acme/app, and use gh for host access/authentication.
- GitLab: keep the complete namespace before the repository, such as
acme/platform in gitlab.com/acme/platform/app, and use glab. Never
flatten a nested namespace to only acme or platform.
manifest.sources holds directory names (for example first-tree), not
forge identities. Never infer an owner or namespace from a clone directory.
The parsed source identity controls source access and metadata lookup; a GitLab
namespace is not a valid tree init --owner value because tree init currently
creates the Context Tree on GitHub.
Choose the GitHub account that will host the tree — pass --owner. When the
main resolved source is on GitHub, use its owner. When several GitHub sources
share one account, use it; when they span different accounts, use the account
that owns most of them, and ask the admin on a genuine tie. For GitLab-only
sources, preserve every full GitLab namespace in resolvedSources, then ask
which GitHub organization or personal account should host the Context Tree;
do not flatten or pass a GitLab namespace to --owner. Why: the repo created
by tree init must live under the GitHub account that the team's First Tree
GitHub App covers. When the App is already installed there, --owner matches
that installation account. When no App is installed yet, the explicit owner
prevents the tree from silently landing under the personal gh login. Sources
on GitLab remain readable through glab or git and do not change the tree
repo's GitHub host.
If --owner can't be honored, stop and surface it — do not silently
fall back. Two distinct cases, each with its own recovery:
- You lack repo-create rights under
<tree-github-owner> (no App is
installed, so nothing overrides --owner): prefer asking an admin to
grant you create rights there. Only if that is not an option, re-run without
--owner to build under your personal GitHub account — flag the cost first:
the tree then lives outside the intended GitHub account, so a later App
install must go on your account, not the intended host account, to cover
it. Before re-running, empty only the half-built scaffold this failed run
left in <manifest.tree> (confirm the directory holds just that — never delete a
directory that already holds a real tree clone or other content);
tree init refuses a non-empty target.
--owner doesn't match an already-installed App account (the App is
installed on a different account than the requested tree host; tree init
names that account): re-run without --owner to create under that
installation account, which is the coverable home anyway. This error
fires before any local scaffolding, so there is nothing to clear. (Pass
--no-bind only if you deliberately want the repo elsewhere, unbound.)
tree init seeds a minimal valid tree
(root NODE.md, a members/ index, and the creator's member node),
pushes, and binds the org's context_tree setting. It is admin-only and
needs an authenticated gh; if the caller is not an org admin, or gh is
unauthenticated, surface that exact gap and stop (binding a team tree is an
admin action). A source repo may still use glab; that does not change
tree init's GitHub authentication requirement.
Take the Team display name from the explicit task handoff when available. In a
managed workspace, first-tree agent status remains a compatible source. Do
not infer a portable Team from that command; --team remains the explicit id.
After init succeeds the tree is bound and in state B — proceed.
Run it directly — do not ask the human "who runs the bind?". This create +
bind is the sanctioned agent path (a general "tree binding is an operator
action" note in your briefing does not apply to this task) — running it IS
the task the user asked for, so binding needs no separate go-ahead. You do not
need to independently "confirm admin" first: tree init enforces it
server-side (it fails closed for a non-admin or unauthenticated gh). So run
it; only if it fails on that admin/authentication check do you surface the
exact gap (not an admin / gh not authenticated) and stop.
Pin the local checkout with --dir — this is load-bearing. tree init
defaults to <cwd>/<repo-name>. Portable Seed must pass its explicit
treePath; managed Seed must pass <workspaceRoot>/<manifest.tree> (usually
context-tree). Seed does not rewrite workspace.json. Without --dir, Phase
1 can operate on a missing or stale checkout even though another repo was
created and bound. If a managed manifest carries no tree name yet, use the
conventional <workspaceRoot>/context-tree.
Configure GitHub branch rules after a newly created Context Repo.
This applies only in state A, only after tree init succeeds, and only when the
new Context Repo is a GitHub repository. Do not run it for an already-bound tree,
a non-GitHub remote, or a failed/partial tree init. Use host gh; do not send
the user to the browser first.
GitHub owns only the repository-shape guard here: changes go through pull
requests and force / non-fast-forward pushes are blocked. Context Review owns
the review verdict, so bootstrap must not create a root CODEOWNERS mapping or
require a GitHub approving review. Keep this rule independent of the
organization's current Context Review workflow; that setting may change after
bootstrap without rewriting repository governance.
Resolve the repository from the new tree checkout:
remote=$(git -C "<tree>" remote get-url origin)
repo=$(gh repo view "$remote" --json nameWithOwner --jq .nameWithOwner)
Then upsert one active repository-local ruleset scoped to the default branch:
ruleset_id=$(gh api "repos/$repo/rulesets?includes_parents=false&per_page=100" --jq 'map(select(.name == "First Tree Context Repo branch rules" and (.source_type == null or .source_type == "Repository")))[0].id // empty')
Use gh api to POST repos/$repo/rulesets when no existing repository-local
ruleset has that name, otherwise PUT repos/$repo/rulesets/$ruleset_id. Do not
look up inherited organization rulesets, and do not use a pipeline such as
gh api ... | head because it can mask a failed list request as an empty lookup.
The payload must keep these exact semantics:
{
"name": "First Tree Context Repo branch rules",
"target": "branch",
"enforcement": "active",
"conditions": { "ref_name": { "include": ["~DEFAULT_BRANCH"], "exclude": [] } },
"rules": [
{ "type": "non_fast_forward" },
{
"type": "pull_request",
"parameters": {
"required_approving_review_count": 0,
"require_code_owner_review": false,
"dismiss_stale_reviews_on_push": false,
"require_last_push_approval": false,
"required_review_thread_resolution": false
}
}
]
}
The ruleset blocks force / non-fast-forward pushes and requires changes through
pull requests. It deliberately requires zero GitHub approvals and no Code Owner
review; the remaining review parameters are disabled. If any gh or ruleset
step fails after you know the repo is a GitHub Context Repo, continue with the
seed flow and tell the user automatic GitHub branch-rule setup failed. Give a
manual checklist in plain words: require pull requests, block force pushes, and
require zero approving or Code Owner reviews. Do not treat this setup failure as
a reason to delete or recreate the Context Repo.
Delay integration coverage guidance until there is a reviewable milestone —
recommend, never block. Detect the Context Tree repo's forge from its own
origin; do not infer it from a source repo. State A's tree init always
creates a GitHub-hosted tree and prints GitHub App coverage guidance. For a
pre-existing GitLab-hosted tree in state B, relay an integration URL or settings
target only when First Tree returns it explicitly; otherwise state that no
documented First Tree integration path was returned and do not invent one.
Capture any authoritative result, but do not interrupt source resolution,
structure review, or Phase 1 work with an installation detour.
After the Phase 1 PR/MR is open, or earlier only when the next requested
operation actually needs Cloud snapshot/reviewer access, surface an uncovered
tree repo once in the setup chat. Relay only a recovery URL returned by
tree init or another authoritative First Tree command:
- GitHub tree, selected-repositories install excludes it:
tree init
prints a GitHub installation-settings URL — an absolute https:// link, so
it renders clickable in chat. Relay it and say to add the tree repo there.
- GitHub tree, no App installed: give the admin a clickable link to the web
console's GitHub settings, built from the server URL the agent knows — take
the
Server: value from first-tree agent status and append
/settings/github (an absolute https://…/settings/github renders
clickable; a bare /settings/github path does not — the chat link guard
drops relative paths). Tell them to open it and click Install on GitHub.
This assumes the web console shares the server's origin (the standard
deployment); if it does not, the link may not resolve — then just name the
destination in words: Settings → GitHub in the web app. Do not
fabricate a raw GitHub App install URL yourself — the install must run
through the web console so it binds back to your org. Add the placement
caveat: the tree repo was created under the GitHub account shown as
<owner> in tree init's output, from --owner, so install the App on that
same account; installing it elsewhere will not cover this repo.
- GitHub tree, suspended install:
tree init prints the
installation-settings URL — relay it and say to reactivate the First Tree App
installation there.
- GitLab-hosted existing tree: use only a GitLab integration URL/settings
target returned by First Tree. Never substitute
/settings/github, a GitHub
App URL, or an invented GitLab product path.
B — Bound but unseeded. The tree is bound and holds at most non-normal
supporting/member structure under the generated policy's content classes,
with no normal top-level domain directory yet.
This is the normal seed entry, whether the bootstrap came from state A's
tree init above or from an earlier provision. Proceed to Phase 1. Phase 1
layers the normal domain skeleton plus any missing supporting/member structure
on top of whatever bootstrap nodes already exist: extend the root NODE.md
index and supporting trees rather than recreating them.
C — Already seeded. Outside the verified Phase 2 continuation above, the
tree has one or more normal top-level domain
directories — any directory directly under the explicit <treePath> in
portable mode or <workspaceRoot>/<manifest.tree>/ in managed mode
other than .git/, .first-tree/, .github/, members/, raw-context/, and
dotfile-prefixed dirs. Refuse with a one-line explanation pointing at
first-tree-write for incremental source-driven writes, or ask for a
focused maintenance scope. Do not delete nodes to force a re-seed —
that is human-owned.
The generated policy's archive/supporting and member content classes are not
normal domains. A tree that has only those non-normal classes plus bootstrap
nodes is still unseeded for this skill's purposes.
Resolve readable sources
Use one ordered source-resolution rule and call its result
resolvedSources throughout both phases:
- A non-empty
manifest.sources is authoritative. Require every declared
source on disk. Do not let a URL or local path supplied in the request hide a
half-provisioned declared workspace.
- An empty or absent
manifest.sources is valid. Use an explicit local
project folder or GitHub/GitLab repository URL supplied in the current
invocation or task handoff. If neither exists, ask the user for exactly one of them and stop
until they answer. Do not send them to Settings and do not make GitHub App
installation a prerequisite.
- Validate before reading. A local folder must exist, be readable, and be
a Git repository. A GitHub or GitLab URL must be a repository URL that the
matching host CLI (
gh or glab) or git can access; materialize a
task-local read checkout under the workspace worktrees/ directory. These
reads do not register team resources or mutate the source repo.
For every resolved source, determine sourceForge from the supplied URL or,
for a local repository, git remote get-url origin before choosing host tools.
Use gh for GitHub and glab for GitLab; use plain git for an unrecognized
host unless the user supplies its supported CLI. Preserve the full GitLab
namespace, including nested groups, in source identity and diagnostics.
For a local non-bare repository, inspect its identity in place but materialize
the evidence read at one exact commit. For a local bare repository, follow the
worktree protocol below. For a GitHub or
GitLab URL, clone/fetch it into task-local read storage, then read its default
branch. Private URLs may rely on the user's existing gh / glab / git
credentials; if access fails, report that exact credential/access gap rather
than asking for the First Tree GitHub App.
When state A needs --owner, use a parsed GitHub source owner as the candidate
GitHub tree host. A parsed GitLab namespace remains source metadata and must
not be flattened or passed to tree init; for GitLab-only sources, ask which
GitHub account should host the tree. If a local repo has no recognized forge
remote, ask for the GitHub tree host separately and never guess from its
directory name. Revalidate the same resolvedSources before Phase 2 so a later
process cannot silently switch inputs. Phase 1 records each canonical source
identity and exact evidence commit in the durable ledger; Phase 2 must
materialize those recorded commits, not newer default-branch tips.
For declared sources, require every clone on disk. Each source
clone lives at <workspaceRoot>/<manifest.sourcesRoot>/<manifest.sources[i]>
— the runtime writes sourcesRoot: "source-repos", so the path is
<workspaceRoot>/source-repos/<name> (a legacy flat manifest omits
sourcesRoot and keeps sources at <workspaceRoot>/<name>). Every entry
must exist (each is a bare clone — see Materialize source read
worktrees below). A missing source means the workspace is
half-provisioned; surface to the user and stop. Do not seed from a partial
workspace. The resulting declared clones become resolvedSources.
Materialize source read worktrees
Under the agent-managed repo model the declared sources are bare
clones (a git object store, no working tree) — you cannot ls or read
files at the clone path directly. Resolve each source's bare-clone path
from the manifest's sourcesRoot (do NOT hard-code it — the field is
optional):
sourcesRoot present (current manifests, value source-repos):
<source-clone> = <workspaceRoot>/<sourcesRoot>/<source>
(e.g. <workspaceRoot>/source-repos/<source>)
sourcesRoot absent (legacy flat manifest):
<source-clone> = <workspaceRoot>/<source>
Before any structural read, materialize one read worktree per source off
its declared/pinned ref when one exists, otherwise off the source's latest
default branch. Follow the Worktrees protocol in your AGENTS.md /
CLAUDE.md briefing:
git -C <source-clone> fetch origin
source_ref="<declared-or-pinned-ref-if-present>"
if [ -n "$source_ref" ]; then
remote_ref="refs/remotes/origin/$source_ref"
if git -C <source-clone> rev-parse --verify --quiet "$remote_ref^{commit}" >/dev/null; then
source_ref="origin/$source_ref"
fi
fi
if [ -z "$source_ref" ]; then
source_ref="$(git -C <source-clone> symbolic-ref --quiet --short refs/remotes/origin/HEAD || true)"
fi
if [ -z "$source_ref" ]; then
git -C <source-clone> remote set-head origin --auto
source_ref="$(git -C <source-clone> symbolic-ref --quiet --short refs/remotes/origin/HEAD)"
fi
git -C <source-clone> worktree add <workspaceRoot>/worktrees/seed-<source> "$source_ref"
Do not hard-code origin/main: GitLab and imported repositories often use
master, trunk, or another default branch. Do not pass a branch-like declared
ref such as main or release directly to worktree add; prefer the freshly
fetched remote-tracking ref (origin/main, origin/release) when it exists.
Preserve pinned SHAs, tags, and fully qualified refs that do not resolve as a
remote-tracking branch. Every "read every resolved source" / Tier 0–2 scan in
Phase 1 operates on these read worktrees, not the bare clone paths. Remove them
(git -C <source-clone> worktree remove <path>) once both PRs/MRs are open.
The Two Phases
Resolving the tree's state comes first; the two phases below are the build
itself — they run for a tree that needs building (states A and B). State C
refuses before reaching them.
Before opening either review request, detect the Context Tree repo's forge from
its own origin: use gh and PR terminology for GitHub, or glab and MR
terminology for GitLab. Do not infer the tree forge from a source repo. State A
tree init currently creates a GitHub tree, while state B may point at a tree
that was provisioned separately.
Phase 1 — Structure (~3–10 min, main agent only)
└─ Tiered structural read of every resolved source
└─ Propose top + second-level domain skeleton
└─ User approves checklist
└─ Create NODE.md stubs + members/<owner> + raw-context
└─ PR/MR 1 on chore/seed-phase1-structure
User merges PR/MR 1 ────────────────────────────────────────────
Phase 2 — Content (parallel; ~10–20 min wall-clock per batch)
└─ Allocate sub-agents (cap 6 concurrent; split big top-levels,
merge thin ones, batch dispatch when total work units > 6)
└─ Each sub-agent: deep-read its subtree, draft leaves, report coverage
└─ Main agent: consolidate + cross-check + collect escalations
└─ PR/MR 2 on chore/seed-phase2-content
The hand-off point between phases is the merge of PR/MR 1. Do not start
Phase 2 work before the user has signed off on the structure — the
sub-agent fan-out is expensive and Phase 2 inherits Phase 1's
structural decisions.
Phase 1 — Structure
Goal: produce a top + second-level domain skeleton that reflects
the team's concern axes (not the codebase's directory layout), drawn
from observable signals across all resolved source repos.
Tiered exploration
Read in three tiers; escalate from one to the next only when the prior
tier left a specific question unanswered.
| Tier | Action | Cost | Yields |
|---|
| 0 | Filesystem walk over every resolved source checkout (including declared bare-source read worktrees): one listing pass per source (a single find-style enumeration, depth-capped at 3–4 levels on huge repos), not repeated re-walks. List directories and well-known meta files. Filter out build artefacts (node_modules, dist, build, .next, .turbo, .venv, target, .git). Count file extensions, package boundaries, presence of docs/, infra/, terraform/, k8s/, .github/workflows/. | seconds, even on 10k+ files | monorepo shape, package boundaries, docs hierarchy, ops signals, file-type distribution |
| 1 | First-line / first-paragraph reads of meta files surfaced by Tier 0: top README.md, every packages/*/README.md, docs/*.md first H1, ARCHITECTURE.md, CONTRIBUTING.md, AGENTS.md, CLAUDE.md, SECURITY.md, package.json description fields, gh repo view --json description,topics,homepageUrl / glab repo view metadata where available (skip silently on any failure — 4xx, missing token, no network, no gh / glab binary; READMEs are the fallback). | 1–3 min total across all sources | one-sentence description per observed concept; product positioning; ops/contrib focus |
| 2 | Surgical content read of a specific file that Tier 0+1 left ambiguous (e.g. packages/foo/ exists, README is empty → read packages/foo/src/index.ts head 30 lines). Triggered per uncertainty, not as a blanket sweep. | 1–3 min per question, 0 if not needed | answer one structural question |
Tier 2 is targeted, not exhaustive. If you cannot articulate the
specific question Tier 2 will answer, do not do Tier 2 — you have
enough already. Sub-agents are allowed at this tier when multiple
independent Tier-2 questions exist, but the default is "main agent
reads in-line".
From observations to domain candidates
Aggregate observations across all sources, then abstract:
- Top-level domain candidates describe a concern axis the team
reasons about: e.g.
system/ for technical reality, product/ for
product intent, customer/ for who is served, marketing/ for how
they are reached, team-practice/ for working agreements, goal/
for organisation-level direction. On a well-factored monorepo the
concern axes and the package layout may largely coincide — that is
fine. The rule guards against blindly mirroring directories, not
against agreeing with a good layout; what matters is that each
candidate is justified as a concern the team reasons about, even
when the directory of the same name happens to exist.
- No archetype templates. Do not load a pre-baked "SaaS default"
or "OSS default" set. Every candidate must be justified by a
signal observed in the resolved sources. A domain that the sources do
not motivate is a domain that does not belong.
- Second-level domain candidates under each top-level emerge from
the same observations. For
system/, package boundaries and docs
organisation are the strongest signals. For product/ and
customer/, README positioning and marketing-site content are the
strongest signals.
- Supporting structure is automatic, not a candidate. Ensure the
generated policy's member content and archive/supporting intake nodes
exist — in today's tree shape,
members/<owner>/NODE.md and
raw-context/NODE.md. State A's tree init may have already created a
member index and creator node, so extend those rather than recreating
them (add the computed owner if it differs from the creator). These are
scaffolding, not concern axes; do not put them in the user checklist as
toggles — but do compute the primary owner now
(one git log per source, seconds) and show the derived name in
the confirmation message, so the user confirms structure and owner
in one round-trip instead of being asked twice.
- ON / OFF marking rule. Mark a candidate ON when ≥ 2
independent signals support it, or one strong structural signal
(a dedicated package, repo, or docs section). Mark it
ON, weak signal when exactly one soft signal supports it (a
README phrase, a single doc) — recommended but flagged so the user
knows the evidence is thin. Mark it OFF when you considered it
(because it is a common concern axis) but found no signal in the
sources; listing OFF candidates shows the user what was looked at
and rejected, which is itself information.
The generated policy's "add a directory only when the domain shape justifies
it" rule applies here as the ≥3 leaves threshold: Phase 1 may open a domain
when ≥3 leaves are foreseeable; Phase 2's job is to land them. There is no
separate "provisional" lifecycle — a domain that ends Phase 2 with 0 leaves is
ordinary follow-up maintenance later.
User confirmation
Present the candidate skeleton as a markdown checklist in chat,
grouped by recommendation strength, with one-line evidence per item.
Example:
Based on a structural read of N source repo(s), here is the proposed
top + second-level skeleton:
system/ [recommended ON]
├─ architecture.md ← evidence: docs/architecture.md exists
├─ cli/ ← evidence: apps/cli/ + docs/cli-reference.md
└─ cloud/ ← evidence: packages/server/ + packages/web/
product/ [recommended ON, weak signal]
└─ evidence: README L1-3 frames a product positioning (single soft
signal — flagged so you know the evidence is thin)
team-practice/ [recommended ON]
└─ evidence: CONTRIBUTING.md + AGENTS.md describe team workflow
customer/ [recommended OFF]
└─ evidence: no marketing site repo, no user-facing positioning
Proposed primary owner: gandy (412 commits in the last 6 months) —
say so if someone else should own the seed.
Reply with the domains you want to open (default: all "ON"). You can
add custom domains the signals did not surface.
Wait for the user's reply. Default behavior on plain "ok" / "yes" is
"create everything marked ON, skip everything marked OFF". The user
may toggle individual items, drop the OFF defaults entirely, or add
their own top-level domains. Accept free-form replies — the
box-drawing layout above is for reading, not a form the user must
echo back. "open system and ops, skip customer, also add compliance"
is a complete answer; interpret it and restate the final set in one
line before writing.
This user-checklist step is what satisfies the policy requirement that
top-level domains require explicit human-owner approval. The main agent
proposes candidates with evidence; the human is the one who actually opens
each one. Without an explicit "yes" from the user, do not create any top-level
domain — even ones marked "recommended ON".
What to write
After the user confirms, create on the seed branch:
<tree>/NODE.md — root index, with ## Active Domains,
## Members, ## Raw Context sections enumerating what was opened.
Description paragraph drawn verbatim from the most-authoritative
source README first paragraph, or summarised in ≤30 words if that
paragraph is too long.
<tree>/members/<owner>/NODE.md — see frontmatter spec below.
Body has the owner's identity line plus a ## Recent Contributors
list (top 5 from git log --since='6 months ago').
<tree>/raw-context/NODE.md — one-paragraph statement of purpose
("intake for meeting notes, explorations, and material not yet
promoted to a durable domain").
- For each approved top-level domain: a
<tree>/<domain>/NODE.md
stub with title, owners, one-paragraph statement of what the
domain covers, and a ## Leaves section listing the approved
second-level entries (or "(none yet — Phase 2 will populate)" when
the domain is opened without a second-level skeleton).
- For each approved second-level entry: a sub-
NODE.md stub or a
placeholder .md file, again with title, owners, and a
one-paragraph charter — no leaf content yet.
<tree>/.first-tree/progress.md — the exact Phase 1 marker, checked Phase 1
line, Team id, canonical source ledger, and approved top-level list from
references/durable-progress.md. Do not add an unchecked Phase 2 item.
Frontmatter shape
Domain and leaf nodes carry the standard shape:
---
title: "<noun phrase>"
owners: [<primary owner>]
---
Member nodes carry the extended shape required by
first-tree tree verify's member validator:
---
title: "<forge handle>"
owners: [<forge handle>]
type: human
role: "Owner"
domains:
- <domain-1>
- <domain-2>
---
Notes on defaults:
- Primary owner = the most-active recent contributor across the
resolved sources, computed from
git log --since='6 months ago' --format='%aN <%aE>' | sort | uniq -c | sort -rn | head -1. Use
the forge handle when gh repo view or glab repo view resolves the email;
fall back to the %aN author name otherwise. Compute this during
Phase 1 exploration and surface it in the confirmation checklist
(see above) — the user's reply to that checklist is the owner
confirmation; do not ask a second time unless the derivation was
ambiguous (e.g. two contributors within ~10% of each other).
- Member-node
role defaults to "Owner" because seed does
not know the real role. Validator only requires non-empty; the
user adjusts later.
- Member-node
domains defaults to the list of opened
top-level domain names (one entry per opened domain). Validator
requires non-empty.
- Do not set
lastReviewed. That field marks owner-reviewed
state and is written only after a real owner review;
stamping it on a bootstrap node makes unreviewed content look
reviewed and hides it from future audits.
- Do not set
decisionLocksCode (defaults false).
PR/MR 1
- Branch:
chore/seed-phase1-structure
- Show the diff to the user before push.
- Commit message:
chore: seed initial tree structure from <N> source repo(s)
- PR/MR title:
Seed Phase 1 — initial tree structure
- PR/MR body: enumerate which top + second-level domains were opened
and which were proposed-but-skipped (one line each, with the
user's reason if given). Do not list the source PRs/MRs or commits — the
audit trail is
git log.
- Run
first-tree tree verify --tree-path <tree> locally before
opening the PR/MR. Non-zero exit blocks.
- In portable mode, repeat
tree seed --team immediately before the push and
again before PR/MR creation. Require current Admin plus the same canonical
binding repo and branch. Query the forge by the deterministic head branch
first; reuse an existing branch or PR/MR instead of duplicating it.
- Stop after the PR/MR is open. Do not start Phase 2 work until the
approved structure and durable progress record are merged.
Phase 2 — Content
Goal: populate each approved top-level domain with initial leaf
nodes drawn from a real read of the resolved source code, under a time
budget, with explicit coverage reports surfacing what was and was not
extracted.
Trigger
Any explicit request to resume Seed after PR/MR 1 may trigger recovery; it can
arrive in a new process, agent, or chat. The agent does not poll the remote,
inbox, or any other source between phases. Run the exact Phase 2 recovery
algorithm in references/durable-progress.md: fresh explicit-Team preflight,
strict bound-branch fetch, detached merged-state inspection, marker and Team
validation, entry-for-entry source-identity comparison, recorded-commit
readability checks, and source worktrees materialized at those commits.
The old transcript and any private setup cache are irrelevant. A current-message
claim or familiar domain layout cannot replace the durable checks. git ls-tree
may inspect the exact Context Tree commit, but do not use git ls-tree, git show, git grep, or similar content reads directly against a source bare clone.
Removing a temporary source read worktree after its evidence read is expected;
the proof is the materialize/read event, not a leftover checkout.
Sub-agent allocation
Phase 2 must balance parallelism against three costs: token spend,
main-agent consolidation complexity, and the user's PR/MR 2 review
burden. The allocation policy below caps each batch at 6 concurrent
sub-agents and uses queue-batched dispatch so no approved domain is
deferred.
No sub-agent dispatch available? Some runtimes give the seeding
agent no sub-agent / task-dispatch tool (and sub-agents themselves
never get one — do not design nested fan-out). Fall back to
sequential self-dispatch: the main agent works through the same
queue one unit at a time, under the same per-unit contract — subtree
scope, time budget, stopping discipline, and a coverage report per
unit. Wall-clock is slower; the outputs and the PR/MR 2 shape are
identical. Everything below that says "sub-agent" applies to a
self-dispatched unit unchanged, except the parallel-write-safety
rules, which become trivial. In sequential mode, still defer all
git commits until after the consolidation pass — name normalisation
and the soft_links resolution check happen across every unit's
output and may rewrite earlier drafts; committing per-unit inline
would force consolidation fixes into extra commits and blur the
per-unit git log boundaries that PR/MR 2 review depends on.
Concurrency cap: 6 sub-agents per batch. Empirical limit.
Beyond this, wall-clock savings flatten while token cost and
consolidation overhead grow. Adjust in a future skill version if
real usage proves the number too low or too high.
Default allocation: one sub-agent per approved top-level domain.
When the user approved N ≤ 6 top-level domains and none meet the
split criteria below, dispatch one sub-agent per top-level. The
sub-agent's scope is the whole top-level subtree; second-level
entries are its working surface, not separate units.
Split a top-level into multiple sub-agents (one per second-level
under it). Splitting is a cost decision: split when the projected
work for one sub-agent exceeds what its budget can absorb. The
structural test is only the eligibility filter — a split needs
natural seams to split along. Both must hold:
- Eligibility: the top-level has ≥ 3 second-level entries
opened in Phase 1 (fewer means no clean seams; an over-budget
2-entry domain gets a bigger budget, not a split).
- Cost: any of these indicators says one sub-agent's budget is
not enough:
- Source aggregate signal weight for this top-level ≥ 30 files
of Tier-1 meta material to read (like the 6-cap, an empirical
threshold — adjust in a future skill version if real usage
proves it wrong).
- A source repo / package is dedicated to one specific
second-level (e.g.
apps/cli/ maps naturally to
system/cli/). The mapping is then 1 source package →
1 sub-agent.
- The main agent's estimate of total work would push a single
sub-agent past the 20-minute budget. Estimate it mechanically:
~1 min per Tier-1 meta file in scope, plus ~2–3 min per open
Tier-2 question carried over from Phase 1, plus ~2 min per
expected leaf for drafting.
Each split sub-agent's authority shrinks to its assigned
second-level path. It cannot touch sibling second-levels under the
same top-level — those belong to their own sub-agents and the
authority table's "modify a sibling domain's files = forbidden" rule
applies between split siblings, not just between top-level
siblings.
Merge multiple thin top-levels into one sub-agent when each
top-level has ≤ 2 source signals (counted as distinct Tier-1 meta
hits — the files / forge fields that motivated the domain in Phase 1)
AND ≤ 1 leaf expected to be drafted by Phase 2 in this seed run
(not "ever"). Weigh the commit-boundary cost before merging: a
merged pair lands as one commit spanning two domains, which blurs
the per-domain review boundary PR/MR 2 otherwise preserves. Merge only
when both domains are thin enough that the combined commit is still
trivially reviewable; when in doubt, run the thin domain as its own
quick work unit instead. A merged sub-agent's authority covers
both assigned top-level paths in full — the
"sibling = forbidden" rule applies between work units, not inside a
merged unit's pair. Surface the pairing in the PR/MR 2 body so the user
sees why one commit spans two domains.
Batched dispatch when total work units > 6. Maintain a queue of
work units (one per top-level, plus split children, minus merged
pairs). Fan out the first 6; as any sub-agent returns its drafts +
coverage report, immediately dispatch the next from the queue.
Continue until the queue is empty. No work unit is deferred to
"Known Gaps" — every approved domain is seeded eventually, just
across multiple waves. Wall-clock for a queue of ~12 ends up roughly
2× a single batch's runtime, not a linear blow-up, because the
slowest sub-agent in batch N gates the start of batch N+1 only if
its slot is the last to free.
Record the actual allocation (splits, merges, batches) in the PR/MR 2
body so the user can see why each commit covers the subtree it does
— and so they can spot if the main agent split or merged something
they would have left alone.
Parallel write safety
Sub-agents run in parallel and may touch the filesystem simultaneously.
To keep them out of .git/index.lock contention and out of each
other's commit history, enforce two invariants:
- Sub-agents write files only. They use Read / Write / Edit
against
<tree>/<assigned-subtree>/ paths but do not call
git add, git commit, git push, or any other git-mutating
command. Their disjoint sub-paths guarantee filesystem-level safety.
- The main agent serialises every git operation. After all
sub-agents return their drafts + coverage reports, the main agent
runs the consolidation pass (below), then groups files by
sub-agent and lands one commit per sub-agent (
git add <sub-path>/... && git commit -m "...") on
chore/seed-phase2-content. This keeps domain boundaries visible
in git log without ever risking concurrent index writes.
Sub-agent contract
Each sub-agent receives:
- The path of its assigned subtree (e.g.
<tree>/system/).
- The Phase 1 stub it inherits (NODE.md text + the second-level
entries already laid out, if any).
- The list of resolved sources and their on-disk paths.
- The time budget (default 15 minutes wall-clock; the main agent
may set a lower budget for thin domains).
- The generated Context Tree Policy plus the seed-specific hard rules below.
- The leaf shape target: up to ~60 lines per leaf, structured
as
## Decision / ## Rationale / ## Constraints (matching
the normal node shape; omit a section you don't need).
Shorter is better when the signal is thin — a six-line node that
captures the decision cleanly beats a sixty-line node that buries
it; do not pad to hit a length. Consistently longer is a smell
that implementation detail is leaking in; push it back to the
source repo and keep the durable claim.
The sub-agent's authority within its assigned subtree:
| Action | Authority |
|---|
| Restructure inside its subtree (split / merge second-level, add third-level, rewrite NODE.md descriptions, adjust internal soft_links) | full |
| Add new sub-domain branches (new sub-directories) under its assigned top-level | full |
Write soft_links: entries that point into sibling subtrees (read-only cross-domain reference; target file is not modified) | full |
| Modify a sibling domain's files or a parent NODE.md | forbidden |
| Create a new top-level domain | forbidden |
Modify members/, raw-context/, or other supporting structures | forbidden |
Run any git command (add / commit / push / checkout / ...) — the main agent serialises git ops | forbidden |
Forbidden actions become entries in the sub-agent's
escalations list, returned to the main agent: "I observed N
decisions that suggest a new top-level domain security/; I did not
create it." The main agent aggregates these for the user.
Stopping discipline
Sub-agents do not chase completeness. Stop when any of these
holds, whichever comes first:
- Time budget exhausted. Wall-clock since dispatch ≥ assigned
budget (default 15 min).
- Three consecutive Read / Grep tool calls returned no new
candidate leaf. One tool call = one stop-check tick; an
exploratory
Grep that turns up nothing counts the same as a
Read of a file that has nothing tree-worthy. After three ticks
in a row, the domain's high-value veins are mined out.
- Twelve leaves drafted in this domain. Empirical cap from
observing real seed runs; not a hard architectural limit. A single
seed pass should not create a sixty-leaf domain — defer the rest
to
first-tree-write writes after the user has reviewed PR/MR 2.
Adjust in a future skill version if real usage shows the number is
consistently too low or too high.
Mandatory coverage report
Every sub-agent returns, alongside its drafted nodes, a structured
coverage report:
## Coverage in <subtree>
Read:
- <path> (full)
- <path> (sampled)
- <path> (first paragraph)
Skipped (budget / out-of-scope):
- <path> — reason
- <path> — reason
Leaves created: N (see commits <sha-range>)
Known gaps:
- <one-line description of an unread area worth flagging>
- <one-line description of an open question worth flagging>
Escalations (forbidden actions surfaced for main agent):
- <one-line description>
Use exactly three read-depth tags, so reports aggregate cleanly
across sub-agents:
(full) — the entire file was read.
(sampled) — representative sections / head reads, not the whole
file.
(first paragraph) — only the first paragraph or first H1 block.
The main agent stitches every sub-agent's coverage report into the
PR/MR 2 description so the user can see, at merge time, exactly what was
and was not covered — and decide whether to accept, manually fill, or
re-run seed with a focused prompt.
Main agent consolidation
After all sub-agents return:
-
Resolve naming inconsistencies across subtrees (different
sub-agents may have named the same cross-cutting concept
differently — normalise to one name).
-
Verify soft_links references resolve — mechanically, not by
eyeball, and with the same criteria tree verify enforces: a
valid target is either an existing .md file, or an existing
directory that contains a NODE.md. A bare test -e is not
enough — a directory without NODE.md (or a non-markdown file)
exists on disk but still fails verify. Extract every
soft_links: entry from every drafted node and check:
t="<tree>${entry}"
{ [ -f "$t" ] && case "$t" in *.md) true;; *) false;; esac; } \
|| [ -f "$t/NODE.md" ] \
|| echo "DANGLING: ${src} -> ${entry}"
Where a sub-agent linked to a target that fails this check, drop
the link and surface it as a known gap.
-
Aggregate escalations into a single section in the PR/MR 2 body
("Sub-agents flagged the following as out of their scope; user to
decide whether to act now or later").
-
Run first-tree tree verify --tree-path <tree> from a clean
checkout of chore/seed-phase2-content. Non-zero blocks.
PR/MR 2
- Branch:
chore/seed-phase2-content
- One commit per sub-agent (preserve domain boundaries for review),
plus one consolidation commit at the end.
- PR/MR title:
Seed Phase 2 — initial leaves across <N> domain(s)
- PR/MR body sections, in order:
- Coverage report — concatenated coverage reports from every
sub-agent.
- Escalations — items sub-agents flagged as out of scope.
- Known gaps — the union of all per-domain known gaps.
- Next steps — point the user at
first-tree-write for any
follow-up source-driven writes and describe any broad maintenance
gaps as focused follow-up tasks.
- Add the exact checked
Seed Phase 2 content line to
.first-tree/progress.md without changing its Team, source ledger, or
approved top-level list.
- In portable mode, repeat the Seed preflight immediately before push and
before PR/MR creation; require the same Team and binding, and reuse an
existing deterministic branch or PR/MR by head.
After PR/MR 2 opens, the seed skill is done. Subsequent writes are owned
by first-tree-write; subsequent maintenance uses focused tasks with
explicit scope.
Recovery path: PR/MR 1 merged, Phase 2 abandoned
A user may merge PR/MR 1 and then never come back for Phase 2. The tree ends up
with real structure and a durable checked Phase 1 record but zero leaves. This
is not a new re-seed condition. A later explicit continuation from any new
process, agent, or chat can complete Phase 2 only after the durable recovery
algorithm passes. A populated tree without that record remains state C and
refuses. For that unrelated path:
- Future writes go through
first-tree-write one source at a
time, exactly as they would on any other live tree.
- A team that wants to back-fill the missing leaf content can ask for
a focused maintenance pass with explicit source scopes; any actual
writes still go through
first-tree-write one source at a time.
Communicate this fallback to the user in PR/MR 1's body so the choice
is visible: "If you merge PR/MR 1 without coming back for Phase 2, the
tree is fully usable but empty; later writes go through
first-tree-write or a focused maintenance follow-up."
Hard Rules
These apply the generated Context Tree Policy to the seed-specific surface.
- Smallest correct edit. A six-line NODE.md that captures the
domain's charter beats a sixty-line one that buries it. Phase 1
stubs are short on purpose; Phase 2 leaves are short on purpose.
- No diffs, no code detail in nodes. Function signatures,
request shapes, retry constants stay in the source repo. The tree
records the durable decision and the rationale.
- No PR/MR references in node bodies. The audit trail for "which
PR/MR delivered this seed" lives in
git log. Nodes carry their
present-tense claim alone.
- No history. Nodes state current truth. Past states live in
git log. Do not preface a node with "Originally we…" or
"Update 2026-XX-XX:".
first-tree tree verify must pass before PR/MR 1 and PR/MR 2 land.
Non-zero exit blocks the commit.
- Remote failures preserve truth. Recheck live Admin and binding before
every push and PR/MR creation. After a possible mutation, inspect the
repository, deterministic branch, PR/MR-by-head, and Server binding before
reporting; never claim rollback.
- Ownership changes go through humans. Phase 1 sets
owners to
the workspace's primary owner as the default; reassignment is the
user's job, not the agent's.
- Do not invent. If a fact is not in the read signals, leave it
out. A missing node is a question; a fabricated node is a trap.
What This Skill Does NOT Do
- Run the Cloud one-click server bootstrap. When the team has no tree
(state A), seed creates and binds it with
first-tree tree init (the user's
local gh; current tree creation is GitHub-only), not the server
/initialize path — the two coexist. A GitLab source still uses glab for
source access. Seed does not write the workspace-root workspace.json; that
stays a runtime concern.
- Install GitHub automation beyond the default-branch ruleset applied after a
newly created GitHub Context Repo (validate workflows, CODEOWNERS ownership,
extra rulesets) — out of scope. If the team wants those, they are separate
follow-on workflows after seed lands.
- Touch
AGENTS.md / CLAUDE.md managed blocks at the tree root —
those are owned by the CLI runtime.
- Generate content beyond what the signals support. If a candidate
domain has weak evidence and the user does not opt it in, leave it
out.
- Start a second seed on the same tree. Once the tree has domain structure,
only the verified durable Phase 2 continuation remains in scope;
other work hands off to
first-tree-write or a focused maintenance task.
References
The generated AGENTS.md / CLAUDE.md briefing carries the shared Context
Tree Policy. Read references/durable-progress.md only when writing Phase 1
progress or evaluating/resuming Phase 2; it is the single detailed recovery
contract.