# Bootstrap Standard Template

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

Standard tier is a strict superset of Fast tier. Execute all Fast-tier steps first, then the Standard-specific steps below.

Track actual newly created relative files in one `BOOTSTRAP_FILES` array across
all inherited steps. Append each file when created; preserve the array across
repeated steps. Never append an existing owner file or a directory.

## Private Contract Preparation

When `PATH_TYPE = private`, resolve `CONFIRMED_ARCHETYPE` with
`private-contract.md` and execute its Scaffold section **before** the inherited
Fast steps. Preserve its rendered metadata, manifests, source, README and CI.
Add its reported created files to `BOOTSTRAP_FILES`. Skip the four public
archetype sections below, then continue at Step 3a, S99 and the lock/quality-policy
steps. Populate command expectations from the selected contract as described
in `private-contract.md`.

## Step 1–7: Execute Fast Tier

Read and execute `skills/bootstrap/fast-template.md` Steps 1–7 in full. Do not skip any step. The Fast commit (`chore: bootstrap (fast)`) is NOT made — Fast steps produce files only; the single commit happens at Standard Step 7 below.

**Exception to fast-template Step 6:** Do not run the git commit in fast-template Step 6. All files (Fast + Standard) are committed together at Standard Step 7 below.

**Exception to fast-template Step 5 (bootstrap.lock):** Do not write `.orchestrator/bootstrap.lock` yet. The lock is written with `tier: standard` at Standard Step 6.

## Archetype Detection

Before executing the stack-specific steps, resolve the final archetype:

```
ARCHETYPE = CONFIRMED_ARCHETYPE  # set by SKILL.md from intensity-heuristic or user selection
```

For `PATH_TYPE = public`, valid values are `static-html` | `node-minimal` | `nextjs-minimal` | `python-uv`. Private IDs come only from the validated contract.

On the public path only, if `ARCHETYPE` is `null` or unset, default to `node-minimal`. A private missing/invalid selection aborts before scaffolding; it never reaches the public sections.

The sections below are conditional on `ARCHETYPE`. Execute only the section that matches.

---

## Archetype: `static-html`

No build tooling needed. Create minimal structure for a static HTML project.

### Step S1: No manifest file

Static HTML has no `package.json` or `pyproject.toml`. Skip to Step S2.

### Step S2: No tsconfig

Static HTML has no TypeScript. Skip to Step S3.

### Step S3: No ESLint / Prettier

No linter for plain HTML/CSS/JS projects. Skip to Step S4.

### Step S4: No test framework

No automated test for static HTML. Skip to Step S5.

### Step S5: Expanded README.md

Overwrite the stub README.md created by the Fast step:

```markdown
# <REPO_NAME>

<One-sentence description.>

## Usage

Open `index.html` in a browser or serve with any static file server:

```bash
npx serve .
```

## Development

Edit `index.html`, `style.css`, and `script.js` directly. No build step required.
```

### Step S6: .editorconfig

Write `.editorconfig`:

```ini
root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.html]
indent_size = 2

[*.css]
indent_size = 2

[*.md]
trim_trailing_whitespace = false
```

---

## Archetype: `node-minimal`

Node/TypeScript project without a framework. Creates `package.json`, `tsconfig.json`, ESLint, Prettier, Vitest, and one sanity test.

### Step S1: package.json

Write `package.json`:

```json
{
  "name": "<REPO_NAME>",
  "version": "0.1.0",
  "description": "<One-sentence description from user prompt>",
  "type": "module",
  "scripts": {
    "build": "tsc --noEmit",
    "lint": "eslint .",
    "format": "prettier --write .",
    "test": "vitest run"
  },
  "devDependencies": {
    "@eslint/js": "^9.0.0",
    "eslint": "^9.0.0",
    "prettier": "^3.0.0",
    "typescript": "^5.0.0",
    "vitest": "^2.0.0"
  }
}
```

### Step S2: tsconfig.json

Write `tsconfig.json`:

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true,
    "noEmit": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "outDir": "dist"
  },
  "include": ["src/**/*", "tests/**/*"],
  "exclude": ["node_modules", "dist"]
}
```

### Step S3: ESLint v9 flat config + Prettier

Write `eslint.config.mjs`:

```js
import js from "@eslint/js";

