frontface-flashy 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ryan Wesley Hitchcock
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,175 @@
1
+ Metadata-Version: 2.4
2
+ Name: frontface-flashy
3
+ Version: 1.0.0
4
+ Summary: Persistent execution discipline for long tasks: lock the objective, track milestones, detect drift and artificial stops, verify before done
5
+ Author: Ryan Wesley Hitchcock
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/ohpersonperson/frontface
8
+ Keywords: execution,task-management,state-machine,agents,workflow
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Requires-Python: >=3.10
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Dynamic: license-file
16
+
17
+ # Flashy — persistent execution discipline
18
+
19
+ Prevents an agent from abandoning a build halfway. Locks the objective,
20
+ expands it into milestones, tracks live execution state, scans for drift
21
+ after every milestone, detects artificial stops ("here's a starting
22
+ point"), checkpoints honestly on genuine limits, and gates completion on
23
+ a quality checklist.
24
+
25
+ Mission rule: **a draft is not a delivery.** Completion is measured by
26
+ whether the user actually received the usable thing they asked for.
27
+
28
+ No prerequisites. The mechanism is the whole product.
29
+
30
+ *(Lineage: this is the front-facing conversion of the `flashy` skill from
31
+ Ryan's skill-suite, MIT — itself a port of an execution-discipline
32
+ protocol into skill format. What changed in the conversion is documented
33
+ in CHANGELOG.md.)*
34
+
35
+ ## What it does
36
+
37
+ For any substantial build/create/write task, run the core loop — **lock,
38
+ move, verify**:
39
+
40
+ 1. **Lock the objective.** Extract three things from the request: the
41
+ stated deliverable, the implicit quality bar ("a login form" implies
42
+ validation and error states even if unsaid), and the implicit scope
43
+ boundaries. Once locked, the objective is immutable for the rest of the
44
+ task: subtasks don't replace the mission, explanations don't replace
45
+ execution, planning doesn't replace progress.
46
+ 2. **Expand into milestones.** Objective → Major Milestones → Subtasks →
47
+ Implementation Steps → Verification → Delivery.
48
+ 3. **Track execution state.** A real state machine —
49
+ `LOCKED → PLANNING → EXECUTING → VERIFYING → DONE`, with `BLOCKED`
50
+ reachable from `EXECUTING`/`VERIFYING` — enforces the transition rules
51
+ in code: no `VERIFYING → DONE` without every Quality Gate box checked,
52
+ no silent `EXECUTING → PLANNING` without an explicit user-driven scope
53
+ change, no `BLOCKED → EXECUTING` without an emitted checkpoint.
54
+ 4. **Scan for drift after every milestone.** Four questions: still solving
55
+ the original problem? wandered into explanation instead of building?
56
+ forgot earlier requirements? would the user call this finished? On
57
+ drift: discard the thread, restate the objective, resume from the last
58
+ verified milestone.
59
+ 5. **Detect artificial stops.** "Here's a starting point," "the rest follows
60
+ similarly," "due to space..." — these are execution failures, not
61
+ conclusions. The package scans output for the phrase list and flags
62
+ them; the operator rewrites the impulse as **Continue Working.**
63
+ 6. **Checkpoint honestly on genuine limits.** Context cutoff, tool error,
64
+ or a low-confidence blocker with no safe default — emit a checkpoint
65
+ with a resume token (`finish:<slug>-m<N>`) and continue next turn.
66
+ Fatigue and "this feels done" are failures, not checkpoints.
67
+ 7. **Verify before done.** The seven-box Quality Gate is a hard gate, not
68
+ a vibe check. Any unchecked box = IN PROGRESS.
69
+
70
+ **Execution bias: build before briefing.** Build → Verify → Polish →
71
+ Explain. Understanding the problem well enough to act is the signal to
72
+ stop analyzing and start executing. And the end-of-response gate: a
73
+ response ending is not a task ending — if useful progress can still be
74
+ made, keep executing; don't hand the turn back for permission.
75
+
76
+ **Pause only for implementation-changing decisions** — choice of
77
+ database/language/framework, deployment target, a required credential, or
78
+ an ambiguity where two reasonable interpretations produce meaningfully
79
+ different deliverables. "Continue?" / "Should I keep going?" are never
80
+ valid stopping points.
81
+
82
+ ## Why it works
83
+
84
+ Most abandoned builds don't fail — they *decay*. The objective quietly
85
+ gets replaced by a subtask. Explanation replaces execution. A response
86
+ "feels long enough" and stops. Flashy attacks the decay mechanically:
87
+
88
+ - **The state machine** makes the rules structural: you cannot declare
89
+ done without the gate, cannot silently re-plan, cannot resume from a
90
+ block without a checkpoint. The failure modes are `TransitionError`s,
91
+ not suggestions.
92
+ - **Artificial-stop detection** catches the exact phrases long tasks die
93
+ on — as a string scan, in code, every time.
94
+ - **The confidence engine** (High/Medium/Low) resolves ambiguity without
95
+ blocking: High proceeds silently, Medium proceeds stating the
96
+ assumption inline, only Low pauses. Most ambiguity never surfaces as a
97
+ stop.
98
+ - **Checkpoints with resume tokens** make interruption recoverable without
99
+ the user re-explaining anything — which removes the incentive to
100
+ quietly abandon a task at a session break.
101
+
102
+ ## Quickstart
103
+
104
+ Zero dependencies beyond Python 3.10+. No model, no network — this
105
+ package is pure discipline machinery.
106
+
107
+ ```bash
108
+ cd flashy
109
+ python3 -m unittest discover -s tests # 41 offline tests — prove the machinery first
110
+ python3 examples/demo_session.py # watch a session run LOCKED -> DONE
111
+ ```
112
+
113
+ Or use it as a library:
114
+
115
+ ```python
116
+ from flashy import MissionSession
117
+
118
+ session = MissionSession(
119
+ "Deliver a functional, usable, in-browser debate simulator.",
120
+ slug="debate-sim",
121
+ )
122
+ session.plan(["Architecture", "UI", "Logic", "Persistence", "Testing", "Delivery"])
123
+ session.start_executing()
124
+
125
+ for milestone in ["Architecture", "UI", "Logic", "Persistence", "Testing", "Delivery"]:
126
+ assert session.end_of_response_gate() == "continue"
127
+ session.complete_milestone(milestone)
128
+ print(session.dashboard())
129
+
130
+ session.begin_verification()
131
+ session.gate.check_all()
132
+ session.finish() # raises unless every box is checked
133
+ assert session.machine.state == "DONE"
134
+ ```
135
+
136
+ ## Inputs, outputs
137
+
138
+ - **Input:** a locked objective (the finished thing, not the artifact
139
+ type), milestones, constraints, pending decisions.
140
+ - **Output:** a driven session — dashboard state, checkpoints with resume
141
+ tokens, and a DONE that actually means done. The package never writes
142
+ files; your harness owns persistence.
143
+
144
+ ## Worked examples
145
+
146
+ Three in `examples/`, a stranger can follow all of them:
147
+
148
+ 1. `example-1-debate-simulator-walkthrough.md` — the full loop end to end
149
+ on a browser debate simulator (ported from the original skill, unchanged).
150
+ 2. `example-2-anti-patterns.md` — before/after pairs: artificial-stop
151
+ language vs. the corrected continuation (ported, unchanged).
152
+ 3. `example-3-flashy-session.md` — the machinery itself: every
153
+ transition, gate, and checkpoint shown as the code enforces it. Run
154
+ `demo_session.py` to watch it execute.
155
+
156
+ *All fields are constructed for teaching — they show the discipline's
157
+ shape, not real findings.*
158
+
159
+ ## FAQ
160
+
161
+ **Does this make the model smarter?** No. It makes it finish things.
162
+ Execution discipline, not reasoning quality.
163
+
164
+ **Does it need an internet connection?** No. No model calls at all — the
165
+ 41 unit tests and the demo run fully offline.
166
+
167
+ **What does it cost?** Nothing. Free, dependency-free, offline.
168
+
169
+ **When should it *not* fire?** Trivial tasks (a single short answer),
170
+ genuine user decisions pending, safety constraints, or real platform
171
+ limits. Never deactivate because a response "feels long enough."
172
+
173
+ ## License
174
+
175
+ MIT. See LICENSE.
@@ -0,0 +1,159 @@
1
+ # Flashy — persistent execution discipline
2
+
3
+ Prevents an agent from abandoning a build halfway. Locks the objective,
4
+ expands it into milestones, tracks live execution state, scans for drift
5
+ after every milestone, detects artificial stops ("here's a starting
6
+ point"), checkpoints honestly on genuine limits, and gates completion on
7
+ a quality checklist.
8
+
9
+ Mission rule: **a draft is not a delivery.** Completion is measured by
10
+ whether the user actually received the usable thing they asked for.
11
+
12
+ No prerequisites. The mechanism is the whole product.
13
+
14
+ *(Lineage: this is the front-facing conversion of the `flashy` skill from
15
+ Ryan's skill-suite, MIT — itself a port of an execution-discipline
16
+ protocol into skill format. What changed in the conversion is documented
17
+ in CHANGELOG.md.)*
18
+
19
+ ## What it does
20
+
21
+ For any substantial build/create/write task, run the core loop — **lock,
22
+ move, verify**:
23
+
24
+ 1. **Lock the objective.** Extract three things from the request: the
25
+ stated deliverable, the implicit quality bar ("a login form" implies
26
+ validation and error states even if unsaid), and the implicit scope
27
+ boundaries. Once locked, the objective is immutable for the rest of the
28
+ task: subtasks don't replace the mission, explanations don't replace
29
+ execution, planning doesn't replace progress.
30
+ 2. **Expand into milestones.** Objective → Major Milestones → Subtasks →
31
+ Implementation Steps → Verification → Delivery.
32
+ 3. **Track execution state.** A real state machine —
33
+ `LOCKED → PLANNING → EXECUTING → VERIFYING → DONE`, with `BLOCKED`
34
+ reachable from `EXECUTING`/`VERIFYING` — enforces the transition rules
35
+ in code: no `VERIFYING → DONE` without every Quality Gate box checked,
36
+ no silent `EXECUTING → PLANNING` without an explicit user-driven scope
37
+ change, no `BLOCKED → EXECUTING` without an emitted checkpoint.
38
+ 4. **Scan for drift after every milestone.** Four questions: still solving
39
+ the original problem? wandered into explanation instead of building?
40
+ forgot earlier requirements? would the user call this finished? On
41
+ drift: discard the thread, restate the objective, resume from the last
42
+ verified milestone.
43
+ 5. **Detect artificial stops.** "Here's a starting point," "the rest follows
44
+ similarly," "due to space..." — these are execution failures, not
45
+ conclusions. The package scans output for the phrase list and flags
46
+ them; the operator rewrites the impulse as **Continue Working.**
47
+ 6. **Checkpoint honestly on genuine limits.** Context cutoff, tool error,
48
+ or a low-confidence blocker with no safe default — emit a checkpoint
49
+ with a resume token (`finish:<slug>-m<N>`) and continue next turn.
50
+ Fatigue and "this feels done" are failures, not checkpoints.
51
+ 7. **Verify before done.** The seven-box Quality Gate is a hard gate, not
52
+ a vibe check. Any unchecked box = IN PROGRESS.
53
+
54
+ **Execution bias: build before briefing.** Build → Verify → Polish →
55
+ Explain. Understanding the problem well enough to act is the signal to
56
+ stop analyzing and start executing. And the end-of-response gate: a
57
+ response ending is not a task ending — if useful progress can still be
58
+ made, keep executing; don't hand the turn back for permission.
59
+
60
+ **Pause only for implementation-changing decisions** — choice of
61
+ database/language/framework, deployment target, a required credential, or
62
+ an ambiguity where two reasonable interpretations produce meaningfully
63
+ different deliverables. "Continue?" / "Should I keep going?" are never
64
+ valid stopping points.
65
+
66
+ ## Why it works
67
+
68
+ Most abandoned builds don't fail — they *decay*. The objective quietly
69
+ gets replaced by a subtask. Explanation replaces execution. A response
70
+ "feels long enough" and stops. Flashy attacks the decay mechanically:
71
+
72
+ - **The state machine** makes the rules structural: you cannot declare
73
+ done without the gate, cannot silently re-plan, cannot resume from a
74
+ block without a checkpoint. The failure modes are `TransitionError`s,
75
+ not suggestions.
76
+ - **Artificial-stop detection** catches the exact phrases long tasks die
77
+ on — as a string scan, in code, every time.
78
+ - **The confidence engine** (High/Medium/Low) resolves ambiguity without
79
+ blocking: High proceeds silently, Medium proceeds stating the
80
+ assumption inline, only Low pauses. Most ambiguity never surfaces as a
81
+ stop.
82
+ - **Checkpoints with resume tokens** make interruption recoverable without
83
+ the user re-explaining anything — which removes the incentive to
84
+ quietly abandon a task at a session break.
85
+
86
+ ## Quickstart
87
+
88
+ Zero dependencies beyond Python 3.10+. No model, no network — this
89
+ package is pure discipline machinery.
90
+
91
+ ```bash
92
+ cd flashy
93
+ python3 -m unittest discover -s tests # 41 offline tests — prove the machinery first
94
+ python3 examples/demo_session.py # watch a session run LOCKED -> DONE
95
+ ```
96
+
97
+ Or use it as a library:
98
+
99
+ ```python
100
+ from flashy import MissionSession
101
+
102
+ session = MissionSession(
103
+ "Deliver a functional, usable, in-browser debate simulator.",
104
+ slug="debate-sim",
105
+ )
106
+ session.plan(["Architecture", "UI", "Logic", "Persistence", "Testing", "Delivery"])
107
+ session.start_executing()
108
+
109
+ for milestone in ["Architecture", "UI", "Logic", "Persistence", "Testing", "Delivery"]:
110
+ assert session.end_of_response_gate() == "continue"
111
+ session.complete_milestone(milestone)
112
+ print(session.dashboard())
113
+
114
+ session.begin_verification()
115
+ session.gate.check_all()
116
+ session.finish() # raises unless every box is checked
117
+ assert session.machine.state == "DONE"
118
+ ```
119
+
120
+ ## Inputs, outputs
121
+
122
+ - **Input:** a locked objective (the finished thing, not the artifact
123
+ type), milestones, constraints, pending decisions.
124
+ - **Output:** a driven session — dashboard state, checkpoints with resume
125
+ tokens, and a DONE that actually means done. The package never writes
126
+ files; your harness owns persistence.
127
+
128
+ ## Worked examples
129
+
130
+ Three in `examples/`, a stranger can follow all of them:
131
+
132
+ 1. `example-1-debate-simulator-walkthrough.md` — the full loop end to end
133
+ on a browser debate simulator (ported from the original skill, unchanged).
134
+ 2. `example-2-anti-patterns.md` — before/after pairs: artificial-stop
135
+ language vs. the corrected continuation (ported, unchanged).
136
+ 3. `example-3-flashy-session.md` — the machinery itself: every
137
+ transition, gate, and checkpoint shown as the code enforces it. Run
138
+ `demo_session.py` to watch it execute.
139
+
140
+ *All fields are constructed for teaching — they show the discipline's
141
+ shape, not real findings.*
142
+
143
+ ## FAQ
144
+
145
+ **Does this make the model smarter?** No. It makes it finish things.
146
+ Execution discipline, not reasoning quality.
147
+
148
+ **Does it need an internet connection?** No. No model calls at all — the
149
+ 41 unit tests and the demo run fully offline.
150
+
151
+ **What does it cost?** Nothing. Free, dependency-free, offline.
152
+
153
+ **When should it *not* fire?** Trivial tasks (a single short answer),
154
+ genuine user decisions pending, safety constraints, or real platform
155
+ limits. Never deactivate because a response "feels long enough."
156
+
157
+ ## License
158
+
159
+ MIT. See LICENSE.
@@ -0,0 +1,27 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "frontface-flashy"
7
+ version = "1.0.0"
8
+ description = "Persistent execution discipline for long tasks: lock the objective, track milestones, detect drift and artificial stops, verify before done"
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ requires-python = ">=3.10"
12
+ authors = [{ name = "Ryan Wesley Hitchcock" }]
13
+ keywords = ["execution", "task-management", "state-machine", "agents", "workflow"]
14
+ classifiers = [
15
+ "Programming Language :: Python :: 3",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Operating System :: OS Independent",
18
+ ]
19
+
20
+ [project.urls]
21
+ Homepage = "https://github.com/ohpersonperson/frontface"
22
+
23
+ [tool.setuptools.packages.find]
24
+ where = ["src"]
25
+
26
+ [tool.pytest.ini_options]
27
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,42 @@
1
+ """flashy — front-facing conversion of the flashy execution skill.
2
+
3
+ Persistent execution discipline for long tasks: lock the objective,
4
+ expand it into milestones, track live state, detect drift and artificial
5
+ stopping points, checkpoint honestly on genuine limits, and gate
6
+ completion on a quality checklist. A draft is not a delivery.
7
+
8
+ No prerequisites. The mechanism is the whole product.
9
+ See README.md for the plain-language version. See CHANGELOG.md for what
10
+ was decoupled from the original skill.
11
+ """
12
+
13
+ from . import checkpoints, detectors, quality_gate, session, state_machine
14
+ from .checkpoints import (
15
+ Checkpoint, CheckpointError, make_resume_token, parse_resume_token,
16
+ )
17
+ from .detectors import (
18
+ ARTIFICIAL_STOP_PHRASES, PERMISSION_PHRASES, DRIFT_QUESTIONS,
19
+ DRIFT_CORRECTION, HIGH, MEDIUM, LOW, CONFIDENCE_LEVELS,
20
+ CONFIDENCE_GUIDANCE, DetectorError, detect_artificial_stop,
21
+ asks_permission_to_continue, should_pause, is_implementation_changing,
22
+ )
23
+ from .quality_gate import QualityGate, GateError, GATE_ITEMS
24
+ from .session import MissionSession, Milestone
25
+ from .state_machine import (
26
+ ExecutionStateMachine, TransitionError,
27
+ LOCKED, PLANNING, EXECUTING, VERIFYING, DONE, BLOCKED,
28
+ )
29
+
30
+ __version__ = "1.0.0"
31
+ __all__ = [
32
+ "checkpoints", "detectors", "quality_gate", "session", "state_machine",
33
+ "Checkpoint", "CheckpointError", "make_resume_token", "parse_resume_token",
34
+ "ARTIFICIAL_STOP_PHRASES", "PERMISSION_PHRASES", "DRIFT_QUESTIONS",
35
+ "DRIFT_CORRECTION", "HIGH", "MEDIUM", "LOW", "CONFIDENCE_LEVELS",
36
+ "CONFIDENCE_GUIDANCE", "DetectorError", "detect_artificial_stop",
37
+ "asks_permission_to_continue", "should_pause", "is_implementation_changing",
38
+ "QualityGate", "GateError", "GATE_ITEMS",
39
+ "MissionSession", "Milestone",
40
+ "ExecutionStateMachine", "TransitionError",
41
+ "LOCKED", "PLANNING", "EXECUTING", "VERIFYING", "DONE", "BLOCKED",
42
+ ]
@@ -0,0 +1,195 @@
1
+ """Checkpoints and resume tokens: honest "not done yet" state.
2
+
3
+ A checkpoint is emitted only on a genuine limit — a context/length limit,
4
+ a session or tool interruption, or a low-confidence blocker with no safe
5
+ default. Never on fatigue, never on "this section feels done," never as a
6
+ soft way to stop early.
7
+
8
+ The resume token is a short, copy-pasteable pointer, not a cryptographic
9
+ token: `finish:<slug>-m<N>` (package:slug-milestoneIndex). Quoting it on
10
+ resume must let the session reconstruct where things stood from the
11
+ checkpoint block alone.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import hashlib
17
+ import json
18
+ import re
19
+ from dataclasses import dataclass, field
20
+
21
+ TOKEN_RE = re.compile(r"^finish:([a-z0-9][a-z0-9-]*)-m(\d+)$")
22
+
23
+
24
+ class CheckpointError(ValueError):
25
+ """A malformed checkpoint or resume token."""
26
+
27
+
28
+ def make_resume_token(slug: str, milestone_index: int) -> str:
29
+ """Build a resume token like `finish:debate-sim-m3`."""
30
+ slug = slug.strip().lower().replace(" ", "-")
31
+ if not slug or milestone_index < 0:
32
+ raise CheckpointError("Resume token needs a slug and a non-negative index.")
33
+ return f"finish:{slug}-m{milestone_index}"
34
+
35
+
36
+ def parse_resume_token(token: str) -> tuple[str, int]:
37
+ """Split a resume token into (slug, milestone_index). Validates format."""
38
+ m = TOKEN_RE.match(token.strip())
39
+ if not m:
40
+ raise CheckpointError(
41
+ f"Malformed resume token: {token!r}. Expected finish:<slug>-m<N>."
42
+ )
43
+ return m.group(1), int(m.group(2))
44
+
45
+
46
+ @dataclass
47
+ class Checkpoint:
48
+ objective: str
49
+ done: list[str] = field(default_factory=list)
50
+ current: str = ""
51
+ remaining: list[str] = field(default_factory=list)
52
+ next_action: str = ""
53
+ resume_token: str = ""
54
+ blockers: list[str] = field(default_factory=list)
55
+ # PARAM-FLASHY-004: SHA-256 binding over the content fields, stamped at
56
+ # write time. resume() verifies it; a mismatch aborts the resume.
57
+ digest: str = ""
58
+ # Set only via resume(allow_manual_edit=True): the operator explicitly
59
+ # acknowledged a hand edit. Rendered into the block so the override is
60
+ # visible, never silent. Excluded from the digest input.
61
+ manual_edit_override: bool = False
62
+
63
+ def __post_init__(self) -> None:
64
+ if not self.objective.strip():
65
+ raise CheckpointError("A checkpoint needs the locked objective.")
66
+ if not self.resume_token.strip():
67
+ raise CheckpointError("A checkpoint needs a resume token.")
68
+ parse_resume_token(self.resume_token) # validate format now
69
+ if not self.digest:
70
+ self.digest = self._compute_digest()
71
+
72
+ def _compute_digest(self) -> str:
73
+ """SHA-256 over the canonical content payload.
74
+
75
+ Covers objective, done-list, and core state. The resume token is a
76
+ pointer, not content; the digest itself and the override flag are
77
+ resume-act metadata — all three are excluded.
78
+ """
79
+ payload = json.dumps(
80
+ {
81
+ "objective": self.objective,
82
+ "done": list(self.done),
83
+ "current": self.current,
84
+ "remaining": list(self.remaining),
85
+ "next_action": self.next_action,
86
+ "blockers": list(self.blockers),
87
+ },
88
+ sort_keys=True,
89
+ )
90
+ return hashlib.sha256(payload.encode("utf-8")).hexdigest()
91
+
92
+ def verify(self) -> None:
93
+ """Verify the checkpoint's content against its digest binding.
94
+
95
+ Raises CheckpointError on a missing digest (predates binding) or a
96
+ mismatch (content modified after the checkpoint was written).
97
+ """
98
+ if not self.digest:
99
+ raise CheckpointError(
100
+ "Checkpoint carries no digest — it predates digest binding "
101
+ "and cannot be integrity-verified. Re-emit the checkpoint "
102
+ "to bind it."
103
+ )
104
+ if self.digest != self._compute_digest():
105
+ raise CheckpointError(
106
+ "Checkpoint digest mismatch: the content was modified after "
107
+ "the checkpoint was written. Resume aborted — re-verify the "
108
+ "checkpoint, or resume with allow_manual_edit=True to "
109
+ "acknowledge the hand edit explicitly."
110
+ )
111
+
112
+ @classmethod
113
+ def resume(cls, text: str, *, allow_manual_edit: bool = False) -> "Checkpoint":
114
+ """Parse a checkpoint block and verify its digest binding.
115
+
116
+ Returns the reconstructed Checkpoint. On digest mismatch the resume
117
+ aborts with CheckpointError — unless allow_manual_edit=True, in
118
+ which case the checkpoint is returned with manual_edit_override set
119
+ (the override is rendered into the block, never silent).
120
+ """
121
+ cp = cls.parse(text)
122
+ if not cp.digest:
123
+ # Pre-binding block: no write-time attestation exists. The
124
+ # bypass is for acknowledged hand edits (digest mismatch), not
125
+ # for unverifiable provenance — refuse outright, no override.
126
+ raise CheckpointError(
127
+ "Checkpoint carries no digest — it predates digest binding "
128
+ "and cannot be integrity-verified. Re-emit the checkpoint "
129
+ "to bind it."
130
+ )
131
+ try:
132
+ cp.verify()
133
+ except CheckpointError:
134
+ if not allow_manual_edit:
135
+ raise
136
+ cp.manual_edit_override = True
137
+ return cp
138
+
139
+ def render(self) -> str:
140
+ lines = [
141
+ "FLASHY CHECKPOINT",
142
+ f"OBJECTIVE -> {self.objective}",
143
+ f"DONE -> {', '.join(self.done) if self.done else '(none yet)'}",
144
+ f"CURRENT -> {self.current or '(not started)'}",
145
+ f"REMAINING -> {', '.join(self.remaining) if self.remaining else '(none)'}",
146
+ f"NEXT -> {self.next_action or '(none recorded)'}",
147
+ f"RESUME -> {self.resume_token}",
148
+ f"DIGEST -> {self.digest}",
149
+ ]
150
+ if self.blockers:
151
+ lines.append(f"BLOCKERS -> {', '.join(self.blockers)}")
152
+ if self.manual_edit_override:
153
+ lines.append("OVERRIDE -> manual edit acknowledged by operator")
154
+ lines.append("")
155
+ lines.append("Continuing...")
156
+ return "\n".join(lines)
157
+
158
+ @classmethod
159
+ def parse(cls, text: str) -> "Checkpoint":
160
+ """Parse a rendered checkpoint back. Raises CheckpointError on bad input."""
161
+ if "FLASHY CHECKPOINT" not in text:
162
+ raise CheckpointError("Not a Flashy checkpoint.")
163
+ fields: dict[str, str] = {}
164
+ for line in text.splitlines():
165
+ if "->" in line:
166
+ key, _, value = line.partition("->")
167
+ fields[key.strip()] = value.strip()
168
+
169
+ def split_list(value: str) -> list[str]:
170
+ value = value.strip()
171
+ if not value or value in ("(none yet)", "(none)"):
172
+ return []
173
+ return [p.strip() for p in value.split(",") if p.strip()]
174
+
175
+ try:
176
+ cp = cls(
177
+ objective=fields["OBJECTIVE"],
178
+ done=split_list(fields.get("DONE", "")),
179
+ current="" if fields.get("CURRENT") == "(not started)" else fields.get("CURRENT", ""),
180
+ remaining=split_list(fields.get("REMAINING", "")),
181
+ next_action="" if fields.get("NEXT") == "(none recorded)" else fields.get("NEXT", ""),
182
+ resume_token=fields["RESUME"],
183
+ blockers=split_list(fields.get("BLOCKERS", "")),
184
+ digest=fields.get("DIGEST", ""),
185
+ manual_edit_override="OVERRIDE" in fields,
186
+ )
187
+ except KeyError as exc:
188
+ raise CheckpointError(f"Checkpoint missing field: {exc}") from exc
189
+ if not fields.get("DIGEST"):
190
+ # Pre-binding block: __post_init__ computed a fresh digest, but
191
+ # no write-time attestation exists — clear it so verify()
192
+ # refuses explicitly instead of blessing content that was never
193
+ # bound.
194
+ cp.digest = ""
195
+ return cp