# Bootstrap Fast Template

> Scaffold instructions for the Fast tier.
> Called from `skills/bootstrap/SKILL.md` Phase 3 when `CONFIRMED_TIER = fast`.

## What Fast Tier Creates

| File | Purpose |
|------|---------|
| `CLAUDE.md` (or `AGENTS.md` on Codex) | Project instruction file with `## Session Config` |
| `.gitignore` | Platform-appropriate minimal ignore rules |
| `README.md` | One-line stub with project name and description |
| `.orchestrator/bootstrap.lock` | Gate marker — committed to git |

Intentionally absent: `package.json`, frameworks, tests, CI config. The feature that follows brings its own stack.

Maintain the inherited `BOOTSTRAP_FILES` array and append every newly created
relative file at creation. Preserve existing owner files and never reset this
array when Fast is inherited by Standard or Deep.

## Step 1: Ensure Git Repo is Initialized

```bash
cd "$REPO_ROOT"
if [[ ! -d ".git" ]]; then
  git init
  echo "Git repo initialized."
fi
```

## Step 2: Generate CLAUDE.md (or AGENTS.md)

**If `PATH_TYPE = public`:**

Defer entirely to `skills/bootstrap/public-fallback.md` — "Public Path — Fast Tier" section. That file is the single source of truth for Public-path CLAUDE.md generation (claude init path for Claude Code; `_minimal` template synthesis for Codex/Cursor; Session Config injection). Do not duplicate its logic here.