/** @type {import("eslint").Linter.Config[]} */
export default [
  js.configs.recommended,
  {
    rules: {
      "no-unused-vars": "warn",
      "no-console": "off",
    },
  },
  {
    ignores: ["dist/", "node_modules/", "coverage/"],
  },
];
```

Write `.prettierrc`:

```json
{
  "semi": true,
  "singleQuote": false,
  "tabWidth": 2,
  "trailingComma": "es5",
  "printWidth": 100
}
```

### Step S4: Vitest sanity test

Create directory and test file:

```bash
mkdir -p "$REPO_ROOT/tests"
```

Write `tests/sanity.test.ts`:

```ts
import { describe, it, expect } from "vitest";

describe("sanity", () => {
  it("true is true", () => {
    expect(true).toBe(true);
  });
});
```

Also create the source directory so TypeScript is happy:

```bash
mkdir -p "$REPO_ROOT/src"
```

Write `src/index.ts` (minimal entry point):

```ts
// Entry point — replace with your implementation.
export {};
```

### Step S5: Expanded README.md

Overwrite the stub README.md:

```markdown
# <REPO_NAME>

<One-sentence description.>

## Installation

```bash
pnpm install
```

## Usage

```bash
pnpm build
```

## Development

| Command | Description |
|---------|-------------|
| `pnpm build` | Type-check with TypeScript |
| `pnpm lint` | Lint with ESLint |
| `pnpm format` | Format with Prettier |
| `pnpm test` | Run tests with Vitest |
```

### Step S6: .editorconfig

Write `.editorconfig`:

```ini
root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false
```

---

## Archetype: `nextjs-minimal`

Bare Next.js setup. Creates `package.json`, `tsconfig.json`, ESLint, Prettier, Vitest, and one sanity test.

### Step S1: package.json

Write `package.json`:

```json
{
  "name": "<REPO_NAME>",
  "version": "0.1.0",
  "description": "<One-sentence description from user prompt>",
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "eslint .",
    "format": "prettier --write .",
    "test": "vitest run"
  },
  "dependencies": {
    "next": "^16.4.2",
    "react": "^19.0.0",
    "react-dom": "^19.0.0"
  },
  "devDependencies": {
    "@eslint/js": "^9.0.0",
    "@types/node": "^22.0.0",
    "@types/react": "^19.0.0",
    "@types/react-dom": "^19.0.0",
    "eslint": "^9.0.0",
    "prettier": "^3.0.0",
    "typescript": "^5.0.0",
    "vitest": "^2.0.0"
  }
}
```

### Step S2: tsconfig.json

Write `tsconfig.json`:

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "plugins": [{ "name": "next" }],
    "paths": { "@/*": ["./src/*"] }
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
  "exclude": ["node_modules"]
}
```

### Step S3: ESLint v9 flat config + Prettier

Write `eslint.config.mjs`:

```js
import js from "@eslint/js";

/** @type {import("eslint").Linter.Config[]} */
export default [
  js.configs.recommended,
  {
    rules: {
      "no-unused-vars": "warn",
      "no-console": "off",
    },
  },
  {
    ignores: ["dist/", "node_modules/", ".next/", "coverage/"],
  },
];
```

Write `.prettierrc`:

```json
{
  "semi": true,
  "singleQuote": false,
  "tabWidth": 2,
  "trailingComma": "es5",
  "printWidth": 100
}
```

### Step S4: Vitest sanity test

Create directory and test file:

```bash
mkdir -p "$REPO_ROOT/tests"
```

Write `tests/sanity.test.ts`:

```ts
import { describe, it, expect } from "vitest";

describe("sanity", () => {
  it("true is true", () => {
    expect(true).toBe(true);
  });
});
```

Create minimal Next.js app entry:

```bash
mkdir -p "$REPO_ROOT/src/app"
```

Write `src/app/page.tsx`:

```tsx
export default function Home() {
  return <main><h1><REPO_NAME></h1></main>;
}
```

Write `src/app/layout.tsx`:

```tsx
export const metadata = { title: "<REPO_NAME>" };

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}
```

Write the three error surfaces (projects-baseline rule `frontend.md` § Error Pages). `retry()` needs Next ≥ 16.3; never `reset()`, never `error.message`, and no `pages/500.tsx` (next to `app/` it makes the navigation hooks nullable project-wide).

