From 46aea87cc657232a952dc574d1ecda680bd6b088 Mon Sep 17 00:00:00 2001 From: Topi Jarvinen Date: Sat, 25 Jul 2026 09:24:41 +0300 Subject: [PATCH 1/2] =?UTF-8?q?chore:=20wrap=20up=20=E2=80=94=20the=20kit?= =?UTF-8?q?=20now=20practises=20Principle=20#1=20on=20itself?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Session wrap-up, and the resolution of the dogfooding gap the assessment found: this repo's own handoff and friction log were unrendered skeletons, so the kit was not doing the one thing Principle #1 requires. It could not simply be fixed by filling them in. docs/handoff.md and docs/friction-log.md are the SKELETONS shipped to adopters — writing real session blocks there would ship this repo's narrative into every adopting repo and destroy the 'devkit-template: unrendered' marker the seeding logic depends on. So the kit's own files are docs/kit-*.md, with the indirection explained at the config keys. An adopter's config keeps the plain names; only the template repo needs this. - config paths + doc_budgets repointed to docs/kit-*.md - docs/kit-handoff.md: first real session block (six PRs, 83 -> 196 tests, what was decided, what was learned, and the Phase 3 next step) - docs/kit-friction-log.md: the friction that is NOT yet issue-shaped, plus a note that most of this session's friction routed straight to GitHub Issues — which is Principle #2's prescribed routing, not a neglected inbox - kit_doctor: kit-*.md added to ADOPTER_OWNED so they are never compared - the marker test split in two: shipped skeletons asserted by LITERAL filename (reading paths.handoff would now check the kit's live plan instead), plus a new test asserting the kit's own plan is real — no marker, no placeholder dates Filed while wrapping up: #18 (the cp -r quickstart can't tell kit-owned from adopter-owned; the manifest already encodes the boundary, nothing consumes it for installing — landing that would let kit-*.md go back to the plain names) and #19 (a receipt can be recorded while the primary bot is merely queued, which is how #16's findings landed post-merge). 196 tests pass. check_doc_budget and kit_doctor both clean: the kit's own narrative docs now report 'in use' rather than 'still an unrendered template'. --- config/dev-model.yaml | 22 ++++++--- docs/kit-friction-log-archive.md | 4 ++ docs/kit-friction-log.md | 35 +++++++++++++ docs/kit-handoff-history.md | 6 +++ docs/kit-handoff.md | 84 ++++++++++++++++++++++++++++++++ kit-manifest.json | 6 ++- scripts/kit_doctor.py | 5 ++ scripts/tests/test_kitconfig.py | 24 +++++++-- 8 files changed, 174 insertions(+), 12 deletions(-) create mode 100644 docs/kit-friction-log-archive.md create mode 100644 docs/kit-friction-log.md create mode 100644 docs/kit-handoff-history.md create mode 100644 docs/kit-handoff.md diff --git a/config/dev-model.yaml b/config/dev-model.yaml index e2d159c..11537aa 100644 --- a/config/dev-model.yaml +++ b/config/dev-model.yaml @@ -27,10 +27,16 @@ paths: # The two canonical narrative files (Principles #1 and #2). The kit ships these # as docs/handoff.md and docs/friction-log.md; repoint them if your repo already # keeps a plan / inbox under different names. - handoff: docs/handoff.md # the living plan / session handoff - handoff_history: docs/handoff-history.md # swept older session blocks - friction_log: docs/friction-log.md # the friction inbox - friction_log_archive: docs/friction-log-archive.md + # THIS repo is the kit's own workspace, so its narrative files are named + # kit-*.md. docs/handoff.md and docs/friction-log.md are the SKELETONS shipped + # to adopters (rendered from docs/templates/ by init.sh) — if the kit pointed + # its own plan at those, every session block would ship to adopters and the + # unrendered marker would be gone. An adopter's own config uses the plain + # names; only the template repo needs this indirection. + handoff: docs/kit-handoff.md # the living plan / session handoff + handoff_history: docs/kit-handoff-history.md # swept older session blocks + friction_log: docs/kit-friction-log.md # the friction inbox + friction_log_archive: docs/kit-friction-log-archive.md playbook: docs/autonomous-session-playbook.md # the autonomous operating contract # Directory containing the deterministic kit engines. Mature repos can vendor # them under scripts/devkit without rewriting every workflow. @@ -48,13 +54,13 @@ doc_budgets: # Warn-only line-count tripwires (the archival mechanism of Principle #1). A file # over budget is a housekeeping nudge, never a hard block. Read by # scripts/check_doc_budget.py. - - path: docs/handoff.md + - path: docs/kit-handoff.md budget: 400 - archive: docs/handoff-history.md + archive: docs/kit-handoff-history.md remedy: "run the wrap-up workflow to sweep old session blocks into the history file" - - path: docs/friction-log.md + - path: docs/kit-friction-log.md budget: 150 - archive: docs/friction-log-archive.md + archive: docs/kit-friction-log-archive.md remedy: "run the triage-friction-log workflow to graduate the inbox to the tracker" vcs: diff --git a/docs/kit-friction-log-archive.md b/docs/kit-friction-log-archive.md new file mode 100644 index 0000000..82a6e6a --- /dev/null +++ b/docs/kit-friction-log-archive.md @@ -0,0 +1,4 @@ +# Friction Log Archive — agentic-dev-kit + +Graduated friction entries live here after they have been routed to the tracker +(GitHub Issues on this repo) or promoted into a repeated-pattern rule. diff --git a/docs/kit-friction-log.md b/docs/kit-friction-log.md new file mode 100644 index 0000000..3667b14 --- /dev/null +++ b/docs/kit-friction-log.md @@ -0,0 +1,35 @@ +# Friction Log — agentic-dev-kit + +> **Lean inbox (Principle #2 — the friction flywheel).** Friction surfaced during real use, +> recorded at session end. Single incidents route **down** to the tracker; a genuine +> multi-occurrence **pattern** graduates **up** into a rule or skill change. +> +> **This repo's tracker is GitHub Issues on itself**, so most of this session's friction was +> filed directly as issues rather than parked here — which is the routing Principle #2 +> prescribes, not a neglected inbox. Entries below are the ones that are *not* yet +> issue-shaped. +> +> Tracker board: https://github.com/topij/agentic-dev-kit/issues + +## 2026-07-25 — inbox + +- **The `cp -r` quickstart can't distinguish kit-owned from adopter-owned files (severity: M).** + Any file the kit tracks lands in an adopter's repo, which is why this repo's own narrative + docs had to be renamed `kit-*.md` rather than simply filled in. `kit-manifest.json` now + encodes the ownership boundary (`adopter_owned`), so a manifest-aware installer could copy + correctly and the rename would become unnecessary. Filed as issue #18. + +- **`--record-review` accepts a receipt while the primary bot is still queued (severity: M).** + Recorded a fallback receipt on #16 when CodeRabbit's check read `PENDING — Review queued`; + its four valid findings landed after the merge. The doctrine distinguishes *unavailable* + from *slow*, but nothing mechanically does. Candidate: treat a configured bot's own + `PENDING` check as a merge blocker while no receipt exists — but that inverts the + informational-check exclusion in the one case where the exclusion is load-bearing (it is + what stops the loop wedging on a bot that never reports), so it needs care. Filed as #19. + +- **A lane's local gate fails for reasons unrelated to its diff (severity: H).** + All three lanes this session hit the same two `state_paths` test failures, caused purely by + running from inside a marker-carrying worktree. A gate that goes red for environmental + reasons teaches agents to ignore a red gate. Already filed as issue #10 — raising severity + here because three independent occurrences in one session makes it a pattern, not an + incident. diff --git a/docs/kit-handoff-history.md b/docs/kit-handoff-history.md new file mode 100644 index 0000000..1edc1eb --- /dev/null +++ b/docs/kit-handoff-history.md @@ -0,0 +1,6 @@ +# Handoff History — agentic-dev-kit + +Archived session narratives from [`kit-handoff.md`](kit-handoff.md). Keep active direction +and the next step there; this file is append-only history. + +## Session log diff --git a/docs/kit-handoff.md b/docs/kit-handoff.md new file mode 100644 index 0000000..966a394 --- /dev/null +++ b/docs/kit-handoff.md @@ -0,0 +1,84 @@ +# agentic-dev-kit — Living Plan (Handoff) + +> **Forward-looking handoff (Principle #1).** Read this at the start of every session +> (`/session-start`); update it at the end (`/wrap-up`). This file — not an agent's +> memory, not a scratch note — is the single source of truth for what's done, in +> progress, and next. +> +> **Why `kit-*.md` and not `handoff.md`:** `docs/handoff.md` is the *skeleton shipped to +> adopters*, rendered from `docs/templates/` by `init.sh`. If this repo pointed its own +> plan at that file, every session block here would ship into adopters' repos and the +> unrendered marker would be gone. An adopter's config uses the plain names; only the +> template repo needs this indirection. +> +> Older session blocks graduate to [`kit-handoff-history.md`](kit-handoff-history.md) once +> this file crosses its line budget (`scripts/check_doc_budget.py`). + +Last updated: 2026-07-25 — first full assessment-and-remediation pass; the kit now fixes +its own adoption path, and can diagnose an installed copy. + +## Latest session — 2026-07-25 + +**Theme —** Assessed the kit against its own ten principles, then fixed what the +assessment found. Six PRs merged; tests 83 → 188. The recurring shape: **the kit had +written down rules it was itself violating.** + +- **Adoption was broken at step one.** `init.sh` shipped mode `100644`, so the documented + `./init.sh` failed for every adopter. Its narrative-doc seeding was dead code — the kit + *ships* `docs/handoff.md`, so the "seed only if absent" guard was permanently false and + everyone started with a `my-project` header and a `` + line that `init.sh` never stamped. (#8) +- **An upgrade path now exists, and it was mostly already written.** `init.sh` already had + `migrate_runtime_schema()`; it had never run anywhere, because `./init.sh` didn't work. + Fixing the mode unblocked the mechanism. Added `kit.version`, template rendering keyed on + an unrendered marker, hook installation as a shim (honoring `core.hooksPath`), and + **probing** `paths.engines` rather than defaulting it. (#8) +- **Engines became kit-owned.** `scripts/lib/kitconfig.py` — a stdlib-only config reader, + verified byte-equal to PyYAML on the shipped config and on two real adopter configs — let + review-bot markers, CI policy, and the cron/CI exemption move *out* of the engines and + into config. Every shipped engine is now dependency-free. That invariant is what makes an + upgrade a file copy instead of a manual merge. (#8, #9, #16 — closed #5) +- **Four hardcoded-literal bugs fixed.** `pre-push` diffed against a hardcoded + `origin/main`, so on any repo whose trunk isn't `main` the guard **silently never fired**; + `pr-watch` could never converge on a repo with no CI; `JOB_NAME` was a Jenkins-ism that + GitHub Actions never sets. (#9) +- **Claude-side wiring caught up with Codex-side.** All seven commands had no frontmatter, + so their surfaced descriptions were raw first lines. Added `.claude/settings.json` with + SessionStart budget hooks and a `PostToolUse` hook that mandates PR follow-through — + ported from cs-toolkit, which had the better mechanism. (#13) +- **`kit_doctor` + `/upgrade`.** Per-file drift against a hashed manifest, plus the four + installation checks nothing else performed. Run read-only against the real adopters it + found `brain`'s live breakage (config `paths.engines` pointing at a directory with no + engine in it) and `OpenKitchen`'s four drifted files. (#16, #17) + +**Decided** + +- Engines are kit-owned; config is adopter-owned. Everything else follows from it. +- `differs` never claims a *cause* — a hash mismatch can't distinguish "older version" from + "hand-edited", and claiming the latter sends someone hunting for edits they never made. +- Re-running `./init.sh` is the supported config upgrade; `/upgrade` handles engines. + +**Learned** + +- **The kit predicted its own bugs and shipped them anyway.** `dev_session.sh` states "any + doc that quotes [the lane contract] should quote it, not restate it" — and `parallel.md`'s + kickoff prompt restated it, drifted, and told lanes to "mark ready when done" while the + binding contract said "do not mark it ready." +- **A guard that fails open must be loud.** Three separate silent-no-op bugs this session + (`origin/main`, the uninstalled hook, `paths.engines`). Silence is indistinguishable from + "checked and clean". +- **Same bug class, both directions, one session.** `core.hooksPath` was fixed in `init.sh` + (write side) and then reintroduced in `kit_doctor` (read side) hours later. See issue #15. +- **Queued ≠ unavailable.** A review receipt was recorded while CodeRabbit was merely + queued, and its four valid findings landed after the merge. `decide_done` can't tell the + two apart. + +▶ Next: decide the **Phase 3 sequencing** for the cs-toolkit back-port — porting the review +receipts as-is would break the nightly fixer (nothing there records a receipt, so +`decide_done` would report `done=False` forever on every PR it opens). Proposed order: +additive merge gate first → receipts behind a flag → wire the fixer's own `/code-review` +step to record → flip the flag. + +______________________________________________________________________ + +> Older session entries live in [`kit-handoff-history.md`](kit-handoff-history.md). diff --git a/kit-manifest.json b/kit-manifest.json index f5bcd79..6bfcdad 100644 --- a/kit-manifest.json +++ b/kit-manifest.json @@ -5,6 +5,10 @@ "docs/handoff-history.md", "docs/friction-log.md", "docs/friction-log-archive.md", + "docs/kit-handoff.md", + "docs/kit-handoff-history.md", + "docs/kit-friction-log.md", + "docs/kit-friction-log-archive.md", "README.md", ".gitignore" ], @@ -63,7 +67,7 @@ }, "scripts/kit_doctor.py": { "role": "engine", - "sha256": "23303dfac720e6f20cd1b1131d44b44893bbde8d061d18eadbb26a86a4252eef" + "sha256": "d2e4fcaf3e4814a340b1c0ac499e3a79a977122004d593629cbb562a9213bbd0" }, "scripts/lib/devmodel_config.py": { "role": "engine", diff --git a/scripts/kit_doctor.py b/scripts/kit_doctor.py index ad80eeb..8a15934 100755 --- a/scripts/kit_doctor.py +++ b/scripts/kit_doctor.py @@ -110,6 +110,11 @@ "docs/handoff-history.md", "docs/friction-log.md", "docs/friction-log-archive.md", + # This repo's own narrative files (see the note in config/dev-model.yaml). + "docs/kit-handoff.md", + "docs/kit-handoff-history.md", + "docs/kit-friction-log.md", + "docs/kit-friction-log-archive.md", "README.md", ".gitignore", ) diff --git a/scripts/tests/test_kitconfig.py b/scripts/tests/test_kitconfig.py index bb80895..fe0b9c6 100644 --- a/scripts/tests/test_kitconfig.py +++ b/scripts/tests/test_kitconfig.py @@ -188,14 +188,32 @@ def test_narrative_templates_ship(name): assert (REPO_ROOT / "docs" / "templates" / name).is_file() -def test_shipped_skeletons_carry_the_unrendered_marker(): +@pytest.mark.parametrize( + "skeleton", + ["handoff.md", "handoff-history.md", "friction-log.md", "friction-log-archive.md"], +) +def test_shipped_skeletons_carry_the_unrendered_marker(skeleton): """The marker is what lets init.sh tell 'the file the kit shipped' from 'a handoff someone is using'. Without it the seed step can never fire on a - copy-in, which is exactly how adopters ended up with `my-project` headers.""" + copy-in, which is exactly how adopters ended up with `my-project` headers. + + Asserted on the LITERAL adopter-facing filenames, deliberately not via + `paths.handoff`: this repo points its own config at `docs/kit-*.md` so its + session blocks never ship to adopters, so reading the config here would check + the kit's live plan (which must NOT carry the marker) instead of the skeleton.""" + doc = REPO_ROOT / "docs" / skeleton + assert "devkit-template: unrendered" in doc.read_text(encoding="utf-8"), doc + + +def test_kits_own_plan_is_real_not_a_skeleton(): + """The flip side: this repo must actually practise Principle #1. A kit whose + own living plan is an unrendered template is not dogfooding it.""" config = kitconfig.load_config(SHIPPED_CONFIG) for key in ("paths.handoff", "paths.friction_log"): doc = REPO_ROOT / kitconfig.get(config, key) - assert "devkit-template: unrendered" in doc.read_text(encoding="utf-8"), doc + text = doc.read_text(encoding="utf-8") + assert "devkit-template: unrendered" not in text, doc + assert "YYYY-MM-DD" not in text, f"{doc} still has placeholder dates" @pytest.mark.parametrize( From 5a27c3d9b81d40c5906bc4e87aa3fb18b7dc5e5f Mon Sep 17 00:00:00 2001 From: Topi Jarvinen Date: Sat, 25 Jul 2026 09:26:09 +0300 Subject: [PATCH 2/2] docs: correct getting-started's stale seeding description MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Found by the fallback review pass on this PR. getting-started.md still described init.sh as seeding the narrative docs 'only if they don't already exist' — the rule #8 replaced, and the one that could never fire (the kit ships those files, so a copy-in always landed them first). Swept the other docs/handoff.md references while I was there and deliberately left them: README's what's-inside table, parallel-dev.md, adopt.md, and the two flywheel skills all describe what an ADOPTER gets, where the plain filenames are correct. Only this repo's own config uses kit-*.md. --- docs/getting-started.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index e8b2f9d..924336b 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -35,10 +35,16 @@ cp -r /path/to/agentic-dev-kit/. . > [Upgrading an already-adopted repo](../README.md#upgrading-an-already-adopted-repo) > section for what that does and doesn't touch. -`init.sh` prompts you for a handful of values — project name, agent runtime, tracker, the -protected branch, your review bot — and stamps them into `config/dev-model.yaml`. -It also seeds `docs/handoff.md` and `docs/friction-log.md` (only if they don't -already exist) and adds the state sandbox to `.gitignore`. +`init.sh` prompts you for a handful of values — project name, agent runtime, tracker board, +the protected branch, your review bot — and stamps them into `config/dev-model.yaml`. +It renders the four narrative docs from `docs/templates/`, installs the pre-push hook, and +adds the state sandbox to `.gitignore`. + +It renders a narrative doc when the target is **missing or still carries the shipped +`devkit-template: unrendered` marker** — so a handoff you are actually using is left +byte-identical, which is what makes re-running it the supported upgrade path. (The older +"only if it doesn't already exist" rule couldn't work: the kit *ships* those files, so a +copy-in always landed them first and the seed step never fired.) Then open `config/dev-model.yaml` and fill in anything you skipped — especially the `tracker` and `models` blocks. That one file is where every skill and script