**`claude init` overwrite guard:** `public-fallback.md` Fast Tier runs `claude init` only when
`CLAUDE.md` does not already exist (`[[ ! -f "$REPO_ROOT/CLAUDE.md" ]]`). This prevents
overwriting project-specific customisations on re-runs (issue #108).

After `public-fallback.md` completes CLAUDE.md generation, continue to Step 2b to verify the Session Config block.

**If `PATH_TYPE = private`:**

For Standard/Deep inheritance, `private-contract.md` already rendered the
instruction file and stack into the repo. Preserve those files and continue
to Step 2b; use its quality-gate command mapping for missing Session Config
fields. Preserve the rendered README and `.gitignore` in subsequent Fast steps.
For a standalone Fast tier, no archetype is selected: use `public-fallback.md`'s
minimal Fast instruction-file generation and report `source: plugin-template`.

**Step 2b: Verify Session Config block.** After writing or updating CLAUDE.md, check for the sentinel string `## Session Config`:

```bash
if grep -q "^## Session Config" CLAUDE.md; then
  echo "Session Config block confirmed."
else
  # Sentinel absent — claude init did NOT populate the file (or wrote minimal content).
  # Fall back to plugin-template generation: append canonical Harte-Regeln block
  # BEFORE the Session Config block (pattern: the templates/_shared/... cp steps
  # in Step 3a below).
  if ! grep -q "^## Harte Regeln" CLAUDE.md 2>/dev/null; then
    cat "$PLUGIN_ROOT/templates/_shared/harte-regeln.md" >> CLAUDE.md
  fi
  # Fall back to plugin-template generation: append canonical Session Config block
  # (issue #182: 7 mandatory fields enforced by scripts/lib/config-schema.mjs).
  cat >> CLAUDE.md <<'EOF'

## Session Config

project-name: <PROJECT_NAME>
vcs: <VCS>
persistence: true
enforcement: warn   # strict | warn | off
waves: 5
agents-per-wave: 6
test-command: <detect per package-manager>
typecheck-command: <detect>
lint-command: <detect>
recent-commits: 20
stale-branch-days: 7
skill-evolution:
  autonomy: off            # off | advisory | autonomous-gated — opt-in self-evolution (default off)
EOF
fi
```

If `## Session Config` is present, confirm the 7 mandatory fields (per issue #182, enforced by `scripts/lib/config-schema.mjs`) plus `project-name` and `vcs` are present. Mandatory: `test-command`, `typecheck-command`, `lint-command`, `agents-per-wave`, `waves`, `persistence`, `enforcement`. If any are missing, append them.

**Config file selection by platform:**
- Claude Code → `CLAUDE.md`
- Codex CLI → `AGENTS.md`
- Cursor IDE → `CLAUDE.md`

**Step 2c: CLAUDE.md budget lint.** After Step 2b confirms/repairs the Session Config block, run the raw-file-property lint against the freshly written instruction file:

```bash
node "$PLUGIN_ROOT/scripts/lib/claude-md-budget-lint.mjs" --repo-root "$REPO_ROOT" --require-provenance --mode warn --json
```

`--mode warn` is deliberate at Anlage-time — the lint informs, the operator decides; it never blocks the scaffold. Interpret the JSON `violations[]`:
- `max-lines` — the instruction file is already over the lean-root ceiling. The ceiling's SSOT is the exported constant `DEFAULT_MAX_LINES` in `scripts/lib/claude-md-budget-lint.mjs` (read it, do not memorise it; `= 80` as of 2026-08-03), overridable per run with `--max-lines`. The budget counts **non-exempt effective lines**, not the raw `wc -l`: the runtime-critical `## Session Config` block is excluded, because it is machine-parsed configuration rather than trimmable prose and applying a raw-line ceiling to it was structurally unreachable (#959 — the ceiling was re-derived in these units at the same time). Recommend trimming to pointers per the lean-root convention (delegate detail to `README.md` / `.orchestrator/steering/` / `.claude/rules/*.md` — see this plugin's own `CLAUDE.md` for a worked example of the pointer pattern; it **passes** this lint, 61 non-exempt of 209 raw lines against the ceiling of 80, measured 2026-08-03 at HEAD `730ee9d` — re-run the command above rather than trusting that number).
- `max-line-chars` — a single line exceeds the char ceiling (400 default); surface the line number for a quick manual wrap.
- `provenance-header` — line 1 lacks a `<!-- source: ...` attribution. On the `claude init` path (Public Fast Tier, Claude Code) this is a WARN only, never a hard failure — `claude init` output is not plugin-authored and has no reason to carry the plugin's provenance convention.

Report any violations to the user as part of the bootstrap summary; do not block or retry on them.

## Step 3: Generate .gitignore

Detect the platform from existing files in the repo root (best-effort, repo may be empty):

```bash
# Detection order — first match wins
if ls *.py pyproject.toml setup.py 2>/dev/null | head -1 | grep -q .; then STACK="python"
elif ls *.ts *.js package.json 2>/dev/null | head -1 | grep -q .; then STACK="node"
else STACK="generic"; fi
```

If absent, write `.gitignore` with the appropriate content and append `.gitignore`
to `BOOTSTRAP_FILES`. Preserve an existing file.

**Generic (no stack detected):**
```gitignore
# OS
.DS_Store
Thumbs.db

# Editor
.vscode/
.idea/
*.swp
*.swo

# Logs
*.log

# Environment
.env
.env.local
.env.*.local

# Session Orchestrator state (platform-specific, not committed)
.claude/
.codex/
.cursor/
```

**Node/TypeScript (append to generic):**
```gitignore
# Node
node_modules/
dist/
build/
.next/
coverage/
*.tsbuildinfo
```

**Python (append to generic):**
```gitignore
# Python
__pycache__/
*.py[cod]
*.egg-info/
.venv/
dist/
build/
.pytest_cache/
.mypy_cache/
.ruff_cache/
```

Note: `.orchestrator/` is NOT gitignored — `bootstrap.lock` must be committed. Only the platform state dirs (`.claude/`, `.codex/`, `.cursor/`) are excluded.

## Step 3a: Install Canonical Rules

Vendor the canonical always-on rules from the plugin's `rules/` library into `$REPO_ROOT/.claude/rules/`. `rules/` is the single source of truth for every distributable rule — never `cp` a rule file from anywhere else.

`syncBootstrapRules` calls the canonical `scripts/lib/rules-sync.mjs` writer.
Standalone Fast uses ordinary plugin rules without private archetype selection;
inherited Fast passes the confirmed contract ID and records all created paths.

Idempotency is handled by the writer itself:
- Missing → create
- Exists, plugin-owned (first line is the `<!-- source: session-orchestrator plugin ... -->` header) and byte-identical → skip silently
- Exists, plugin-owned and stale → overwrite (the plugin copy is canonical)
- Exists WITHOUT that header → preserved untouched (a repo-private rule the operator authored)

Shell:
```bash
mkdir -p "$REPO_ROOT/.claude"
export PLUGIN_ROOT REPO_ROOT CONFIRMED_ARCHETYPE
RULES_RESULT=$(node --input-type=module <<'NODE'
import { pathToFileURL } from 'node:url';
const { syncBootstrapRules } = await import(pathToFileURL(`${process.env.PLUGIN_ROOT}/scripts/lib/baseline-archetypes.mjs`));
const result = await syncBootstrapRules({ repoRoot: process.env.REPO_ROOT,
  archetype: process.env.CONFIRMED_ARCHETYPE || undefined, minimal: !process.env.CONFIRMED_ARCHETYPE });
process.stdout.write(`${JSON.stringify(result)}\n`);
if (result.status === 'error') process.exitCode = 2;
NODE
) || exit 2
printf '%s\n' "$RULES_RESULT"
while IFS= read -r _file; do BOOTSTRAP_FILES+=("$_file"); done \
  < <(printf '%s\n' "$RULES_RESULT" | jq -r '.created[]')
if [[ ! -e "$REPO_ROOT/.claude/loop.md" && ! -L "$REPO_ROOT/.claude/loop.md" ]]; then
  cp "$PLUGIN_ROOT/templates/_shared/loop.md" "$REPO_ROOT/.claude/loop.md"
  BOOTSTRAP_FILES+=(.claude/loop.md)
fi
```

The command prints a JSON report and exits non-zero on any error. At standalone
Fast tier the lock and selected ID are absent, so scoped rules are skipped.
Inherited Fast delivers private required targets through the same writer.

Surface `errors[]` and `sanitizer[]` to the operator. `sanitizer[]` (issue #1098) carries `{file, line, kind, text}` records for citations that read fine inside the plugin repo and dangle once vendored (`repo-local-path`, `unresolvable-see-also`); the CLI also prints each to stderr as `rules-sync: sanitizer <kind> <file>:<line> — <text>`. **Report it, do not act on it automatically** — it never rewrites content and never changes the exit code, so a human decides whether the citation is a leak.

Why: PSA-003 destructive-command safeguards require every consumer repo to carry the parallel-sessions rule. See issue #155. The `loop.md` vendor gives bare `/loop` a repo-aware maintenance prompt (issue #633 Hebel 3).

Why one writer (issue #1060): a literal `cp` from a second source directory bypasses the pre-write validator AND lands a file carrying no provenance header. On the next `--sync-rules` a headerless file is classified as a repo-private override and preserved forever, so the plugin can never update it again — and whichever rival copy is smaller silently wins.

## Step 4: Generate README.md

If absent, write the stub below and append `README.md` to `BOOTSTRAP_FILES`.
Preserve an existing README.

```markdown
# <REPO_NAME>

<One-sentence description — same as used in CLAUDE.md.>
```

Keep it minimal. One heading, one sentence. The feature that follows will expand it.

## Step 5: Create .orchestrator Directory and bootstrap.lock

```bash
mkdir -p "$REPO_ROOT/.orchestrator"
```

Write `.orchestrator/bootstrap.lock` **atomically** (mktemp + mv prevents a corrupt lock if the
process is interrupted mid-write):

```bash
_LOCK_CREATED=false
[[ -e "$REPO_ROOT/.orchestrator/bootstrap.lock" || -L "$REPO_ROOT/.orchestrator/bootstrap.lock" ]] || _LOCK_CREATED=true
_LOCK_TMP=$(mktemp "$REPO_ROOT/.orchestrator/bootstrap.lock.XXXXXX")
cat > "$_LOCK_TMP" << LOCK
# .orchestrator/bootstrap.lock
version: 1
tier: fast
archetype: null
timestamp: <current ISO 8601 UTC — e.g., 2026-04-16T09:30:00Z>
source: <claude-init | plugin-template>
plugin-version: <session-orchestrator plugin version — read from $PLUGIN_ROOT/package.json .version field>
bootstrapped-at: <current ISO 8601 UTC — same value as timestamp; distinct field for age-validation probe>
LOCK
mv "$_LOCK_TMP" "$REPO_ROOT/.orchestrator/bootstrap.lock"
if [[ "$_LOCK_CREATED" = true ]]; then BOOTSTRAP_FILES+=(.orchestrator/bootstrap.lock); fi
```

Set `source`:
- `claude-init` if CLAUDE.md contained `## Session Config` before Step 2b's sentinel check (i.e., `claude init` populated it)
- `plugin-template` if the sentinel was absent and the fallback path wrote the block

## Step 6: Initial Git Commit

Stage all created files and commit:

```bash
cd "$REPO_ROOT"
# Add only the files bootstrap created — no sweeping -u/-A to avoid catching pre-existing files
for _f in ${BOOTSTRAP_FILES[@]+"${BOOTSTRAP_FILES[@]}"}; do
  [[ -f "$_f" && ! -L "$_f" ]] && git add -- "$_f"
done
git commit -m "chore: bootstrap (fast)"
```

The commit message is fixed — do not vary it. It is the artifact that documents bootstrap provenance in `git log`.

## Step 7: Report Created Files

After the commit succeeds, output a concise summary:

```
Bootstrap (fast) complete. Created:
  CLAUDE.md (or AGENTS.md)              — Session Config with project-name, vcs
  .gitignore                            — <stack>-appropriate minimal rules
  README.md                             — one-line stub
  .claude/rules/parallel-sessions.md   — vendored PSA rule (issue #155)
  .orchestrator/bootstrap.lock          — version: 1, tier: fast
Committed: "chore: bootstrap (fast)"
```

Then return control to `SKILL.md` Phase 5.