Write `src/app/error.tsx`:

```tsx
"use client";

import Link from "next/link";
import { useEffect } from "react";

export default function ErrorBoundary({
  error,
  retry,
}: {
  error: Error & { digest?: string };
  retry: () => void;
}) {
  useEffect(() => {
    // Report it: swap in your error tracker here, e.g. Sentry.captureException(error).
    console.error(error);
  }, [error]);

  return (
    <main style={{ maxWidth: "28rem", margin: "0 auto", padding: "4rem 1.25rem", textAlign: "center" }}>
      <p style={{ fontWeight: 600 }}><REPO_NAME></p>
      <h1>Something went wrong</h1>
      <p>This part of the page could not be loaded. Trying again usually helps.</p>
      <p style={{ display: "flex", flexWrap: "wrap", gap: "0.75rem", justifyContent: "center" }}>
        <button type="button" onClick={retry} style={{ minHeight: 44, padding: "0 1.5rem" }}>
          Try again
        </button>
        <Link href="/" style={{ display: "inline-flex", alignItems: "center", minHeight: 44 }}>
          Back to the home page
        </Link>
      </p>
      {error.digest ? <p style={{ fontSize: "0.75rem" }}>Error reference: {error.digest}</p> : null}
    </main>
  );
}
```

Write `src/app/global-error.tsx` (it replaces the root layout, so own `<html>`/`<body>` and inline styles):

```tsx
"use client";

import { useEffect } from "react";

export default function GlobalError({
  error,
  retry,
}: {
  error: Error & { digest?: string };
  retry: () => void;
}) {
  useEffect(() => {
    // Report it: swap in your error tracker here, e.g. Sentry.captureException(error).
    console.error(error);
  }, [error]);

  return (
    <html lang="en" style={{ colorScheme: "light dark" }}>
      <body style={{ margin: 0, background: "Canvas", color: "CanvasText", fontFamily: "system-ui, sans-serif" }}>
        <main style={{ minHeight: "100vh", display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: "1rem", padding: "2rem 1.25rem", textAlign: "center" }}>
          <p style={{ margin: 0, fontWeight: 700 }}><REPO_NAME></p>
          <h1 style={{ margin: 0, fontSize: "1.5rem" }}>Something went wrong</h1>
          <p style={{ margin: 0 }}>The page could not be loaded. Trying again usually helps.</p>
          <button type="button" onClick={retry} style={{ minHeight: 44, padding: "0 1.5rem" }}>
            Try again
          </button>
          {/* A hard navigation on purpose: a full page load rebuilds the broken state. */}
          {/* eslint-disable-next-line @next/next/no-html-link-for-pages -- see the line above */}
          <a href="/" style={{ color: "CanvasText" }}>Back to the home page</a>
          {error.digest ? <p style={{ margin: 0, fontSize: "0.75rem" }}>Error reference: {error.digest}</p> : null}
        </main>
      </body>
    </html>
  );
}
```

Write `src/app/not-found.tsx`:

```tsx
import Link from "next/link";

export default function NotFound() {
  return (
    <main style={{ maxWidth: "28rem", margin: "0 auto", padding: "4rem 1.25rem", textAlign: "center" }}>
      <p style={{ fontWeight: 600 }}><REPO_NAME></p>
      <h1>Page not found</h1>
      <p>This address does not exist (any more). The home page may help.</p>
      <Link href="/" style={{ display: "inline-flex", alignItems: "center", minHeight: 44 }}>
        Back to the home page
      </Link>
    </main>
  );
}
```

### Step S5: Expanded README.md

Overwrite the stub README.md:

```markdown
# <REPO_NAME>

<One-sentence description.>

## Installation

```bash
pnpm install
```

## Usage

```bash
pnpm dev      # Start development server at http://localhost:3000
pnpm build    # Production build
pnpm start    # Start production server
```

## Development

| Command | Description |
|---------|-------------|
| `pnpm dev` | Start dev server |
| `pnpm lint` | Lint with ESLint |
| `pnpm format` | Format with Prettier |
| `pnpm test` | Run tests with Vitest |
```

### Step S6: .editorconfig

Write `.editorconfig`:

```ini
root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false
```

---

## Archetype: `python-uv`

Python project using `uv` as the package manager and `pytest` for testing.

### Step S1: pyproject.toml

