@opengsd/gsd-path 1.6.1 → 1.6.2

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.
package/CHANGELOG.md CHANGED
@@ -11,6 +11,19 @@ app and the daemon have their own file,
11
11
  [daemon/app/CHANGELOG.md](daemon/app/CHANGELOG.md). Release bookkeeping and
12
12
  test-only changes are not listed; the Git history has them.
13
13
 
14
+ ## [1.6.2] - 2026-10-17
15
+
16
+ ### Changed
17
+ - record 1.6.2 live host release evidence
18
+ - prepare 1.6.2 patch candidate
19
+ - Document README What's new heading in RELEASE.md
20
+ - update release notes for v1.6.1
21
+
22
+ ### Fixed
23
+ - keep the README What's new section current from release notes
24
+ - read quoted STATE values in the ship checkpoint guard
25
+ - refuse a build checkpoint while STATE is ship/active
26
+ - keep evidence-line citations when final review reuses a wave
14
27
  ## [1.6.1] - 2026-10-16
15
28
 
16
29
  ### Added
package/QUICK.md CHANGED
@@ -14,7 +14,7 @@ One router skill drives a gated pipeline: inspect → define → research → de
14
14
  plan → build → ship. Every handoff lives in `.project/` so any session can resume from disk.
15
15
 
16
16
  **Hosts:** Codex, Claude Code, Grok, OpenCode, Antigravity, Cursor, and Kimi
17
- Code have a passing live milestone for release 1.6.1. Copilot CLI, Qwen, Zed,
17
+ Code have a passing live milestone for release 1.6.2. Copilot CLI, Qwen, Zed,
18
18
  Kiro, and Muse Code install, but have no live milestone for that release. See
19
19
  [host tiers](README.md).
20
20
 
package/README.md CHANGED
@@ -9,11 +9,11 @@ milestone loop. While a milestone builds, the next one can be planned in
9
9
  parallel under `.project/next/` (lookahead); a building milestone can also be
10
10
  abandoned on an explicit ruling, archiving its partial work for a re-slice.
11
11
 
12
- **Hosts with a passing full live milestone for release 1.6.1:** Codex, Claude
12
+ **Hosts with a passing full live milestone for release 1.6.2:** Codex, Claude
13
13
  Code, Grok, OpenCode, Antigravity CLI, Cursor, and Kimi Code.
14
14
 
15
15
  **Hosts with an installer and a dispatch contract, but no live milestone for
16
- release 1.6.1:** GitHub Copilot CLI (account quota exhausted), Qwen Code, Zed,
16
+ release 1.6.2:** GitHub Copilot CLI (account quota exhausted), Qwen Code, Zed,
17
17
  and Kiro (API credits), and Muse Code (no live release evidence recorded yet).
18
18
  These hosts are excluded from the release evaluation. Exclusion is not a pass.
19
19
 
