Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 14 additions & 8 deletions config/dev-model.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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:
Expand Down
14 changes: 10 additions & 4 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions docs/kit-friction-log-archive.md
Original file line number Diff line number Diff line change
@@ -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.
35 changes: 35 additions & 0 deletions docs/kit-friction-log.md
Original file line number Diff line number Diff line change
@@ -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.
6 changes: 6 additions & 0 deletions docs/kit-handoff-history.md
Original file line number Diff line number Diff line change
@@ -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
84 changes: 84 additions & 0 deletions docs/kit-handoff.md
Original file line number Diff line number Diff line change
@@ -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 `<tracker.url — stamped by init.sh>`
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).
6 changes: 5 additions & 1 deletion kit-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
],
Expand Down Expand Up @@ -63,7 +67,7 @@
},
"scripts/kit_doctor.py": {
"role": "engine",
"sha256": "23303dfac720e6f20cd1b1131d44b44893bbde8d061d18eadbb26a86a4252eef"
"sha256": "d2e4fcaf3e4814a340b1c0ac499e3a79a977122004d593629cbb562a9213bbd0"
},
"scripts/lib/devmodel_config.py": {
"role": "engine",
Expand Down
5 changes: 5 additions & 0 deletions scripts/kit_doctor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
)
Expand Down
24 changes: 21 additions & 3 deletions scripts/tests/test_kitconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Loading