Write `pyproject.toml`:

```toml
[project]
name = "<REPO_NAME>"
version = "0.1.0"
description = "<One-sentence description from user prompt>"
readme = "README.md"
requires-python = ">=3.12"
dependencies = []

[project.optional-dependencies]
dev = [
  "pytest>=8.0",
  "ruff>=0.4",
]

[tool.ruff]
line-length = 100
target-version = "py312"

[tool.ruff.lint]
select = ["E", "F", "I"]

[tool.pytest.ini_options]
testpaths = ["tests"]
```

### Step S2: No tsconfig

Python project. Skip.

### Step S3: Ruff (replaces ESLint/Prettier for Python)

Ruff is already configured in `pyproject.toml` above. No separate config file needed. Note: Ruff is the Python-ecosystem equivalent of ESLint + Prettier for this archetype.

### Step S4: pytest sanity test

```bash
mkdir -p "$REPO_ROOT/tests"
touch "$REPO_ROOT/tests/__init__.py"
mkdir -p "$REPO_ROOT/src"
touch "$REPO_ROOT/src/__init__.py"
```

Write `tests/test_sanity.py`:

```python
def test_sanity() -> None:
    assert True
```

### Step S5: Expanded README.md

Overwrite the stub README.md:

```markdown
# <REPO_NAME>

<One-sentence description.>

## Installation

```bash
uv sync
```

## Usage

```bash
uv run python src/main.py
```

## Development

| Command | Description |
|---------|-------------|
| `uv sync` | Install dependencies |
| `uv run ruff check .` | Lint with Ruff |
| `uv run ruff format .` | Format with Ruff |
| `uv run pytest` | Run tests with pytest |
```

### Step S6: .editorconfig

Write `.editorconfig`:

```ini
root = true

[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false

[*.yml]
indent_size = 2

[*.json]
indent_size = 2
```

---

<!-- @include _shared-template.md#parallel-sessions-rule -->
## Step 3a: Install Parallel-Sessions Rule

Canonical implementation in [`_shared-template.md#parallel-sessions-rule`](_shared-template.md).

Execute the shared partial's `syncBootstrapRules` action. It supplies private
required basenames to `scripts/lib/rules-sync.mjs`, preserving its manifest,
pre-write validation and local-override checks. The partial appends actual newly
created rule paths, and creates `.claude/loop.md` only when missing. See the
shared partial for the executable shell command. Issues #155, #633, #1060.

Note: Runs before S99. S99 no longer fetches `parallel-sessions.md` from the baseline — that entry
was removed from the S99 manifest in #1060. The baseline copy carries no provenance header, so
letting it win would make the next `--sync-rules` classify the target as a repo-private override and
preserve it permanently, i.e. the plugin could never update that rule again. `rules/` is the single
source for it.

## Step 3b: Initialize .orchestrator/metrics/ (#185)

Create the metrics directory and an empty learnings file so the `/evolve` skill and discovery probes have a write target from day one:

```bash
mkdir -p "$REPO_ROOT/.orchestrator/metrics"
if [[ ! -e "$REPO_ROOT/.orchestrator/metrics/learnings.jsonl" && ! -L "$REPO_ROOT/.orchestrator/metrics/learnings.jsonl" ]]; then
  : > "$REPO_ROOT/.orchestrator/metrics/learnings.jsonl"
  BOOTSTRAP_FILES+=(.orchestrator/metrics/learnings.jsonl)
fi
```

**Idempotent.** Re-running bootstrap does not overwrite an existing file.

**Gitignore guidance:** Do NOT add `.orchestrator/metrics/*.jsonl` to `.gitignore`. The files are intentionally visible in version control — they carry project learnings and session history that future contributors benefit from.