@@ -24,14 +24,16 @@ The [host trust matrix](https://github.com/open-gsd/gsd-path/blob/main/docs/trus
24
24
  is the source of truth for live milestone proof and each host's guard tier.
25
25
 
26
26
  <!-- release-docs -->
27
- **Latest npm release:** [@opengsd/gsd-path@1.6.1](https://www.npmjs.com/package/@opengsd/gsd-path/v/1.6.1) — [release notes](CHANGELOG.md#161---2026-10-16)
27
+ ## What's new in 1.6.2 — 2026-10-17
28
+
29
+ **Latest npm release:** [@opengsd/gsd-path@1.6.2](https://www.npmjs.com/package/@opengsd/gsd-path/v/1.6.2) — [release notes](CHANGELOG.md#162---2026-10-17)
28
30
 
29
31
  **Recent highlights**
30
- - accept GitHub rebase merges in pull-request integration
31
- - run dispatch_driver subprocess helpers from the selected runtime
32
- - refuse a relative model_policy --record and check release before locking
33
- - let promote-next replace a DOCS-AUDIT carry-forward the lookahead keeps
34
- - let the owner authorize one exact pre-ship push to a task branch
32
+ - keep the README What's new section current from release notes
33
+ - read quoted STATE values in the ship checkpoint guard
34
+ - refuse a build checkpoint while STATE is ship/active
35
+ - keep evidence-line citations when final review reuses a wave
36
+ - record 1.6.2 live host release evidence
35
37
 
36
38
  <!-- /release-docs -->
37
39
 
@@ -55,25 +57,6 @@ is the source of truth for live milestone proof and each host's guard tier.
55
57
  | [RELEASE.md](RELEASE.md) | Maintainer CI and npm release cycle |
56
58
  | [GUIDE.md](GUIDE.md) | Pointer to the guides above |
57
59
 
58
- ## What's new — October 2, 2026
59
-
60
- These changes are merged into the source checkout; npm installs use the latest
61
- published release.
62
-
63
- - **Desktop app (pre-release):** one app for macOS, Windows, and Linux shows
64
- your projects, manages skills in each coding agent, and updates itself. See
65
- [Desktop app](#desktop-app).
66
- - **Skills from the published release:** the monitor installs skills from the
67
- npm release, checks the download against the registry's sha512, and refuses
68
- one that does not match. A release picker lets you stay on an older version.
69
- - **Safer monitor writes:** a monitor started by the app accepts changes only
70
- with the app's local token, so another web page cannot change your setup.
71
- - **Environment editor:** edit a project's `.env` files with values masked and
72
- a diff before each save.
73
- - **Usage charts:** tokens and cost per day, time per phase, tasks per wave,
74
- and verify history. Missing data shows as missing, never as zero.
75
- - **Muse Code** is a supported host.
76
-
77
60
  ## Install from npm
78
61
 
79
62
  Requires **Node.js 18.17+** for the npm installer and **Python 3.9+** for
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengsd/gsd-path",
3
- "version": "1.6.1",
3
+ "version": "1.6.2",
4
4
  "description": "Disk-backed AI agent pipeline. Docs: DOCS.md (start here), QUICK.md, FULL.md, UPDATE.md",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -4502,6 +4502,20 @@ def attest(
4502
4502
  }
4503
4503
 
4504
4504
 
4505
+ def _committed_ship_active(repo: Path) -> bool:
4506
+ """True when the STATE.md committed at HEAD is ship/active."""
4507
+
4508
+ result = run_git(repo, "show", "HEAD:.project/STATE.md")
4509
+ if result.returncode != 0:
4510
+ return False
4511
+ frontmatter = result.stdout.split("\n---", 1)[0]
4512
+ fields = {
4513
+ key: _unquote(value)
4514
+ for key, value in re.findall(r"(?m)^(phase|status):(.*)$", frontmatter)
4515
+ }
4516
+ return fields.get("phase") == "ship" and fields.get("status") == "active"
4517
+
4518
+
4505
4519
  def checkpoint(
4506
4520
  repo: Path,
4507
4521
  expected_head: str,
@@ -4551,6 +4565,12 @@ def checkpoint(
4551
4565
  "subject": subject.strip(),
4552
4566
  }
4553
4567
  raise IsolationError("primary HEAD differs from --expected-head")
4568
+ if subject.strip().startswith("build:") and _committed_ship_active(repo):
4569
+ raise IsolationError(
4570
+ "STATE is ship/active: final review creates no pre-ship commit; leave its "
4571
+ "outputs uncommitted for archive_milestone.py, and send findings through "
4572
+ "the patch plan (see SHIP.md)"
4573
+ )
4554
4574
  pending = uncommitted_paths(repo)
4555
4575
  forced: Set[str] = set()
4556
4576
  # Build only: an approval undo resets to a parent without the ledger and would drop it.
@@ -108,10 +108,14 @@ def _final_view(relative, text, criteria, reviewed, head, surfaces=None):
108
108
  fields[field] = contracts._raw_source_field(block, field, relative)
109
109
  if "Surface" in fields and not {"Check", "Observed"}.issubset(fields):
110
110
  raise contracts.HandoffError(f"{criterion} lacks its recorded surface walkthrough")
111
+ reference = f"{relative} — {criterion}"
112
+ if "Observed" in fields and evidence:
113
+ # The reviewer may cite held-out and judgment ids on the evidence line.
114
+ reference += f"; evidence: {evidence}"
111
115
  lines += [f"### {criterion} — {title}", "", "- **Verdict**: met",
112
116
  f"- **Check**: {fields.get('Check', 'none')}",
113
117
  f"- **Observed**: {fields.get('Observed', evidence)}",
114
- f"- **Reference**: {relative} — {criterion}",
118
+ f"- **Reference**: {reference}",
115
119
  "- **Finding**: none", "- **Fix direction**: none"]
116
120
  if "Surface" in fields:
117
121
  expected = (surfaces or {}).get(criterion)
@@ -130,7 +130,9 @@ STATE.md-only "finalize" checkpoint for work already checkpointed. The helper
130
130
  refuses a `build:` checkpoint that deletes or renames a `.project/tasks/`
131
131
  brief unless the canonical `build: abandon milestone <slug>` checkpoint moves
132
132
  that same brief into `.project/archive/`; build adds fix tasks and repairs
133
- briefs, it never replans.
133
+ briefs, it never replans. It also refuses a `build:` checkpoint while the
134
+ committed STATE is `ship/active`: final-review outputs stay uncommitted until
135
+ the ship commit.
134
136
 
135
137
  ## Execute from runtime receipts
136
138
 
@@ -69,7 +69,8 @@ so the position is unchanged. Then:
69
69
  task logs prove it is the immediately preceding `ship/blocked` finding
70
70
  set, the approved patch tasks copied every finding verbatim, and those tasks
71
71
  are now done; otherwise it blocks. Git history preserves the superseded
72
- finding, while final review itself still creates no pre-ship commit.
72
+ finding, while final review itself still creates no pre-ship commit
73
+ (`isolation.py checkpoint` refuses a `build:` checkpoint at `ship/active`).
73
74
  2. Derive a stable numbered list of cross-wave integration risks from
74
75
  interfaces and flows that span waves. List only genuine risks that could
75
76
  plausibly fail; never pad the list. Include PLAN.md's project Verify as
@@ -4502,6 +4502,20 @@ def attest(
4502
4502
  }
4503
4503
 
4504
4504
 
4505
+ def _committed_ship_active(repo: Path) -> bool:
4506
+ """True when the STATE.md committed at HEAD is ship/active."""
4507
+
4508
+ result = run_git(repo, "show", "HEAD:.project/STATE.md")
4509
+ if result.returncode != 0:
4510
+ return False
4511
+ frontmatter = result.stdout.split("\n---", 1)[0]
4512
+ fields = {
4513
+ key: _unquote(value)
4514
+ for key, value in re.findall(r"(?m)^(phase|status):(.*)$", frontmatter)
4515
+ }
4516
+ return fields.get("phase") == "ship" and fields.get("status") == "active"
4517
+
4518
+
4505
4519
  def checkpoint(
4506
4520
  repo: Path,
4507
4521
  expected_head: str,
@@ -4551,6 +4565,12 @@ def checkpoint(
4551
4565
  "subject": subject.strip(),
4552
4566
  }
4553
4567
  raise IsolationError("primary HEAD differs from --expected-head")
4568
+ if subject.strip().startswith("build:") and _committed_ship_active(repo):
4569
+ raise IsolationError(
4570
+ "STATE is ship/active: final review creates no pre-ship commit; leave its "
4571
+ "outputs uncommitted for archive_milestone.py, and send findings through "
4572
+ "the patch plan (see SHIP.md)"
4573
+ )
4554
4574
  pending = uncommitted_paths(repo)
4555
4575
  forced: Set[str] = set()
4556
4576
  # Build only: an approval undo resets to a parent without the ledger and would drop it.
@@ -108,10 +108,14 @@ def _final_view(relative, text, criteria, reviewed, head, surfaces=None):
108
108
  fields[field] = contracts._raw_source_field(block, field, relative)
109
109
  if "Surface" in fields and not {"Check", "Observed"}.issubset(fields):
110
110
  raise contracts.HandoffError(f"{criterion} lacks its recorded surface walkthrough")
111
+ reference = f"{relative} — {criterion}"
112
+ if "Observed" in fields and evidence:
113
+ # The reviewer may cite held-out and judgment ids on the evidence line.
114
+ reference += f"; evidence: {evidence}"
111
115
  lines += [f"### {criterion} — {title}", "", "- **Verdict**: met",
112
116
  f"- **Check**: {fields.get('Check', 'none')}",
113
117
  f"- **Observed**: {fields.get('Observed', evidence)}",
114
- f"- **Reference**: {relative} — {criterion}",
118
+ f"- **Reference**: {reference}",
115
119
  "- **Finding**: none", "- **Fix direction**: none"]
116
120
  if "Surface" in fields:
117
121
  expected = (surfaces or {}).get(criterion)
@@ -130,7 +130,9 @@ STATE.md-only "finalize" checkpoint for work already checkpointed. The helper
130
130
  refuses a `build:` checkpoint that deletes or renames a `.project/tasks/`
131
131
  brief unless the canonical `build: abandon milestone <slug>` checkpoint moves
132
132
  that same brief into `.project/archive/`; build adds fix tasks and repairs
133
- briefs, it never replans.
133
+ briefs, it never replans. It also refuses a `build:` checkpoint while the
134
+ committed STATE is `ship/active`: final-review outputs stay uncommitted until
135
+ the ship commit.
134
136
 
135
137
  ## Execute from runtime receipts
136
138
 
@@ -4502,6 +4502,20 @@ def attest(
4502
4502
  }
4503
4503
 
4504
4504
 
4505
+ def _committed_ship_active(repo: Path) -> bool:
4506
+ """True when the STATE.md committed at HEAD is ship/active."""
4507
+
4508
+ result = run_git(repo, "show", "HEAD:.project/STATE.md")
4509
+ if result.returncode != 0:
4510
+ return False
4511
+ frontmatter = result.stdout.split("\n---", 1)[0]
4512
+ fields = {
4513
+ key: _unquote(value)
4514
+ for key, value in re.findall(r"(?m)^(phase|status):(.*)$", frontmatter)
4515
+ }
4516
+ return fields.get("phase") == "ship" and fields.get("status") == "active"
4517
+
4518
+
4505
4519
  def checkpoint(
4506
4520
  repo: Path,
4507
4521
  expected_head: str,
@@ -4551,6 +4565,12 @@ def checkpoint(
4551
4565
  "subject": subject.strip(),
4552
4566
  }
4553
4567
  raise IsolationError("primary HEAD differs from --expected-head")
4568
+ if subject.strip().startswith("build:") and _committed_ship_active(repo):
4569
+ raise IsolationError(
4570
+ "STATE is ship/active: final review creates no pre-ship commit; leave its "
4571
+ "outputs uncommitted for archive_milestone.py, and send findings through "
4572
+ "the patch plan (see SHIP.md)"
4573
+ )
4554
4574
  pending = uncommitted_paths(repo)
4555
4575
  forced: Set[str] = set()
4556
4576
  # Build only: an approval undo resets to a parent without the ledger and would drop it.
@@ -4502,6 +4502,20 @@ def attest(
4502
4502
  }
4503
4503
 
4504
4504
 
4505
+ def _committed_ship_active(repo: Path) -> bool:
4506
+ """True when the STATE.md committed at HEAD is ship/active."""
4507
+
4508
+ result = run_git(repo, "show", "HEAD:.project/STATE.md")
4509
+ if result.returncode != 0:
4510
+ return False
4511
+ frontmatter = result.stdout.split("\n---", 1)[0]
4512
+ fields = {
4513
+ key: _unquote(value)
4514
+ for key, value in re.findall(r"(?m)^(phase|status):(.*)$", frontmatter)
4515
+ }
4516
+ return fields.get("phase") == "ship" and fields.get("status") == "active"
4517
+
4518
+
4505
4519
  def checkpoint(
4506
4520
  repo: Path,
4507
4521
  expected_head: str,
@@ -4551,6 +4565,12 @@ def checkpoint(
4551
4565
  "subject": subject.strip(),
4552
4566
  }
4553
4567
  raise IsolationError("primary HEAD differs from --expected-head")
4568
+ if subject.strip().startswith("build:") and _committed_ship_active(repo):
4569
+ raise IsolationError(
4570
+ "STATE is ship/active: final review creates no pre-ship commit; leave its "
4571
+ "outputs uncommitted for archive_milestone.py, and send findings through "
4572
+ "the patch plan (see SHIP.md)"
4573
+ )
4554
4574
  pending = uncommitted_paths(repo)
4555
4575
  forced: Set[str] = set()
4556
4576
  # Build only: an approval undo resets to a parent without the ledger and would drop it.
@@ -4502,6 +4502,20 @@ def attest(
4502
4502
  }
4503
4503
 
4504
4504
 
4505
+ def _committed_ship_active(repo: Path) -> bool:
4506
+ """True when the STATE.md committed at HEAD is ship/active."""
4507
+
4508
+ result = run_git(repo, "show", "HEAD:.project/STATE.md")
4509
+ if result.returncode != 0:
4510
+ return False
4511
+ frontmatter = result.stdout.split("\n---", 1)[0]
4512
+ fields = {
4513
+ key: _unquote(value)
4514
+ for key, value in re.findall(r"(?m)^(phase|status):(.*)$", frontmatter)
4515
+ }
4516
+ return fields.get("phase") == "ship" and fields.get("status") == "active"
4517
+
4518
+
4505
4519
  def checkpoint(
4506
4520
  repo: Path,
4507
4521
  expected_head: str,
@@ -4551,6 +4565,12 @@ def checkpoint(
4551
4565
  "subject": subject.strip(),
4552
4566
  }
4553
4567
  raise IsolationError("primary HEAD differs from --expected-head")
4568
+ if subject.strip().startswith("build:") and _committed_ship_active(repo):
4569
+ raise IsolationError(
4570
+ "STATE is ship/active: final review creates no pre-ship commit; leave its "
4571
+ "outputs uncommitted for archive_milestone.py, and send findings through "
4572
+ "the patch plan (see SHIP.md)"
4573
+ )
4554
4574
  pending = uncommitted_paths(repo)
4555
4575
  forced: Set[str] = set()
4556
4576
  # Build only: an approval undo resets to a parent without the ledger and would drop it.
@@ -4502,6 +4502,20 @@ def attest(
4502
4502
  }
4503
4503
 
4504
4504
 
4505
+ def _committed_ship_active(repo: Path) -> bool:
4506
+ """True when the STATE.md committed at HEAD is ship/active."""
4507
+
4508
+ result = run_git(repo, "show", "HEAD:.project/STATE.md")
4509
+ if result.returncode != 0:
4510
+ return False
4511
+ frontmatter = result.stdout.split("\n---", 1)[0]
4512
+ fields = {
4513
+ key: _unquote(value)
4514
+ for key, value in re.findall(r"(?m)^(phase|status):(.*)$", frontmatter)
4515
+ }
4516
+ return fields.get("phase") == "ship" and fields.get("status") == "active"
4517
+
4518
+
4505
4519
  def checkpoint(
4506
4520
  repo: Path,
4507
4521
  expected_head: str,
@@ -4551,6 +4565,12 @@ def checkpoint(
4551
4565
  "subject": subject.strip(),
4552
4566
  }
4553
4567
  raise IsolationError("primary HEAD differs from --expected-head")
4568
+ if subject.strip().startswith("build:") and _committed_ship_active(repo):
4569
+ raise IsolationError(
4570
+ "STATE is ship/active: final review creates no pre-ship commit; leave its "
4571
+ "outputs uncommitted for archive_milestone.py, and send findings through "
4572
+ "the patch plan (see SHIP.md)"
4573
+ )
4554
4574
  pending = uncommitted_paths(repo)
4555
4575
  forced: Set[str] = set()
4556
4576
  # Build only: an approval undo resets to a parent without the ledger and would drop it.
@@ -4502,6 +4502,20 @@ def attest(
4502
4502
  }
4503
4503
 
4504
4504
 
4505
+ def _committed_ship_active(repo: Path) -> bool:
4506
+ """True when the STATE.md committed at HEAD is ship/active."""
4507
+
4508
+ result = run_git(repo, "show", "HEAD:.project/STATE.md")
4509
+ if result.returncode != 0:
4510
+ return False
4511
+ frontmatter = result.stdout.split("\n---", 1)[0]
4512
+ fields = {
4513
+ key: _unquote(value)
4514
+ for key, value in re.findall(r"(?m)^(phase|status):(.*)$", frontmatter)
4515
+ }
4516
+ return fields.get("phase") == "ship" and fields.get("status") == "active"
4517
+
4518
+
4505
4519
  def checkpoint(
4506
4520
  repo: Path,
4507
4521
  expected_head: str,
@@ -4551,6 +4565,12 @@ def checkpoint(
4551
4565
  "subject": subject.strip(),
4552
4566
  }
4553
4567
  raise IsolationError("primary HEAD differs from --expected-head")
4568
+ if subject.strip().startswith("build:") and _committed_ship_active(repo):
4569
+ raise IsolationError(
4570
+ "STATE is ship/active: final review creates no pre-ship commit; leave its "
4571
+ "outputs uncommitted for archive_milestone.py, and send findings through "
4572
+ "the patch plan (see SHIP.md)"
4573
+ )
4554
4574
  pending = uncommitted_paths(repo)
4555
4575
  forced: Set[str] = set()
4556
4576
  # Build only: an approval undo resets to a parent without the ledger and would drop it.
@@ -4502,6 +4502,20 @@ def attest(
4502
4502
  }
4503
4503
 
4504
4504
 
4505
+ def _committed_ship_active(repo: Path) -> bool:
4506
+ """True when the STATE.md committed at HEAD is ship/active."""
4507
+
4508
+ result = run_git(repo, "show", "HEAD:.project/STATE.md")
4509
+ if result.returncode != 0:
4510
+ return False
4511
+ frontmatter = result.stdout.split("\n---", 1)[0]
4512
+ fields = {
4513
+ key: _unquote(value)
4514
+ for key, value in re.findall(r"(?m)^(phase|status):(.*)$", frontmatter)
4515
+ }
4516
+ return fields.get("phase") == "ship" and fields.get("status") == "active"
4517
+
4518
+
4505
4519
  def checkpoint(
4506
4520
  repo: Path,
4507
4521
  expected_head: str,
@@ -4551,6 +4565,12 @@ def checkpoint(
4551
4565
  "subject": subject.strip(),
4552
4566
  }
4553
4567
  raise IsolationError("primary HEAD differs from --expected-head")
4568
+ if subject.strip().startswith("build:") and _committed_ship_active(repo):
4569
+ raise IsolationError(
4570
+ "STATE is ship/active: final review creates no pre-ship commit; leave its "
4571
+ "outputs uncommitted for archive_milestone.py, and send findings through "
4572
+ "the patch plan (see SHIP.md)"
4573
+ )
4554
4574
  pending = uncommitted_paths(repo)
4555
4575
  forced: Set[str] = set()
4556
4576
  # Build only: an approval undo resets to a parent without the ledger and would drop it.
@@ -4502,6 +4502,20 @@ def attest(
4502
4502
  }
4503
4503
 
4504
4504
 
4505
+ def _committed_ship_active(repo: Path) -> bool:
4506
+ """True when the STATE.md committed at HEAD is ship/active."""
4507
+
4508
+ result = run_git(repo, "show", "HEAD:.project/STATE.md")
4509
+ if result.returncode != 0:
4510
+ return False
4511
+ frontmatter = result.stdout.split("\n---", 1)[0]
4512
+ fields = {
4513
+ key: _unquote(value)
4514
+ for key, value in re.findall(r"(?m)^(phase|status):(.*)$", frontmatter)
4515
+ }
4516
+ return fields.get("phase") == "ship" and fields.get("status") == "active"
4517
+
4518
+
4505
4519
  def checkpoint(
4506
4520
  repo: Path,
4507
4521
  expected_head: str,
@@ -4551,6 +4565,12 @@ def checkpoint(
4551
4565
  "subject": subject.strip(),
4552
4566
  }
4553
4567
  raise IsolationError("primary HEAD differs from --expected-head")
4568
+ if subject.strip().startswith("build:") and _committed_ship_active(repo):
4569
+ raise IsolationError(
4570
+ "STATE is ship/active: final review creates no pre-ship commit; leave its "
4571
+ "outputs uncommitted for archive_milestone.py, and send findings through "
4572
+ "the patch plan (see SHIP.md)"
4573
+ )
4554
4574
  pending = uncommitted_paths(repo)
4555
4575
  forced: Set[str] = set()
4556
4576
  # Build only: an approval undo resets to a parent without the ledger and would drop it.
@@ -4502,6 +4502,20 @@ def attest(
4502
4502
  }
4503
4503
 
4504
4504
 
4505
+ def _committed_ship_active(repo: Path) -> bool:
4506
+ """True when the STATE.md committed at HEAD is ship/active."""
4507
+
4508
+ result = run_git(repo, "show", "HEAD:.project/STATE.md")
4509
+ if result.returncode != 0:
4510
+ return False
4511
+ frontmatter = result.stdout.split("\n---", 1)[0]
4512
+ fields = {
4513
+ key: _unquote(value)
4514
+ for key, value in re.findall(r"(?m)^(phase|status):(.*)$", frontmatter)
4515
+ }
4516
+ return fields.get("phase") == "ship" and fields.get("status") == "active"
4517
+
4518
+
4505
4519
  def checkpoint(
4506
4520
  repo: Path,
4507
4521
  expected_head: str,
@@ -4551,6 +4565,12 @@ def checkpoint(
4551
4565
  "subject": subject.strip(),
4552
4566
  }
4553
4567
  raise IsolationError("primary HEAD differs from --expected-head")
4568
+ if subject.strip().startswith("build:") and _committed_ship_active(repo):
4569
+ raise IsolationError(
4570
+ "STATE is ship/active: final review creates no pre-ship commit; leave its "
4571
+ "outputs uncommitted for archive_milestone.py, and send findings through "
4572
+ "the patch plan (see SHIP.md)"
4573
+ )
4554
4574
  pending = uncommitted_paths(repo)
4555
4575
  forced: Set[str] = set()
4556
4576
  # Build only: an approval undo resets to a parent without the ledger and would drop it.
@@ -69,7 +69,8 @@ so the position is unchanged. Then:
69
69
  task logs prove it is the immediately preceding `ship/blocked` finding
70
70
  set, the approved patch tasks copied every finding verbatim, and those tasks
71
71
  are now done; otherwise it blocks. Git history preserves the superseded
72
- finding, while final review itself still creates no pre-ship commit.
72
+ finding, while final review itself still creates no pre-ship commit
73
+ (`isolation.py checkpoint` refuses a `build:` checkpoint at `ship/active`).
73
74
  2. Derive a stable numbered list of cross-wave integration risks from
74
75
  interfaces and flows that span waves. List only genuine risks that could
75
76
  plausibly fail; never pad the list. Include PLAN.md's project Verify as
@@ -4502,6 +4502,20 @@ def attest(
4502
4502
  }
4503
4503
 
4504
4504
 
4505
+ def _committed_ship_active(repo: Path) -> bool:
4506
+ """True when the STATE.md committed at HEAD is ship/active."""
4507
+
4508
+ result = run_git(repo, "show", "HEAD:.project/STATE.md")
4509
+ if result.returncode != 0:
4510
+ return False
4511
+ frontmatter = result.stdout.split("\n---", 1)[0]
4512
+ fields = {
4513
+ key: _unquote(value)
4514
+ for key, value in re.findall(r"(?m)^(phase|status):(.*)$", frontmatter)
4515
+ }
4516
+ return fields.get("phase") == "ship" and fields.get("status") == "active"
4517
+
4518
+
4505
4519
  def checkpoint(
4506
4520
  repo: Path,
4507
4521
  expected_head: str,
@@ -4551,6 +4565,12 @@ def checkpoint(
4551
4565
  "subject": subject.strip(),
4552
4566
  }
4553
4567
  raise IsolationError("primary HEAD differs from --expected-head")
4568
+ if subject.strip().startswith("build:") and _committed_ship_active(repo):
4569
+ raise IsolationError(
4570
+ "STATE is ship/active: final review creates no pre-ship commit; leave its "
4571
+ "outputs uncommitted for archive_milestone.py, and send findings through "
4572
+ "the patch plan (see SHIP.md)"
4573
+ )
4554
4574
  pending = uncommitted_paths(repo)
4555
4575
  forced: Set[str] = set()
4556
4576
  # Build only: an approval undo resets to a parent without the ledger and would drop it.
@@ -108,10 +108,14 @@ def _final_view(relative, text, criteria, reviewed, head, surfaces=None):
108
108
  fields[field] = contracts._raw_source_field(block, field, relative)
109
109
  if "Surface" in fields and not {"Check", "Observed"}.issubset(fields):
110
110
  raise contracts.HandoffError(f"{criterion} lacks its recorded surface walkthrough")
111
+ reference = f"{relative} — {criterion}"
112
+ if "Observed" in fields and evidence:
113
+ # The reviewer may cite held-out and judgment ids on the evidence line.
114
+ reference += f"; evidence: {evidence}"
111
115
  lines += [f"### {criterion} — {title}", "", "- **Verdict**: met",
112
116
  f"- **Check**: {fields.get('Check', 'none')}",
113
117
  f"- **Observed**: {fields.get('Observed', evidence)}",
114
- f"- **Reference**: {relative} — {criterion}",
118
+ f"- **Reference**: {reference}",
115
119
  "- **Finding**: none", "- **Fix direction**: none"]
116
120
  if "Surface" in fields:
117
121
  expected = (surfaces or {}).get(criterion)
@@ -130,7 +130,9 @@ STATE.md-only "finalize" checkpoint for work already checkpointed. The helper
130
130
  refuses a `build:` checkpoint that deletes or renames a `.project/tasks/`
131
131
  brief unless the canonical `build: abandon milestone <slug>` checkpoint moves
132
132
  that same brief into `.project/archive/`; build adds fix tasks and repairs
133
- briefs, it never replans.
133
+ briefs, it never replans. It also refuses a `build:` checkpoint while the
134
+ committed STATE is `ship/active`: final-review outputs stay uncommitted until
135
+ the ship commit.
134
136
 
135
137
  ## Execute from runtime receipts
136
138
 
@@ -69,7 +69,8 @@ so the position is unchanged. Then:
69
69
  task logs prove it is the immediately preceding `ship/blocked` finding
70
70
  set, the approved patch tasks copied every finding verbatim, and those tasks
71
71
  are now done; otherwise it blocks. Git history preserves the superseded
72
- finding, while final review itself still creates no pre-ship commit.
72
+ finding, while final review itself still creates no pre-ship commit
73
+ (`isolation.py checkpoint` refuses a `build:` checkpoint at `ship/active`).
73
74
  2. Derive a stable numbered list of cross-wave integration risks from
74
75
  interfaces and flows that span waves. List only genuine risks that could
75
76
  plausibly fail; never pad the list. Include PLAN.md's project Verify as
@@ -4502,6 +4502,20 @@ def attest(
4502
4502
  }
4503
4503
 
4504
4504
 
4505
+ def _committed_ship_active(repo: Path) -> bool:
4506
+ """True when the STATE.md committed at HEAD is ship/active."""
4507
+
4508
+ result = run_git(repo, "show", "HEAD:.project/STATE.md")
4509
+ if result.returncode != 0:
4510
+ return False
4511
+ frontmatter = result.stdout.split("\n---", 1)[0]
4512
+ fields = {
4513
+ key: _unquote(value)
4514
+ for key, value in re.findall(r"(?m)^(phase|status):(.*)$", frontmatter)
4515
+ }
4516
+ return fields.get("phase") == "ship" and fields.get("status") == "active"
4517
+
4518
+
4505
4519
  def checkpoint(
4506
4520
  repo: Path,
4507
4521
  expected_head: str,
@@ -4551,6 +4565,12 @@ def checkpoint(
4551
4565
  "subject": subject.strip(),
4552
4566
  }
4553
4567
  raise IsolationError("primary HEAD differs from --expected-head")
4568
+ if subject.strip().startswith("build:") and _committed_ship_active(repo):
4569
+ raise IsolationError(
4570
+ "STATE is ship/active: final review creates no pre-ship commit; leave its "
4571
+ "outputs uncommitted for archive_milestone.py, and send findings through "
4572
+ "the patch plan (see SHIP.md)"
4573
+ )
4554
4574
  pending = uncommitted_paths(repo)
4555
4575
  forced: Set[str] = set()
4556
4576
  # Build only: an approval undo resets to a parent without the ledger and would drop it.
@@ -108,10 +108,14 @@ def _final_view(relative, text, criteria, reviewed, head, surfaces=None):
108
108
  fields[field] = contracts._raw_source_field(block, field, relative)
109
109
  if "Surface" in fields and not {"Check", "Observed"}.issubset(fields):
110
110
  raise contracts.HandoffError(f"{criterion} lacks its recorded surface walkthrough")
111
+ reference = f"{relative} — {criterion}"
112
+ if "Observed" in fields and evidence:
113
+ # The reviewer may cite held-out and judgment ids on the evidence line.
114
+ reference += f"; evidence: {evidence}"
111
115
  lines += [f"### {criterion} — {title}", "", "- **Verdict**: met",
112
116
  f"- **Check**: {fields.get('Check', 'none')}",
113
117
  f"- **Observed**: {fields.get('Observed', evidence)}",
114
- f"- **Reference**: {relative} — {criterion}",
118
+ f"- **Reference**: {reference}",
115
119
  "- **Finding**: none", "- **Fix direction**: none"]
116
120
  if "Surface" in fields:
117
121
  expected = (surfaces or {}).get(criterion)