-
Notifications
You must be signed in to change notification settings - Fork 0
kit_doctor + /upgrade: make an already-adopted install diagnosable #16
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,146 @@ | ||
| --- | ||
| description: Upgrade an already-adopted agentic-dev-kit installation to the current kit — migrate the config schema, refresh kit-owned engines, and diff anything that drifted. The counterpart to /adopt (first install) and to re-running ./init.sh (config only). Use when pulling a kit update into a repo that already has the kit. | ||
| argument-hint: "[--dry-run]" | ||
| --- | ||
|
|
||
| Upgrade this repo's agentic-dev-kit installation. Non-destructive: runs on a branch, and | ||
| never replaces a file without knowing it is safe to replace. | ||
|
|
||
| > **The invariant this rests on.** Engines are **kit-owned**; config is **adopter-owned**. | ||
| > Everything project-specific — paths, tracker, review-bot markers, CI policy, model | ||
| > tiers — lives in `config/dev-model.yaml`, so an engine should never need editing to | ||
| > adopt it. That is what makes an upgrade a file copy instead of a manual merge. If this | ||
| > run finds engines you had to edit, that is a **kit bug**: report it rather than | ||
| > carrying the patch forward. | ||
|
|
||
| ## Step 0 — Establish what shape this repo is in | ||
|
|
||
| Four shapes exist in the wild and they need different handling. Determine which: | ||
|
|
||
| ```bash | ||
| ls config/dev-model.yaml 2>/dev/null && echo "has config" || echo "NO CONFIG" | ||
| ``` | ||
|
|
||
| - **No `config/dev-model.yaml`** → this repo predates the config surface entirely (a kit | ||
| *ancestor*, or a partial hand-install). **Stop and run `/adopt` instead** — there is no | ||
| schema to migrate from. Say so plainly rather than guessing a config into existence. | ||
| - **Config present** → continue. `kit.version` tells you the schema generation; its | ||
| absence means v1 (pre-`runtime:`, pre-`models.tiers`). | ||
|
|
||
| Also fetch the kit you are upgrading *to*, if it isn't already local: | ||
|
|
||
| ```bash | ||
| git clone --depth 1 https://github.com/topij/agentic-dev-kit /tmp/agentic-dev-kit | ||
| ``` | ||
|
|
||
| Everything below copies **from** that checkout **into** this repo. | ||
|
|
||
| ## Step 1 — Diff the installation (read-only) | ||
|
|
||
| ```bash | ||
| uv run <engine-dir>/kit_doctor.py --manifest /tmp/agentic-dev-kit/kit-manifest.json | ||
| ``` | ||
|
|
||
| Read `<engine-dir>` from `paths.engines`. If `kit_doctor.py` isn't installed yet, run the | ||
| kit's copy against this repo: `uv run /tmp/agentic-dev-kit/scripts/kit_doctor.py --root . | ||
| --manifest /tmp/agentic-dev-kit/kit-manifest.json`. | ||
|
|
||
| The report gives you, per kit-owned file: `unchanged` / `differs` / `missing` / | ||
| `unknown-version`, plus four installation-level checks. **Read all four** — each is a | ||
| silent failure mode: | ||
|
|
||
| - **config schema version** — unversioned or behind means migrations are pending. | ||
| - **`paths.engines` resolves to a directory that actually holds engines.** A `✗` here is | ||
| the live breakage where every workflow's `<engine-dir>/…` reference points at nothing. | ||
| Nothing else validates this value, so nothing else would have told you. | ||
| - **pre-push hook installed** — a shipped-but-uninstalled hook binds nothing. | ||
| - **narrative docs rendered** — a doc still carrying `devkit-template: unrendered` means | ||
| the adoption never completed its seeding step. | ||
|
|
||
| `differs` deliberately does **not** claim a cause: a hash mismatch cannot distinguish | ||
| "older kit version" from "hand-edited". The report narrows it by schema version; you | ||
| confirm with an actual diff in Step 3. | ||
|
|
||
| ## Step 2 — Migrate the config (idempotent) | ||
|
|
||
| ```bash | ||
| ./init.sh | ||
| ``` | ||
|
|
||
| This is the supported config upgrade path, and it is safe to re-run any number of times. | ||
| It only ever **adds** missing keys, never guesses over an existing value; it probes | ||
| `paths.engines` from where engines actually are rather than defaulting; it stamps | ||
| `kit.version`; it installs the pre-push hook as a shim (honoring `core.hooksPath`); and | ||
|
Comment on lines
+64
to
+73
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Run the fetched kit’s migrator, not a potentially stale local This procedure fetches the current kit but then invokes the target repository’s existing 🤖 Prompt for AI Agents |
||
| it leaves a narrative doc that is genuinely in use byte-identical, re-rendering only one | ||
| that still carries the unrendered marker. | ||
|
|
||
| Press Enter through every prompt to keep current values. Then re-read the diff of | ||
| `config/dev-model.yaml` and confirm nothing you rely on changed. | ||
|
|
||
| ## Step 3 — Refresh engines, by state | ||
|
|
||
| Work through `kit_doctor`'s file list. **Branch first:** | ||
|
|
||
| ```bash | ||
| git checkout -b chore/kit-upgrade | ||
| ``` | ||
|
|
||
| - **`unchanged`** → copy the new version straight in. It is provably untouched, so there | ||
| is nothing to lose. | ||
| - **`missing`** → decide, don't assume. A sized-down adoption omits engines deliberately | ||
| (one surveyed repo installs 2 of 6 on purpose). Ask the operator whether each missing | ||
| piece is wanted before installing it. If a piece stays out, note it in the PR body so | ||
| the next upgrade doesn't re-litigate it. | ||
| - **`differs`** → `diff` the local file against the kit's, and read the diff: | ||
| - Only kit-authored changes (the local copy is simply older) → replace it. | ||
| - Local edits present → for each, find where that value now lives in | ||
| `config/dev-model.yaml` and move it there, then take the kit's engine. If there is no | ||
| config key for it, **stop**: that is the kit bug the invariant above describes. File | ||
| it upstream and keep the local patch, clearly flagged, until it lands. | ||
| - **Local edits that are genuinely ahead of the kit** — a fix made here first — are the | ||
| one case to route *upstream* instead: open a PR against the kit rather than | ||
| overwriting your better version. | ||
| - **`unknown-version`** → the manifest has no entry, so drift is unjudgeable. Treat as | ||
| `differs` and diff by hand. | ||
|
|
||
| Never batch-replace the whole list because most of it was `unchanged`. The `differs` | ||
| entries are exactly where the risk is. | ||
|
|
||
| ## Step 4 — Refresh the non-engine pieces | ||
|
|
||
| - **Shared workflows** (`docs/agentic-dev-kit/workflows/`) — same state logic as engines. | ||
| These are prompts an agent reads verbatim; a stale one silently teaches old behavior. | ||
| - **Runtime adapters** (`.claude/commands/`, `.agents/skills/`) — install any the kit has | ||
| that this repo lacks; keep the adopter's version where one already exists. | ||
| - **Templates** (`docs/templates/`) — refresh freely; the *rendered* docs are yours and | ||
| are never touched. | ||
| - **`.claude/settings.json`** — if this repo has its own, **merge** the kit's hooks and | ||
| permissions into it rather than replacing; it likely carries project-specific entries. | ||
|
|
||
| ## Step 5 — Verify | ||
|
|
||
| ```bash | ||
| uv run <engine-dir>/kit_doctor.py --manifest /tmp/agentic-dev-kit/kit-manifest.json | ||
| python -m pytest <engine-dir>/lib/state_paths/tests <engine-dir>/tests -q | ||
| uv run <engine-dir>/check_doc_budget.py | ||
| ``` | ||
|
|
||
| `kit_doctor` should now report zero `differs` and zero `unknown-version`, with `missing` | ||
| containing only deliberately-omitted pieces. Anything else means Step 3 left something. | ||
|
|
||
| > **Known gotcha:** the `state_paths` tests fail when run from inside a worktree carrying | ||
| > a `.devkit_state_root` marker — the fixture neutralizes the environment but not marker | ||
| > discovery. Run the gate from the main checkout, and see kit issue #10. | ||
|
|
||
| ## Step 6 — Record the friction, then hand off | ||
|
|
||
| Append anything this upgrade surfaced to the friction log (`paths.friction_log`) — an | ||
| engine you had to edit, a config key that didn't exist, a `missing` piece the report | ||
| couldn't classify. Tag `[kit]` on anything that is a kit-side fix and open an issue | ||
| upstream. That is Principle #2 applied to the kit itself, and it is how the four shapes | ||
| this skill handles were discovered in the first place. | ||
|
|
||
| Open a **draft PR** summarizing: schema version before → after, which engines were | ||
| refreshed / diffed / deliberately skipped, and any local-edit-vs-config resolution you | ||
| made. Leave the merge to the operator — an upgrade touches the machinery every other | ||
| workflow runs on. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -127,6 +127,40 @@ path, and it is safe to run any number of times: | |
| lives in `config/dev-model.yaml`, so you should never need to edit an engine to | ||
| adopt it. If you have, that's a bug — please report it. | ||
|
|
||
| ### Which files actually drifted — `kit_doctor` | ||
|
|
||
| Re-running `init.sh` handles the config. For the **engines**, the problem is that a | ||
| copy-in has no version marker and no record of whether it was edited, so nothing can | ||
| tell an older engine from a locally-patched one: | ||
|
|
||
| ```sh | ||
| uv run scripts/kit_doctor.py # or: python scripts/kit_doctor.py — stdlib only | ||
| ``` | ||
|
Comment on lines
+136
to
+138
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Use a shell comment instead of an em dash.
🤖 Prompt for AI Agents |
||
|
|
||
| Per kit-owned file it reports `unchanged` (safe to replace outright), `differs` (diff | ||
| before replacing), `missing`, or `unknown-version`. `differs` deliberately does **not** | ||
| claim a cause — a hash mismatch can't distinguish "older version" from "hand-edited", so | ||
| it narrows by schema version and leaves the call to you. Adopter-owned paths (your | ||
| config, your narrative docs) are never compared; they're *supposed* to differ. | ||
|
|
||
| It also checks four things nothing else validates: | ||
|
|
||
| - your config's schema generation vs. the kit's | ||
| - that **`paths.engines` points at a directory that actually holds engines** — a `✗` here | ||
| is the silent breakage where every workflow's `<engine-dir>/…` reference resolves to | ||
| nothing | ||
| - that the pre-push hook is installed, not merely shipped | ||
| - that the narrative docs were rendered, not left as unrendered templates | ||
|
|
||
| `kit-manifest.json` is the hash set it compares against, regenerated at release | ||
| (`--generate-manifest`) and gated in CI so it can't go stale. | ||
|
|
||
| **[`/upgrade`](.claude/commands/upgrade.md)** drives the whole sequence — shape detection, | ||
| config migration, then per-file refresh keyed on those states — non-destructively, on a | ||
| branch. It's the counterpart to [`/adopt`](.claude/commands/adopt.md) (first install). A | ||
| repo with no `config/dev-model.yaml` at all predates the config surface and routes to | ||
| `/adopt` instead. | ||
|
|
||
| ### Agent runtime adapters | ||
|
|
||
| The workflow definitions under `docs/agentic-dev-kit/workflows/` are shared. The | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,106 @@ | ||
| { | ||
| "adopter_owned": [ | ||
| "config/dev-model.yaml", | ||
| "docs/handoff.md", | ||
| "docs/handoff-history.md", | ||
| "docs/friction-log.md", | ||
| "docs/friction-log-archive.md", | ||
| "README.md", | ||
| ".gitignore" | ||
| ], | ||
| "files": { | ||
| "docs/agentic-dev-kit/safety-critical-changes.md": { | ||
| "role": "doctrine", | ||
| "sha256": "cc3f3a76a22ba0a8552539bd2b85f07e10e59485eae2c3749f06036bf7db2e91" | ||
| }, | ||
| "docs/agentic-dev-kit/workflows/parallel.md": { | ||
| "role": "workflow", | ||
| "sha256": "519fa1c623b75ba8cc0c6a3377f49a9681f47ada49f141aaf130d4007fffd460" | ||
| }, | ||
| "docs/agentic-dev-kit/workflows/pr-watch.md": { | ||
| "role": "workflow", | ||
| "sha256": "169d3e27e149a49f736931b838d117a2fe6ad3ac6ddcaefccb99a51b97b505c8" | ||
| }, | ||
| "docs/agentic-dev-kit/workflows/session-start.md": { | ||
| "role": "workflow", | ||
| "sha256": "f7a0a3ba8b65bc82f19764de6430bf82d0ee11d08447936b03de0056ac152c3a" | ||
| }, | ||
| "docs/agentic-dev-kit/workflows/wrap-up.md": { | ||
| "role": "workflow", | ||
| "sha256": "63c26e6337c44653a4b710af68a5bad03e61e4fe0a2231479df4ab105053e071" | ||
| }, | ||
| "docs/templates/friction-log-archive.md.tmpl": { | ||
| "role": "template", | ||
| "sha256": "af4a138e4a5ebe5634c55e7e417cd6b9f362bf3a35182b033f72876cd88180cf" | ||
| }, | ||
| "docs/templates/friction-log.md.tmpl": { | ||
| "role": "template", | ||
| "sha256": "33e5bf0fe285ebbc248e062124d52f86bb78c47767f185d3d04df192db09c66d" | ||
| }, | ||
| "docs/templates/handoff-history.md.tmpl": { | ||
| "role": "template", | ||
| "sha256": "111cf71a4c9b2a296f4fcf5ea02c6516907710e5b4fe0a7b3074f444f862ae69" | ||
| }, | ||
| "docs/templates/handoff.md.tmpl": { | ||
| "role": "template", | ||
| "sha256": "1654465836a9e70a6e81d76208783e9a12d65343d1e9c3091f1d30c25a437ce7" | ||
| }, | ||
| "scripts/archive_plan_sessions.py": { | ||
| "role": "engine", | ||
| "sha256": "fb15bd2dbe03257e6304f6e40f5af14bdffa66288fe0cc7ef2cdc38d0b4f9c76" | ||
| }, | ||
| "scripts/check_doc_budget.py": { | ||
| "role": "engine", | ||
| "sha256": "a402424a12278e6a2ba55f90692aef9e44e9f030932529b5e85208590fc4e6dd" | ||
| }, | ||
| "scripts/dev_session.sh": { | ||
| "role": "engine", | ||
| "sha256": "80311d61d9210c633560cd493402724e64aba9ff95b0ab02a40c97fb130d22eb" | ||
| }, | ||
| "scripts/hooks/pre-push": { | ||
| "role": "hook", | ||
| "sha256": "c469700ff61a1506fa5951b4d5ab3ac95f3213227d8838efa95eef0492516fba" | ||
| }, | ||
| "scripts/kit_doctor.py": { | ||
| "role": "engine", | ||
| "sha256": "23303dfac720e6f20cd1b1131d44b44893bbde8d061d18eadbb26a86a4252eef" | ||
| }, | ||
| "scripts/lib/devmodel_config.py": { | ||
| "role": "engine", | ||
| "sha256": "53458a5d0c72b1fe5e5e5f6e8e5881c08c8c03c255493ae841391c5a8d6c1a29" | ||
| }, | ||
| "scripts/lib/kitconfig.py": { | ||
| "role": "engine", | ||
| "sha256": "41c01a2956185d898f6cb75865d713845630a2d8335976f1460de1ed5f875317" | ||
| }, | ||
| "scripts/lib/repo_root.sh": { | ||
| "role": "engine", | ||
| "sha256": "980cbf5596cea67033a5dd02d53630f2a92c24afd693ba7727d5fc50303ff555" | ||
| }, | ||
| "scripts/lib/state_paths/__init__.py": { | ||
| "role": "engine", | ||
| "sha256": "691a7df06a3bf9019b5faca8b8ebdb9d54463d231d492a632fd54c16cd51a2c6" | ||
| }, | ||
| "scripts/lib/state_paths/paths.py": { | ||
| "role": "engine", | ||
| "sha256": "f54fd7d970402f8581f6c5080aa9614ce41cb2dafa2010c08406129731799397" | ||
| }, | ||
| "scripts/lib/state_paths/repo_root.py": { | ||
| "role": "engine", | ||
| "sha256": "d9412f1fe395b944a86126bcc39f85d6838382b0fc29d68c8b29b965cd530b5a" | ||
| }, | ||
| "scripts/lib/state_paths/resolver.py": { | ||
| "role": "engine", | ||
| "sha256": "e39fca241d6fbf28596ed3954f28a55e65831181cedb02c7c15e372895bb5bd2" | ||
| }, | ||
| "scripts/pr_watch.py": { | ||
| "role": "engine", | ||
| "sha256": "8e1fa0e8e14cc4e26172a91ea675194f7f985592a139ac5a09dd9def97deecd1" | ||
| }, | ||
| "scripts/reconcile_sessions.sh": { | ||
| "role": "engine", | ||
| "sha256": "825796c961162b4b69ecacd4c6a228a3395213fc6323df957b0a123896e59a53" | ||
| } | ||
| }, | ||
| "kit_version": 2 | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,7 @@ | ||
| #!/usr/bin/env python3 | ||
| # /// script | ||
| # requires-python = ">=3.12" | ||
| # dependencies = ["pyyaml"] | ||
| # dependencies = [] | ||
| # /// | ||
| """Warn-only tripwire: nudge an archive sweep when a handoff doc grows too large. | ||
|
|
||
|
|
@@ -18,7 +18,7 @@ | |
|
|
||
| The tracked docs + their budgets come from ``config/dev-model.yaml``'s | ||
| ``doc_budgets`` list (``{path, budget, archive, remedy}``) — see | ||
| ``scripts/lib/devmodel_config.py``. Adjust the budgets there, not here. | ||
| ``scripts/lib/kitconfig.py``. Adjust the budgets there, not here. | ||
|
|
||
| Usage: | ||
|
|
||
|
|
@@ -43,12 +43,12 @@ | |
| from pathlib import Path | ||
|
|
||
| sys.path.insert(0, str(Path(__file__).resolve().parent / "lib")) | ||
| from devmodel_config import _repo_root, get, load_config # noqa: E402 | ||
| from kitconfig import get, load_config, repo_root # noqa: E402 | ||
|
|
||
| # Discover the repo root by walking up for a `.git` marker (via devmodel_config) | ||
| # rather than assuming a fixed `scripts/<script>.py` depth — so this keeps working | ||
| # when the kit is vendored under a nested dir (e.g. scripts/devkit/). | ||
| REPO_ROOT = _repo_root() | ||
| REPO_ROOT = repo_root() | ||
|
Comment on lines
+46
to
+51
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🛡️ Proposed fix try:
statuses = evaluate(args.root, args.config)
- except (FileNotFoundError, ValueError) as exc:
+ except (FileNotFoundError, ValueError, KeyError) as exc:
print(f"error: {exc}", file=sys.stderr)
return 2🤖 Prompt for AI Agents |
||
|
|
||
|
|
||
| @dataclass(frozen=True) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Create the upgrade branch before running migration.
Step 2 mutates configuration, hooks, and documentation, but the branch is created only in Step 3. This contradicts the “runs on a branch” guarantee and can leave migration changes on the operator’s current branch. Move the branch-creation step before
./init.sh.Also applies to: 82-86
🤖 Prompt for AI Agents