The store's LOCK artifacts are the opposite case and DO get ignored (#1487), together with the plugin's other runtime locks and state under `.orchestrator/` (#1495): Fast Step 3, inherited above, already appended `.orchestrator/metrics/*.jsonl.lock*`, `.file.lock.*`, `.orchestrator/session.lock` and the rest of the list via [`_shared-template.md#store-lock-ignore`](_shared-template.md) — the one canonical snippet, also run by the `--upgrade` and `--refresh-lock` flows. Nothing to repeat here.

<!-- @include _shared-template.md#baseline-fetch -->
## Step S99: (Optional) Fetch Canonical Rules + Agents from Baseline

Canonical implementation in [`_shared-template.md#baseline-fetch`](_shared-template.md).

PRIVATE: applies the selected local contract rule union without network access, preserving existing files and excluding every plugin-owned basename. An invalid contract aborts.

PUBLIC OPT-IN: only fires when `baseline-ref` is in Session Config, `GITLAB_TOKEN` is set, and
`scripts/lib/fetch-baseline.mjs` exists. Fetches `.claude/rules/*.md` from the baseline GitLab
project (default project 52) and writes `.claude/.baseline-fetch.lock`. Does NOT abort on failure.
The private rule manifest comes from the selected contract. Every plugin-owned
basename is excluded from either delivery path. See the shared partial.

---

<!-- @include _shared-template.md#vault-registration -->
## Step 5.5: Vault-Registration Prompt (Product Repos) (#190)

Canonical implementation in [`_shared-template.md#vault-registration`](_shared-template.md).

Runs `scripts/lib/product-repo-detect.mjs` to detect product-repo signals. When detected and no
`vault:` key exists in Session Config, prompts the user (Y/n) to register a vault entry.
Idempotent via `hasVaultConfig`. See shared partial for full detection script and examples.

---

## Step 6: Write bootstrap.lock (Standard)

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: standard
archetype: <CONFIRMED_ARCHETYPE>
timestamp: <current ISO 8601 UTC — e.g., 2026-04-16T09:30:00Z>
source: <claude-init | plugin-template | projects-baseline>
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` using the same logic as fast-template Step 5:
- `projects-baseline` if `PATH_TYPE = private` and baseline scripts were used
- `claude-init` if `claude init` ran successfully
- `plugin-template` otherwise

<!-- @include _shared-template.md#quality-gate-policy -->
## Step 6.5: Quality-Gate Policy File (#183)

Canonical implementation in [`_shared-template.md#quality-gate-policy`](_shared-template.md).

Write `.orchestrator/policy/quality-gates.json` from exact private contract gates,
or package-manager defaults on the public path. Skip existing owner policy.
See shared partial for full shell command. Issue #183.

<!-- @include _shared-template.md#state-md-scaffold -->
## Step 6.6: STATE.md Scaffold (#184)

Canonical implementation in [`_shared-template.md#state-md-scaffold`](_shared-template.md).

Scaffold `.claude/STATE.md` (idempotent: skip if it already exists). <!-- path-check: example -->
Use `skills/bootstrap/STATE.md.template`.
On Codex CLI / Cursor IDE, substitute `.codex/` or `.cursor/` for `.claude/`.
See shared partial for full shell command. Issue #184.

<!-- @include _shared-template.md#agents-scaffold -->
## Step 6.7: .claude/agents/ Scaffold (#189)

Canonical implementation in [`_shared-template.md#agents-scaffold`](_shared-template.md).

Copy 3 opinionated agent templates (`project-discovery`, `project-code-review`,
`project-quality-gate`) into `$REPO_ROOT/.claude/agents/`. Idempotent: skip existing files.
See shared partial for full shell command. Issue #189.

## Step 7: 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 (standard)"
```

The commit message is fixed — do not vary it.

## Step 8: Report Created Files

After the commit succeeds, output a concise summary of all files created (Fast + Standard). Include the archetype in the header line:

```
Bootstrap (standard, <archetype>) complete. Created:
  CLAUDE.md (or AGENTS.md)             — Session Config (7 mandatory fields, validated)
  .gitignore                            — stack-appropriate rules
  README.md                             — expanded with Installation, Usage, Dev
  .editorconfig                         — consistent editor settings
  <manifest file>                       — e.g., package.json / pyproject.toml
  <tsconfig.json>                       — if JS/TS archetype
  <eslint.config.mjs + .prettierrc>    — if JS/TS archetype
  <tests/sanity.test.ts or equiv>       — sanity test
  .claude/rules/parallel-sessions.md   — vendored PSA rule (issue #155)
  .claude/STATE.md                      — idle placeholder (issue #184)
  .orchestrator/bootstrap.lock          — version: 1, tier: standard
  .orchestrator/policy/quality-gates.json — test/typecheck/lint commands (issue #183)
Committed: "chore: bootstrap (standard)"
```

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