/recover goal-loop recoveryTwo user-facing improvements, shipped together as 0.51.0 on one branch:
jacked install's static "What you get / Next steps" banner with a concise, accurate summary of what actually changed: the version before → after, and which skills/commands/agents/lenses/templates were added / changed / removed. Surface the same summary in the dashboard/tray update UI./recover goal-loop recovery. When recovering a crashed session, recommend the newest session with real substance (skip a near-empty newest), and if that session was actively driving a /goal or /loop, surface the exact kickoff prompt verbatim so the user can copy-paste it to restart — these cannot be auto-resumed.The root problem: jacked keeps no record of what it previously installed — no manifest, and the prior version isn't durably stored. A small manifest fixes this and powers everything in Part A.
New module jacked/install_manifest.py (pure, no Click/rich, fully unit-testable). Manifest file at ~/.claude/jacked-manifest.json:
{
"version": "0.51.0", // the jacked version that wrote this manifest
"written_at": "2026-06-17T16:40:00Z",
"artifacts": {
"skills": {"recover": "sha256:…", "whats-next": "sha256:…", …},
"commands": {"dc": "sha256:…", …},
"agents": {"readme-maintainer": "sha256:…", …},
"lenses": {…},
"templates": {"plan-template.html": "sha256:…", …}
}
}
Public API:
| Function | Responsibility |
|---|---|
CATEGORIES | Ordered list describing each artifact category: key (skills/commands/agents/lenses/templates), source glob + name-extraction, destination dir + dest-path-for-name. Single source of truth shared by install, prune, and diff. |
hash_source(data_root) -> dict | Walk the package source for every category; return {category: {name: "sha256:…"}} of the current shipped artifacts (hash of file bytes). |
load(path=DEFAULT) -> dict | None | Read + JSON-parse the manifest; None if missing/corrupt (corrupt → treat as first install, logged). |
diff(prior, current_hashes) -> ManifestDiff | Per category: added (name in current, not in prior), changed (in both, hash differs), removed (in prior, not in current), unchanged. prior=None → everything is added (first manifested install). |
write(path, version, current_hashes) | Atomic write (temp + rename) of the new manifest. |
Diffing by content hash of the source (current-source vs prior-manifest) is correct in both copy installs (PyPI) and editable/symlink dev installs — it compares "what we ship now" against "what we shipped last time," never the lossy dest-vs-source comparison.
The install command (jacked/cli.py) is refactored to drive the manifest:
prior_version (or None).diff(prior, current).removed name (present in the prior manifest, so jacked-owned), delete its destination (skills: rmtree the skill dir; others: unlink the file). The manifest is the ownership proof — the user's own skills/commands are never in it, so never touched.~/.claude/jacked-last-install.json (see A3) — the structured summary record.Terminal summary (rich, slate/blue-agnostic — uses the existing [green]/[yellow]/→ vocabulary). Upgrade example:
Jacked upgraded 0.50.0 → 0.51.0
Skills + recover new
Commands ~ whats-next updated
Agents − legacy-helper removed
34 unchanged
→ Restart Claude Code to load changes.
First install (no prior manifest): header reads Jacked installed — 0.51.0 and every artifact is listed as new (per the chosen "concise always" output style). If nothing changed on a re-run: Jacked 0.51.0 — already up to date (N artifacts unchanged).
--json flag on install: emit the structured summary to stdout (and suppress the human render), shape identical to jacked-last-install.json below. Lets any caller capture the diff programmatically.
Rather than scrape install's stdout from two different update paths, install self-records: it always writes ~/.claude/jacked-last-install.json:
{
"at": "2026-06-17T16:40:01Z",
"from_version": "0.50.0", // null on first install
"to_version": "0.51.0",
"changes": {
"skills": {"added": ["recover"], "changed": [], "removed": []},
"commands": {"added": [], "changed": ["whats-next"], "removed": []},
"agents": {"added": [], "changed": [], "removed": ["legacy-helper"]},
"lenses": {"added": [], "changed": [], "removed": []},
"templates":{"added": [], "changed": [], "removed": []}
},
"unchanged_count": 34
}
Both update paths get the summary for free because the record is produced by install itself, which both paths run.
GET /api/install/summary (in jacked/api/routes/system.py) → returns {summary: <jacked-last-install.json or null>, mtime_iso}.jacked/data/web/js/): a new small component renders a one-shot "What changed in this update" panel (slate .stat-card + .badge styling) — shown after a post-upgrade reload when /api/install/summary is fresh and unseen (a localStorage "seen at" marker prevents re-showing). Wired into both the dashboard upgrade-modal completion (header.js) and initial app load (app.js).update.html (standalone tray-update page): on overall==="succeeded", fetch /api/install/summary and render the added/changed/removed lists (its own inline dark palette)./recover goal-loop + near-empty pickTwo focused additions to jacked/recover.py + a skill update. No change to the CLI's two-phase shape.
Candidates are already ranked newest-first by last-timestamped line. Add recommend_index(candidates, min_msgs=MIN_SUBSTANCE_MSGS): return the index of the first candidate whose msg_count >= min_msgs (default MIN_SUBSTANCE_MSGS = 4); if none qualify, fall back to index 0 (absolute newest). The CLI's chosen becomes candidates[recommend_index(...)]; the full candidate list (including any skipped near-empty newest) is still returned as alternates. This matches "newest unless the very newest is nearly empty."
A /goal or /loop run cannot be auto-resumed — it only continues when the user pastes the command into a live Claude Code session. So when the recommended session was driving one and it looks like it was still active at the crash, /recover surfaces the exact kickoff verbatim for copy-paste.
/goal and /loop invocations. The command lives in the command wrapper (<command-name>/loop</command-name> + <command-args>…</command-args>) or as a raw line beginning /goal / /loop . Reconstruct the verbatim kickoff (/loop <args> or /goal <args>), preserving the original text exactly. Record each with its position (record index)./goal or /loop invocation falls within the transcript tail window — default the last TAIL_WINDOW = 10 records — with no later explicit stop signal (a subsequent user line containing a stop instruction for that command). A kickoff that appears early with substantial unrelated work after it is treated as finished and is not surfaced.Digest.resumable_commands: list[dict], each {type: "goal"|"loop", kickoff: "<verbatim>"} — populated only when the active gate passes (usually 0 or 1 entries).## ⚠ Manual restart required
This session was driving a /loop that can't be auto-resumed.
Copy this back into Claude Code to restart it:
/loop 5m /babysit-prs
jacked/data/skills/recover/SKILL.md gains: (a) a note that the recommended pick skips a near-empty newest session (and the user can still choose it as an alternate); (b) a step instructing the agent that if the digest contains a "Manual restart required" block, present that /goal or /loop command to the user to copy-paste, and explicitly state it cannot be auto-run.
| File | Change |
|---|---|
jacked/install_manifest.py | New. Manifest schema, CATEGORIES, hash_source, load, diff, write, ManifestDiff. Pure/stdlib. |
jacked/cli.py | install refactor: manifest-driven diff + prune + manifest write + jacked-last-install.json write + concise summary render + --json flag; remove the verbose banner (keep required-plugin blocker line); move full recommendations behind jacked doctor/--recommend. Make uninstall manifest-aware (clean pruned artifacts too). |
jacked/api/routes/system.py | New route GET /api/install/summary reading jacked-last-install.json. |
jacked/data/web/js/components/*.js, app.js, header.js | New one-shot "What changed" panel + wiring on post-upgrade reload and app load (slate/blue, localStorage seen-marker). |
jacked/data/web/update.html | Render added/changed/removed on succeeded via /api/install/summary. |
jacked/data/web/css/style.css | Styles for the change panel if not covered by existing .stat-card/.badge. |
jacked/recover.py | recommend_index (near-empty skip); goal/loop extraction + active-gate; Digest.resumable_commands + render block. |
jacked/data/skills/recover/SKILL.md | Note the near-empty skip + the "Manual restart required" copy-paste step. |
tests/test_install_manifest.py, tests/test_recover.py, tests/…api… | New/extended. Unit tests (run via uv run python -m pytest). |
jacked/__init__.py, README.md | Version → 0.51.0; document the change-summary + goal-loop recovery. |
diff across added/changed/removed/unchanged and first-install (prior=None); hash_source over a fake data root; load on missing/corrupt; write roundtrip (atomic).jacked-last-install.json shape; --json output equals the record; first-install vs upgrade vs no-op render. Driven through CliRunner against a fake source data-root + temp ~/.claude (env override).GET /api/install/summary via FastAPI TestClient — returns the record + mtime, and null when absent./qa browser pass (panel renders, seen-marker suppresses re-show, slate/blue styling).recommend_index skips a near-empty newest and falls back when all are tiny; goal/loop extraction captures verbatim kickoff; active-gate surfaces when the kickoff is in the tail window and suppresses when it's early-with-later-work; render block present only when non-empty./goal or /loop (impossible from a skill) and does not reconstruct loop schedule/iteration state./goal and /loop (the two non-resumable ones the user named) — not a general non-resumable-command registry.| Scenario | Behavior |
|---|---|
| No prior manifest (first run after this ships) | Treat as first install: everything listed as new, write baseline manifest. No pruning (nothing recorded to prune). |
| Corrupt manifest JSON | load returns None (logged); treated as first install. Never crash the install. |
| Prune target already gone / not writable | Skip with a logged warning; never abort install. |
| Editable/dev install (symlinks) | Hash-of-source diff still works (compares source-now vs manifest); version arrow still correct. |
jacked-last-install.json unreadable in UI | /api/install/summary returns {summary: null}; UI shows nothing (no error toast). |
Session has a /goal//loop early but lots of later unrelated work | Active-gate fails → not surfaced (treated as finished). |
| Recommended session is near-empty and so are all others | recommend_index falls back to absolute newest; all candidates still listed. |
MIN_SUBSTANCE_MSGS (default 4) and TAIL_WINDOW (default 10) — sensible defaults; tune against real transcripts during implementation.jacked doctor vs a new jacked install --recommend flag — decide during implementation against the existing doctor command's scope.Generated with the jacked HTML artifact template.