From 30dd317714278a21b8ece56f31d475c9c90c4fe3 Mon Sep 17 00:00:00 2001 From: Thomas Luizon Rodrigues Gregorio Date: Fri, 10 Jul 2026 13:32:49 -0300 Subject: [PATCH] chore(harness): mark Stage 8 (repo-clean install mode) complete in the plan Stage 8 of the harness-upgrade plan landed in agentic-dev-workflow PR #10 (repo-clean / global-only install mode: an out-of-repo store under ~/.claude keyed by git root, dual-target FACT/RULE injectors, DEFAULT --- .claude/research/harness-upgrade-plan.md | 65 +++++++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/.claude/research/harness-upgrade-plan.md b/.claude/research/harness-upgrade-plan.md index 4d2b6359f..90e429d6c 100644 --- a/.claude/research/harness-upgrade-plan.md +++ b/.claude/research/harness-upgrade-plan.md @@ -216,4 +216,67 @@ This table is exactly the decoding logic `/setup-harness` applies in phase 4: - [x] **7j — Two-layer config (plan section A).** ✅ **Landed 2026-07-10 — agentic-dev-workflow PR #8** (`feature/stage7j-two-layer-config`, base main, MERGED). Section A's global/project config layering — specced in the grilled decisions but **never scoped into the 7a–7i checklist** (a real build gap, surfaced when the "one PC, same conventions on every project" use case came up; it had been silently absent while the plan read "complete"). New **`core/setup/config.mjs`** — the resolver, mirroring the hook-policy layering: `resolveConfig` = global (`~/.claude/workflow.config.yaml`) merged with the nearest project `workflow.config.yaml`, **project wins**; `splitConfig` partitions a full config into `{ global, project }` **losslessly** (tracker host/driver → global, repo/labels → project); CLI `resolve`/`split`. **`generate.mjs`** — `planGlobalArtifacts` writes the machine-wide layer (global `workflow.config.yaml` + shared `hooks.policy.json` + tool-defaults rule, no `repos[]`/`CLAUDE.md`/lint); `planArtifacts` gains `configScope:"project"` for the lean project slice (default `"full"` unchanged). **`bootstrap.mjs`** — `wireEnforcementHooks` wires the git/content guardrails machine-wide (PreToolUse/Bash + PostToolUse/Edit|Write|MultiEdit), opt-in via `--enforce-globally` or automatic once a global `hooks.policy.json` exists (`loadPolicy` already merged the global policy; now the hooks actually fire globally). Skills resolve the **effective** (global+project) config — all 54 adapters regenerated; `setup-harness.md` gained the `--global` flow + merge semantics; README documents "one PC, same conventions everywhere". Proof **`scripts/test-config-layers.mjs`** (resolve/override, lossless split, on-disk resolution honoring `CLAUDE_CONFIG_DIR`, global generation, lean project slice, `loadPolicy` layering, enforcement wiring incl. the bootstrap opt-in) wired into CI (now **9 gates**). LF-only, no NUL bytes. -**✅ Stage 7 COMPLETE (2026-07-10) — and with it the entire harness-upgrade plan.** All checklist boxes (7a–7j) are ticked; the pack (`thomasluizon/agentic-dev-workflow`) is the smart, AI-installed harness the plan set out to build: `bootstrap` → `/setup-harness` (research → interview → doc decode → tier every rule → gated decomposition → generate + self-verify) → `/update-harness` (monthly web-grounded staleness), all dual-target (Claude Code + opencode), with the **two-layer config** (global machine defaults + project overrides, section A) so a uniform PC is configured once, and zero project/policy leakage enforced in CI. The one thing that genuinely needs a human at the keyboard: run `/setup-harness` interactively on your real machine to generate its overlay (the grill asks you questions no fixture can answer). +**✅ Stage 7 COMPLETE (2026-07-10) — and with it the entire harness-upgrade plan as originally specced.** All checklist boxes (7a–7j) are ticked; the pack (`thomasluizon/agentic-dev-workflow`) is the smart, AI-installed harness the plan set out to build: `bootstrap` → `/setup-harness` (research → interview → doc decode → tier every rule → gated decomposition → generate + self-verify) → `/update-harness` (monthly web-grounded staleness), all dual-target (Claude Code + opencode), with the **two-layer config** (global machine defaults + project overrides, section A) so a uniform PC is configured once, and zero project/policy leakage enforced in CI. The one thing that genuinely needs a human at the keyboard: run `/setup-harness` interactively on your real machine to generate its overlay (the grill asks you questions no fixture can answer). **A new requirement surfaced after this — Stage 8 (repo-clean mode), now ✅ COMPLETE (2026-07-10).** + +## Stage 8 — Repo-clean (global-only) install mode ✅ COMPLETE (2026-07-10 — agentic-dev-workflow PR #10, MERGED) + +**✅ Stage 8 COMPLETE (2026-07-10) — the harness now supports BOTH in-repo and repo-clean installs.** Landed via **agentic-dev-workflow PR #10** (`feature/stage8-repo-clean-install`, base main, squash-MERGED; agentic main is UNPROTECTED so no `--admin`). All 14 locked decisions built exactly as specced. New **`core/hooks/logic/store.mjs`** (dependency-free, hot-path-safe): `resolveRepoRoot` (pure-fs `.git` walk-up; a linked-worktree `.git` FILE is followed via `gitdir:`+`commondir` to the MAIN worktree root so all worktrees share one entry — verified against real `git worktree` in the proof), deterministic `slugForPath` (mirrors CC's `~/.claude/projects/`), `storeRoot`/`storeEntryDir`, `index.json` + `registerStoreEntry`, `readStorePolicy`/`readStoreConfigText`/`buildInjectedContext` (the shared facts+rules assembly BOTH injectors call). **Layering `DEFAULT < global < store < in-repo`** for `loadPolicy` (hooks/logic) AND `resolveConfig` (setup) — in-repo wins; a mode-1 repo with no store entry is untouched; config + enforcement dual-target for free (both adapters key off cwd). **FACT/RULE injectors:** new CC `UserPromptSubmit` hook `adapters/claude-code/hooks/project-facts.mjs` + opencode `experimental.chat.system.transform` (signature VERIFIED against the installed `@opencode-ai/plugin` SDK 1.14, `(input,{system:string[]})`); no store entry ⇒ silent no-op, fail-open, soft size cap. **`bootstrap` `wireFactsInjector`** (machine-wide, idempotent, auto-vendored); **`generate.mjs` `layout:"store"`** remap (`CLAUDE.md`→`facts.md`, `.claude/*`→flat, lint→gaps). **Install mode is the FIRST interview question** (in-repo committed / gitignored / repo-clean), pre-filled + confirmed; `--repo-clean` is an optional accelerator only. **sync + `/update-harness` + `staleness.mjs` store-aware** (`collectStoreFiles`/`scan-store`, repo-clean `reapplyOverlay`). **`setup-harness.md`** documents the repo-clean generate/verify/migration flow + the two known constraints (LINT → content-scan-hook fallback on store policy; SKILL → machine-wide install + store record, not auto-discovered). **Proof `scripts/test-repo-clean.mjs`** (temp HOME + two fixture git repos, the REAL vendored CC hook + the REAL opencode plugin + a REAL linked worktree) wired as the **10th CI gate**; adapter count unchanged (54 — the injector is an authored hook, not a skill). All 10 gates green (genericity + hook-engine + setup + generate + wiring + bootstrap + update-harness + config-layers + **repo-clean** + adapters-sync) + GitGuardian; generic (tilde paths only), dual-target, cross-platform (no bash-isms), LF-only, no NUL bytes. The single residual human step for the whole initiative is unchanged: run `/setup-harness` interactively on the real machine (and pick repo-clean there if the company PC forbids in-repo AI files). + +### Original spec (as built) + +## Stage 8 — Repo-clean (global-only) install mode (locked spec) + +**Why.** On some company machines the repo working directory must stay pristine — you **cannot add or commit ANY AI/Claude files inside a repo** (`.claude/`, `CLAUDE.md`, `workflow.config.yaml`, `hooks.policy.json`, lint scaffolds — none of it). Today `/setup-harness` — even with the 7j two-layer config — always writes the *project* overlay INTO the repo, so on such a machine the harness is only usable as bare global `~/.claude` instructions. `/setup-harness` must support a **repo-clean / global-only mode** where **every artifact lives under `~/.claude`** (or another out-of-repo location), keyed/scoped to the project, with **ZERO files written into the repo**. (This is the user's real situation on a company PC.) + +**Constraint (RESOLVED 2026-07-10 — grilled + locked): varies per machine.** Some machines forbid any file in the working dir (strict-(b): locked/scanned checkout), some only forbid commits (gitignored untracked OK). So the harness ships **three modes**, and `/setup-harness` **proactively asks** which to use: (1) in-repo committed [default, current], (2) in-repo gitignored, (3) repo-clean/global-only (strict-(b), the superset). No `--repo-clean` flag is required — mode is a first-class interview question, pre-filled from a detected recommendation and confirmed (a flag is only an optional accelerator). + +**What's missing (the gap 7j left):** +- 7j's project layer still writes `/workflow.config.yaml`, `/CLAUDE.md`, `/.claude/*`, `/hooks.policy.json`. +- `core/setup/config.mjs` `resolveConfig` finds the project config by walking UP from cwd for an **in-repo** `workflow.config.yaml`. +- `core/hooks/logic/config.mjs` `loadPolicy` finds the project policy by walking UP from the edited file for an **in-repo** `hooks.policy.json`. +- The FACT tier relies on an **in-repo** `CLAUDE.md`; `harness.answers.yaml`/`harness.manifest.json` are written to the repo root too. + +**Verification DONE (2026-07-10, via `claude-code-guide` + live docs + opencode plugin docs):** (a) NO native out-of-repo per-project mechanism for the FACT/instructions tier — `~/.claude/CLAUDE.md` and `~/.claude/rules/*.md` load for **all** projects; a rule's `paths:` scopes by **file glob, not project root**; so project facts/rules cannot be scoped to one repo without a repo file. (b) Every hook receives `cwd` on stdin and `CLAUDE_PROJECT_DIR` (= git root) in its env; `UserPromptSubmit` can inject via `{"hookSpecificOutput":{"hookEventName":"UserPromptSubmit","additionalContext":…}}`. (c) settings.json: the user layer is global; project settings load only from the launch dir (no per-project-under-`~/.claude` native map); git root is the project boundary; `~/.claude.json` already keys per-project MCP config by **absolute path** (the keying precedent). (d) opencode has **no stable** prompt hook, but `experimental.chat.system.transform` (push into the system array) + `chat.message` can inject context, and `tool.execute.before/after` already carry the cwd. ⇒ **config + enforcement tiers are dual-target for free** (our resolver/`loadPolicy` key off cwd; both adapters pass it); **only the FACT/RULE injection tier needs a built mechanism** — a global cwd-keyed hook per tool. + +**LOCKED design (grilled + locked 2026-07-10; all 14 decisions = the recommended option, plus the mode-selector-is-a-question correction):** + +| # | Decision | Choice | +|---|---|---| +| 1 | Constraint | varies per machine → 3 modes | +| 2 | Store key | absolute repo-root path (git toplevel), machine-local | +| 3 | FACT delivery | global `UserPromptSubmit` hook injects by cwd (no native mechanism exists) | +| 4 | Mode select | proactive interview question, not flag-driven | +| 5 | Store layout | dir-per-project + `index.json`, deterministic path-slug | +| 6 | Root/worktree | pure-fs `.git` walk-up; worktrees → shared main root | +| 7 | Precedence | `DEFAULT < global < store < in-repo` (in-repo wins; mode-1 repos unchanged) | +| 8 | Injector | facts + project rules, every turn, lean, no-op without a store entry | +| 9 | Migration | re-run setup in the target mode + manifest-guided gated cleanup | +| 10 | Global req | repo-clean requires the global install; auto-run bootstrap if missing | +| 11 | Wiring | auto-wire injector + enforcement on the store's first project | +| 12 | Multi-repo | one store entry per git root, sharing config/policy | +| 13 | opencode | full parity via `experimental.chat.system.transform`, degrade-safe | +| 14 | Refresh | `sync` + `/update-harness` store-aware, in-scope now | + +**Store** (`~/.claude/harness/`, honoring `CLAUDE_CONFIG_DIR`): `index.json` (`{ "": { slug, mode, group } }`) + `projects//{workflow.config.yaml, hooks.policy.json, facts.md, rules/*, harness.answers.yaml, harness.manifest.json, harness.mode}`. `slug` = deterministic sanitized abs path (CC's own `~/.claude/projects/` convention) so a hook computes it from cwd with NO index read on the hot path; the index is for listing/migration/collision only. + +**New module `core/hooks/logic/store.mjs`** (dependency-free, hot-path safe, in `logic/` so both the hot-path hooks AND `setup/` import it with no layering inversion): `resolveRepoRoot(startDir)` (walk up for `.git`; if `.git` is a FILE = a linked worktree, parse its `gitdir:` pointer → derive the MAIN worktree root so all worktrees share one entry; pure fs, no git shell-out), `slugForPath`, `storeRoot`/`storeEntryDir`, `readStorePolicy/Config/Facts(startDir)`, index read/write, and `buildInjectedContext(repoRoot)` (the shared facts+rules assembly BOTH injectors call — single logic core). + +**Resolver/policy:** `resolveConfig` = `deepMerge(deepMerge(global, store), inRepo)`; `loadPolicy` inserts the store policy just below the in-repo layer. Both stay pure/testable. Because the CC hooks + opencode plugin already pass `cwd`, **config + enforcement are dual-target for free** — only FACT/RULE injection needs a built hook. + +**FACT/RULE injection:** CC = new authored hook `adapters/claude-code/hooks/project-facts.mjs` (UserPromptSubmit → `buildInjectedContext` → `additionalContext`; no store entry ⇒ silent exit 0; fail-open; soft size cap with a note). opencode = extend `adapters/opencode/plugin/agentic-harness.js` with `experimental.chat.system.transform` (degrade-safe; signature verified vs the installed SDK at build time). + +**Bootstrap:** add `wireFactsInjector`; setup repo-clean, on the FIRST store entry, ensures the global install (auto-runs `bootstrap` if missing) + auto-wires injector + enforcement (idempotent, backed up). `project-facts.mjs` is auto-vendored (buildPlan already copies all hook files). + +**Generator:** `planArtifacts(..., { layout: "store" })` remaps `CLAUDE.md`→`facts.md`, `.claude/rules/*`→`rules/*`, writes to the store entry dir. Store config is self-contained (full) by default, the lean project slice when a `--global` layer exists. + +**Known repo-clean constraints (documented, never silent):** LINT tier can't add a repo linter rule → falls back to the content-scan hook on store policy (recorded in the gaps report); SKILL tier — machine-specialized `investigate`/`ship` are machine-wide (install via `--global`); bespoke project skills are written to the store for record/sync + surfaced by the injector but NOT auto-discovered as slash-commands (CC discovers skills only under `~/.claude/skills` or `/.claude/skills`). + +**Setup UX:** mode is a proactive question (in-repo committed / in-repo gitignored / repo-clean), pre-filled from detection, confirmed; flags are optional accelerators only. + +**Refresh:** `sync.mjs` re-applies from the store's `harness.answers.yaml` + `harness.manifest.json`; `/update-harness` scans the store's installed files; both point their root at the store entry when the repo is repo-clean. + +**Same bar as 7a–7j:** generic, dual-target, gated, cross-platform (no bash-isms, LF-only, no NUL bytes), a self-testing proof (`scripts/test-repo-clean.mjs`) wired into `genericity-check.yml`, README + `setup-harness.md` + this plan updated. One gated PR (cohesive feature); CI green before merge; ask before merging. + +**Acceptance** (proof = `scripts/test-repo-clean.mjs`, the 10th CI gate): on a fixture repo, repo-clean setup produces **ZERO files inside the repo**, yet the effective config + policy + facts all resolve for that repo from `~/.claude`; enforcement BLOCKS a protected-branch push (an ordinary feature push passes) via the store policy; the CC injector emits the project's facts for repo1 and **no-ops** for an unrelated repo2; a linked worktree of repo1 resolves to repo1's store entry; repo2 is unaffected. Every existing gate (genericity + hook-engine + setup + generate + wiring + bootstrap + update-harness + config-layers + adapters-sync) stays green; `core/` + the engine adapters stay generic (tilde paths only, no home-path literals) and dual-target. + +**✅ Stage 8 has landed — the harness now supports both in-repo and repo-clean installs.**