Commit 1a4f1c9
authored
feat: add /PACT:refresh mid-workstream context checkpoint (v4.6.0) (#1150)
* refactor(hooks): remove dormant hooks/refresh package + legacy pact-refresh janitor
Consumer-scan census (AST-anchored: Import + ImportFrom + string-literal
patch-seams; comment mentions excluded by construction; 314 .py files across
hooks/ scripts/ telegram/ skills/*/scripts/ tests/):
- Zero non-test consumers of hooks/refresh/.
- Nine consumers, all under tests/, each importing ONLY the refresh package;
whole-file deleted with it. (One shared.constants.PACT_AGENTS helper import
in test_patterns.py is not a deletion target.)
- String-seams (refresh.constants.LARGE_FILE_THRESHOLD_BYTES x4) live only
inside deleted test_transcript_parser.py.
- The package docstring's PreCompact-consumer claim is stale prose, not a
consumer.
session_end.py: drop _CHECKPOINT_MAX_AGE_DAYS, _cleanup_old_checkpoints, and
its single main() call site. test_session_end.py: 18 janitor couplings removed.
test_py39_annotation_compat.py: discovery floor 63->55 (scanned hooks 66->58,
same slack; deliberate lowering documented inline).
Full suite: 10466 passed, 11 skipped, 0 failed, 0 errors.
* feat(journal): register session_refreshed + session_refresh_consumed event schemas
session_journal.py: session_refreshed required {consolidation_completed: bool,
halt_active: bool} + session_refresh_consumed required {refresh_ts: str}, with
7 optional fields registered (halt_task_ids, feature_task_id, feature_subject,
team_name, next_phase, worktrees, pr_number). Registry-convention comments
adjacent to session_paused.
Protocol rows added to pact-state-recovery.md with identical bytes mirrored
into the pact-protocols.md SSOT region (same commit; extract parity PASSED).
Tests: schema samples (minimal-event oracle), wrong-type sweeps, optional-
fields pin, full-payload happy path, CLI subprocess round-trips including
consumption bound to the surfaced refresh_ts. 19 net-new tests.
Full suite: 10485 passed, 11 skipped, 0 failed, 0 errors.
* feat(resume): add check_resume_state entry point with fail-safe refresh interpreter
check_resume_state(prev_session_dir): single public resume seam — one read per
event type, spent-gate, per-leg interpretation, arbitration only when both
claims survive.
_interpret_refreshed_event: totality by signature (returns str, no None path;
fail-safe-toward-surfacing stated as MUST). Composition rules include the 48h
STALE downgrade (never suppression), verbatim machine-copyable refresh_ts=
echo, UNAVAILABLE sentinel, and the RESPAWN-BEFORE-SEND trailer.
_refresh_is_spent: exact-string claim binding AND >= temporal-sanity second
conjunct; every failure path lands UNSPENT (suppress-direction belt).
_arbitrate: parsed-ts newest-wins, ties to refreshed, losing claim mentioned,
unparseable ts surfaces BOTH with a CONFLICT note. has_unspent_refresh:
presentation-only boolean, any-error-False. Pause leg split into
_interpret_paused_event with byte-identical logic; existing surfaces and all
84 prior resume tests unchanged.
Tests +34: fail-safe tier, interpreter fail-direction counter-test, spent-check
matrix (orphan consumption, two-refreshes-one-consumed, unparseable), HALT
adversarial sweep, arbitration orders/ties, pr_number never gates.
Full suite: 10529 passed, 11 skipped, 0 failed, 0 errors.
* feat(session-init): surface refreshed state at compact and startup seams
Seam A: step-8 call swapped to check_resume_state inside the frame_is_lead
gate (placement and append semantics unchanged). Seam B: the compact branch
computes refresh_pending once via has_unspent_refresh; when true, the
Re-engage-secretary sentence is replaced with the RESPAWN-BEFORE-SEND
directive, the auto-compacted wording is neutralized, and the agent list is
re-labeled "Pre-refresh agents (STOPPED - respawn before messaging)"; when
false the branch text is byte-identical (test-pinned).
Both session_init docstrings reworded byte-equal (step 8: paused or refreshed
work). Mock migration: 55 exact patch(check_paused_state) seams across 8
files re-pointed to check_resume_state; spy-wrapper test reworked around the
new entry point; direct-function tests on the public check_paused_state
wrapper intentionally stay.
TestRefreshSurfacingMatrix (10 non-mocked seam tests, real main() round-trip):
lead x {compact,resume,startup} surfaces prompt + HALT line; non-lead frames
never receive it; quit-path topology; byte-identity without refresh;
spent-refresh reverts to legacy wording. Counter-test-by-revert executed:
reverting the step-8 swap turns exactly 5 tests red (cardinality documented).
Full suite: 10539 passed, 11 skipped, 0 failed, 0 errors.
* feat(session-end): extend paused-session journal guard to checkpointed sessions
_is_checkpointed_session: pure existence-OR over {session_paused,
session_refreshed}, no timestamp comparison (the refresh-then-quit race makes
any ts-ordering guard unsound; existence semantics transfer the prior race
lessons verbatim). Single call-site swap at the cleanup_old_sessions threshold
select. _is_paused_session kept intact (existing tests unchanged; retained
per design as the paused-leg primitive).
Scope note documented in the guard: the tasks/teams reapers remain
journal-blind at 30d, so past 30d a refreshed session degrades to journal-only
fidelity where the loud-mismatch warning is the designed behavior.
Tests +9: 5-row existence-OR truth table, refresh-then-quit race, no-ts event
still counts, missing journal, 35d refreshed journal survives the active TTL,
ages out past the checkpoint bound, non-checkpointed 35d control still reaped.
Full suite: 10550 passed, 11 skipped, 0 failed, 0 errors.
* feat(commands): add refresh.md mid-workstream checkpoint + bootstrap.md resume clauses
commands/refresh.md (new): mid-workstream context checkpoint — preflight
degenerate-case handling (confirm, then unconditional minimal event), HARVEST
with an explicit drain-confirmation gate before shutdown, read-only
PERSIST/VERIFY with full payload computation, journal writes mirroring the
pause byte-conventions (shell-clamped pre-emit, quoted-heredoc --stdin),
staggered graceful-then-TaskStop shutdown (roster-entry removal expected;
team config file preserved), and STANDBY (no new dispatches,
RESPAWN-BEFORE-SEND, expect-and-ignore post-shutdown stragglers, /compact
canonical with /clear tolerated).
commands/bootstrap.md: secretary same-session respawn carve-out; refreshed-
state paragraph (declared continuation, auto-proceed, ask only on
inconsistency, HALT always surfaces); fire-once consumption write template
bound to the surfaced refresh_ts (UNAVAILABLE skips the write); team-config
bidirectional re-identify + ghost-detection clause.
plugin.json: register ./commands/refresh.md (manifest-parity gate requires
registration; version bump is deliberately separate).
Structural pins for both command files + a consolidated-preemit no-false-warn
integration test.
Full suite: 10567 passed, 11 skipped, 0 failed, 0 errors.
* chore: add refresh live-probe runbook + bump version to v4.6.0
* test(refresh): remove orphaned refresh fixtures, pin relabel full-absence
Certification pass over the refresh feature. Coverage audit: every scenario-
matrix bucket covered by the shipped tests; one gap filled (full-absence pin
on the Active Agents relabel — a count=1 replace could leave a duplicate
occurrence under upstream drift). Counter-test-by-revert independently
re-measured: exactly 5 failed / 5 passed, same named tests, failing for the
right reason; restored clean.
Orphan cleanup (sister closure of the dormant-package removal): delete
tests/fixtures/refresh_system.py + the six conftest re-exports + the five
sample_transcripts jsonl fixtures — zero consumers confirmed by independent
AST scan (299 files) plus proxy-bypassed grep, both sides.
Drift-guard ruling: no new test — both str.replace targets are double-pinned
through the real builder (byte-identity pins upstream literals; relabel test
pins replaced output positively); every drift combination lands red.
Full suite after edits: 10567 passed, 11 skipped, 0 failed, 0 errors;
extract parity 19/19 PASS.
* test(refresh): pin frame_is_lead conjunct on compact presentation seam + de-cite removed refresh package in test prose
The lead-gate conjunct on the compact-branch refresh presentation was
empirically unpinned (dropping it flipped zero tests). New negative assertion
scoped to the unknown-role row — the one non-lead shape that reaches the
compact branch; re-verified: the conjunct-drop mutation now flips exactly 1
test (vs 0 before), 340 others green under the mutation, file restored
byte-identical.
test_pact_context: rename + docstrings de-cite the deleted checkpoint_builder
(zero external references to the old name; executed logic byte-identical).
test_hook_infra_classifier: refresh-edge backstop reworded as a reintroduction
canary.
Full suite: 10579 passed, 11 skipped, 0 failed, 0 errors.
* docs(refresh): correct session-start seam attribution, add team_name to checkpoint payload, note quit-path fire-once topology
Protocol docs: the session-start row now attributes resume detection to
check_resume_state() (both the extract and its SSOT region, same commit —
the byte-parity gate cannot catch consistently-stale pairs, so content
correctness was verified against the live call site).
refresh.md: team_name added to the payload computation, full-payload write
template, and field table (registered optional field; gives bootstrap a free
identity cross-check). The degenerate minimal-event variant deliberately
stays required-fields-only.
bootstrap.md: one contract-honesty sentence — quit-path fire-once is enforced
by the one-hop-back read topology; ts-bound consumption covers the /compact
and same-session --resume paths.
Extract parity: 19/19 PASSED. Full suite green (10575 passed, 0 failed,
0 errors).
* docs(bootstrap): enumerate team_name mismatch as refreshed-state inconsistency
* fix(refresh): sanitize resume-prompt field interpolation, preserve HALT line for superseded claims, warn on incomplete consolidation, compose checkpoint predicate
_sanitize_prompt_field: control chars (incl. U+2028/U+2029) collapse to
spaces; 200-char free-text bound, 512 for worktree path elements; internal
failure drops the field's line only — never the prompt.
_arbitrate: a losing HALT-carrying refresh claim is now rendered "superseded"
(never "stale") with its HALT verify-line appended; composition extracted to
a shared _compose_halt_line so the interpreter and arbitration paths can
never render the same event's HALT state differently.
Refreshed interpreter: capture-knowledge warning when consolidation_completed
is explicitly false (absent/malformed keeps the degraded floor — the field is
required and bool-validated at write time). ts validity now also requires the
control-char class to be absent: a control-char ts renders the UNAVAILABLE
branch instead of a raw echo (clean ts keeps byte-exact consumption binding;
spend/identity axis untouched).
session_end: _is_checkpointed_session now composes _is_paused_session
(predicate drift closed).
Tests +214 lines: injection vectors (newline directive, control chars, 10k
values, control-char ts), superseded-claim HALT survival, consolidation
warning both values, verbatim clean-ts round-trip control.
Full suite: 10581 passed, 11 skipped, 0 failed, 0 errors.
* fix(refresh): extend prompt sanitizer control-char class to C1 range1 parent 86db244 commit 1a4f1c9
51 files changed
Lines changed: 2432 additions & 8146 deletions
File tree
- .claude-plugin
- pact-plugin
- .claude-plugin
- commands
- hooks
- refresh
- protocols
- tests
- fixtures
- sample_transcripts
- runbooks
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
12 | 12 | | |
13 | 13 | | |
14 | 14 | | |
15 | | - | |
| 15 | + | |
16 | 16 | | |
17 | 17 | | |
18 | 18 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
605 | 605 | | |
606 | 606 | | |
607 | 607 | | |
608 | | - | |
| 608 | + | |
609 | 609 | | |
610 | 610 | | |
611 | 611 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | | - | |
| 3 | + | |
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
| |||
34 | 34 | | |
35 | 35 | | |
36 | 36 | | |
| 37 | + | |
37 | 38 | | |
38 | 39 | | |
39 | 40 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | | - | |
| 3 | + | |
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
14 | 14 | | |
15 | 15 | | |
16 | 16 | | |
| 17 | + | |
| 18 | + | |
17 | 19 | | |
18 | 20 | | |
19 | 21 | | |
| |||
25 | 27 | | |
26 | 28 | | |
27 | 29 | | |
28 | | - | |
| 30 | + | |
29 | 31 | | |
30 | 32 | | |
31 | 33 | | |
32 | 34 | | |
33 | 35 | | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
34 | 49 | | |
35 | 50 | | |
36 | 51 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
This file was deleted.
0 commit comments