@opengsd/gsd-path 1.6.2 → 1.7.0
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 +21 -0
- package/DOCS.md +27 -1
- package/FULL.md +2 -1
- package/HOOKS.md +38 -4
- package/QUICK.md +1 -1
- package/README.md +19 -11
- package/RUNTIME.md +5 -3
- package/WORKFLOW.md +9 -2
- package/package.json +1 -1
- package/scripts/_common.py +76 -2
- package/scripts/archive_milestone.py +129 -7
- package/scripts/build_recovery.py +25 -2
- package/scripts/build_state.py +333 -8
- package/scripts/check_handoffs.py +437 -30
- package/scripts/check_task_briefs.py +135 -11
- package/scripts/discussion_validate.py +46 -10
- package/scripts/dispatch_driver.py +28 -3
- package/scripts/git_guard.py +44 -4
- package/scripts/guard_hook.py +456 -9
- package/scripts/install.py +34 -4
- package/scripts/integration.py +196 -39
- package/scripts/isolation.py +996 -38
- package/scripts/lean_verification.py +28 -13
- package/scripts/loop_run.py +9 -4
- package/scripts/members.py +17 -3
- package/scripts/pipeline_diagnose.py +104 -1
- package/scripts/pipeline_state.py +10 -2
- package/scripts/runtime_store.py +88 -3
- package/scripts/state_checkpoint.py +15 -0
- package/scripts/status_runtime.py +73 -2
- package/scripts/workflow_run.py +10 -3
- package/scripts/worktree_paths.py +45 -0
- package/skills/gsd-path/BUILD.md +3 -1
- package/skills/gsd-path/DEFINE.md +2 -1
- package/skills/gsd-path/PLAN.md +10 -0
- package/skills/gsd-path/SHIP.md +41 -8
- package/skills/gsd-path/SKILL.md +16 -1
- package/skills/gsd-path/references/build-native.md +20 -3
- package/skills/gsd-path/references/coder.md +6 -3
- package/skills/gsd-path/references/reviewer-final.md +10 -0
- package/skills/gsd-path/references/reviewer-wave.md +61 -22
- package/skills/gsd-path/references/reviewer.md +8 -6
- package/skills/gsd-path/references/runtime-selection.md +36 -0
- package/skills/gsd-path/references/spec-probes.md +19 -1
- package/skills/gsd-path/scripts/_common.py +76 -2
- package/skills/gsd-path/scripts/archive_milestone.py +129 -7
- package/skills/gsd-path/scripts/build_recovery.py +25 -2
- package/skills/gsd-path/scripts/build_state.py +333 -8
- package/skills/gsd-path/scripts/check_handoffs.py +437 -30
- package/skills/gsd-path/scripts/check_task_briefs.py +135 -11
- package/skills/gsd-path/scripts/discussion_validate.py +46 -10
- package/skills/gsd-path/scripts/dispatch_driver.py +28 -3
- package/skills/gsd-path/scripts/git_guard.py +44 -4
- package/skills/gsd-path/scripts/integration.py +196 -39
- package/skills/gsd-path/scripts/isolation.py +996 -38
- package/skills/gsd-path/scripts/lean_verification.py +28 -13
- package/skills/gsd-path/scripts/loop_run.py +9 -4
- package/skills/gsd-path/scripts/members.py +17 -3
- package/skills/gsd-path/scripts/pipeline_state.py +10 -2
- package/skills/gsd-path/scripts/state_checkpoint.py +15 -0
- package/skills/gsd-path/scripts/status_runtime.py +73 -2
- package/skills/gsd-path/scripts/workflow_run.py +10 -3
- package/skills/gsd-path/scripts/worktree_paths.py +45 -0
- package/skills/gsd-path/templates/intent.md +5 -3
- package/skills/gsd-path/templates/task.md +8 -1
- package/skills/gsd-path/templates/wave-review.md +24 -8
- package/skills/gsd-path-build/SKILL.md +3 -1
- package/skills/gsd-path-build/references/build-native.md +20 -3
- package/skills/gsd-path-build/references/coder.md +6 -3
- package/skills/gsd-path-build/references/reviewer-final.md +10 -0
- package/skills/gsd-path-build/references/reviewer-wave.md +61 -22
- package/skills/gsd-path-build/references/reviewer.md +8 -6
- package/skills/gsd-path-build/references/runtime-selection.md +36 -0
- package/skills/gsd-path-build/references/spec-probes.md +19 -1
- package/skills/gsd-path-build/scripts/_common.py +76 -2
- package/skills/gsd-path-build/scripts/archive_milestone.py +129 -7
- package/skills/gsd-path-build/scripts/build_recovery.py +25 -2
- package/skills/gsd-path-build/scripts/build_state.py +333 -8
- package/skills/gsd-path-build/scripts/check_handoffs.py +437 -30
- package/skills/gsd-path-build/scripts/check_task_briefs.py +135 -11
- package/skills/gsd-path-build/scripts/discussion_validate.py +46 -10
- package/skills/gsd-path-build/scripts/dispatch_driver.py +28 -3
- package/skills/gsd-path-build/scripts/integration.py +196 -39
- package/skills/gsd-path-build/scripts/isolation.py +996 -38
- package/skills/gsd-path-build/scripts/loop_run.py +9 -4
- package/skills/gsd-path-build/scripts/members.py +17 -3
- package/skills/gsd-path-build/scripts/pipeline_state.py +10 -2
- package/skills/gsd-path-build/scripts/state_checkpoint.py +15 -0
- package/skills/gsd-path-build/scripts/workflow_run.py +10 -3
- package/skills/gsd-path-build/scripts/worktree_paths.py +45 -0
- package/skills/gsd-path-build/templates/task.md +8 -1
- package/skills/gsd-path-build/templates/wave-review.md +24 -8
- package/skills/gsd-path-decide/references/runtime-selection.md +36 -0
- package/skills/gsd-path-decide/scripts/_common.py +76 -2
- package/skills/gsd-path-decide/scripts/archive_milestone.py +129 -7
- package/skills/gsd-path-decide/scripts/build_recovery.py +25 -2
- package/skills/gsd-path-decide/scripts/check_handoffs.py +437 -30
- package/skills/gsd-path-decide/scripts/check_task_briefs.py +135 -11
- package/skills/gsd-path-decide/scripts/discussion_validate.py +46 -10
- package/skills/gsd-path-decide/scripts/integration.py +196 -39
- package/skills/gsd-path-decide/scripts/isolation.py +996 -38
- package/skills/gsd-path-decide/scripts/loop_run.py +9 -4
- package/skills/gsd-path-decide/scripts/members.py +17 -3
- package/skills/gsd-path-decide/scripts/pipeline_state.py +10 -2
- package/skills/gsd-path-decide/scripts/state_checkpoint.py +15 -0
- package/skills/gsd-path-decide/scripts/worktree_paths.py +45 -0
- package/skills/gsd-path-define/SKILL.md +2 -1
- package/skills/gsd-path-define/references/runtime-selection.md +36 -0
- package/skills/gsd-path-define/references/spec-probes.md +19 -1
- package/skills/gsd-path-define/scripts/_common.py +76 -2
- package/skills/gsd-path-define/scripts/archive_milestone.py +129 -7
- package/skills/gsd-path-define/scripts/build_recovery.py +25 -2
- package/skills/gsd-path-define/scripts/check_handoffs.py +437 -30
- package/skills/gsd-path-define/scripts/check_task_briefs.py +135 -11
- package/skills/gsd-path-define/scripts/discussion_validate.py +46 -10
- package/skills/gsd-path-define/scripts/integration.py +196 -39
- package/skills/gsd-path-define/scripts/isolation.py +996 -38
- package/skills/gsd-path-define/scripts/loop_run.py +9 -4
- package/skills/gsd-path-define/scripts/members.py +17 -3
- package/skills/gsd-path-define/scripts/pipeline_state.py +10 -2
- package/skills/gsd-path-define/scripts/state_checkpoint.py +15 -0
- package/skills/gsd-path-define/scripts/worktree_paths.py +45 -0
- package/skills/gsd-path-define/templates/intent.md +5 -3
- package/skills/gsd-path-discuss/references/runtime-selection.md +36 -0
- package/skills/gsd-path-discuss/scripts/_common.py +76 -2
- package/skills/gsd-path-discuss/scripts/loop_run.py +9 -4
- package/skills/gsd-path-discuss/scripts/worktree_paths.py +45 -0
- package/skills/gsd-path-docs-audit/references/runtime-selection.md +36 -0
- package/skills/gsd-path-docs-audit/scripts/_common.py +76 -2
- package/skills/gsd-path-docs-audit/scripts/archive_milestone.py +129 -7
- package/skills/gsd-path-docs-audit/scripts/build_recovery.py +25 -2
- package/skills/gsd-path-docs-audit/scripts/check_handoffs.py +437 -30
- package/skills/gsd-path-docs-audit/scripts/check_task_briefs.py +135 -11
- package/skills/gsd-path-docs-audit/scripts/discussion_validate.py +46 -10
- package/skills/gsd-path-docs-audit/scripts/integration.py +196 -39
- package/skills/gsd-path-docs-audit/scripts/isolation.py +996 -38
- package/skills/gsd-path-docs-audit/scripts/loop_run.py +9 -4
- package/skills/gsd-path-docs-audit/scripts/members.py +17 -3
- package/skills/gsd-path-docs-audit/scripts/pipeline_state.py +10 -2
- package/skills/gsd-path-docs-audit/scripts/state_checkpoint.py +15 -0
- package/skills/gsd-path-docs-audit/scripts/worktree_paths.py +45 -0
- package/skills/gsd-path-forensics/references/runtime-selection.md +36 -0
- package/skills/gsd-path-forensics/scripts/_common.py +76 -2
- package/skills/gsd-path-forensics/scripts/loop_run.py +9 -4
- package/skills/gsd-path-forensics/scripts/pipeline_diagnose.py +104 -1
- package/skills/gsd-path-forensics/scripts/worktree_paths.py +45 -0
- package/skills/gsd-path-inspect/references/runtime-selection.md +36 -0
- package/skills/gsd-path-inspect/scripts/_common.py +76 -2
- package/skills/gsd-path-inspect/scripts/archive_milestone.py +129 -7
- package/skills/gsd-path-inspect/scripts/build_recovery.py +25 -2
- package/skills/gsd-path-inspect/scripts/check_handoffs.py +437 -30
- package/skills/gsd-path-inspect/scripts/check_task_briefs.py +135 -11
- package/skills/gsd-path-inspect/scripts/discussion_validate.py +46 -10
- package/skills/gsd-path-inspect/scripts/integration.py +196 -39
- package/skills/gsd-path-inspect/scripts/isolation.py +996 -38
- package/skills/gsd-path-inspect/scripts/loop_run.py +9 -4
- package/skills/gsd-path-inspect/scripts/members.py +17 -3
- package/skills/gsd-path-inspect/scripts/pipeline_state.py +10 -2
- package/skills/gsd-path-inspect/scripts/state_checkpoint.py +15 -0
- package/skills/gsd-path-inspect/scripts/workflow_run.py +10 -3
- package/skills/gsd-path-inspect/scripts/worktree_paths.py +45 -0
- package/skills/gsd-path-loop/references/runtime-selection.md +36 -0
- package/skills/gsd-path-loop/scripts/_common.py +76 -2
- package/skills/gsd-path-loop/scripts/loop_run.py +9 -4
- package/skills/gsd-path-loop/scripts/worktree_paths.py +45 -0
- package/skills/gsd-path-migrate/references/runtime-selection.md +36 -0
- package/skills/gsd-path-migrate/scripts/_common.py +76 -2
- package/skills/gsd-path-migrate/scripts/loop_run.py +9 -4
- package/skills/gsd-path-plan/SKILL.md +10 -0
- package/skills/gsd-path-plan/references/runtime-selection.md +36 -0
- package/skills/gsd-path-plan/references/spec-probes.md +19 -1
- package/skills/gsd-path-plan/scripts/_common.py +76 -2
- package/skills/gsd-path-plan/scripts/archive_milestone.py +129 -7
- package/skills/gsd-path-plan/scripts/build_recovery.py +25 -2
- package/skills/gsd-path-plan/scripts/check_handoffs.py +437 -30
- package/skills/gsd-path-plan/scripts/check_task_briefs.py +135 -11
- package/skills/gsd-path-plan/scripts/discussion_validate.py +46 -10
- package/skills/gsd-path-plan/scripts/integration.py +196 -39
- package/skills/gsd-path-plan/scripts/isolation.py +996 -38
- package/skills/gsd-path-plan/scripts/loop_run.py +9 -4
- package/skills/gsd-path-plan/scripts/members.py +17 -3
- package/skills/gsd-path-plan/scripts/pipeline_state.py +10 -2
- package/skills/gsd-path-plan/scripts/state_checkpoint.py +15 -0
- package/skills/gsd-path-plan/scripts/workflow_run.py +10 -3
- package/skills/gsd-path-plan/scripts/worktree_paths.py +45 -0
- package/skills/gsd-path-plan/templates/task.md +8 -1
- package/skills/gsd-path-research/references/runtime-selection.md +36 -0
- package/skills/gsd-path-research/scripts/_common.py +76 -2
- package/skills/gsd-path-research/scripts/archive_milestone.py +129 -7
- package/skills/gsd-path-research/scripts/build_recovery.py +25 -2
- package/skills/gsd-path-research/scripts/check_handoffs.py +437 -30
- package/skills/gsd-path-research/scripts/check_task_briefs.py +135 -11
- package/skills/gsd-path-research/scripts/discussion_validate.py +46 -10
- package/skills/gsd-path-research/scripts/integration.py +196 -39
- package/skills/gsd-path-research/scripts/isolation.py +996 -38
- package/skills/gsd-path-research/scripts/loop_run.py +9 -4
- package/skills/gsd-path-research/scripts/members.py +17 -3
- package/skills/gsd-path-research/scripts/pipeline_state.py +10 -2
- package/skills/gsd-path-research/scripts/state_checkpoint.py +15 -0
- package/skills/gsd-path-research/scripts/worktree_paths.py +45 -0
- package/skills/gsd-path-roadmap/references/runtime-selection.md +36 -0
- package/skills/gsd-path-roadmap/scripts/_common.py +76 -2
- package/skills/gsd-path-roadmap/scripts/archive_milestone.py +129 -7
- package/skills/gsd-path-roadmap/scripts/build_recovery.py +25 -2
- package/skills/gsd-path-roadmap/scripts/check_handoffs.py +437 -30
- package/skills/gsd-path-roadmap/scripts/check_task_briefs.py +135 -11
- package/skills/gsd-path-roadmap/scripts/discussion_validate.py +46 -10
- package/skills/gsd-path-roadmap/scripts/integration.py +196 -39
- package/skills/gsd-path-roadmap/scripts/isolation.py +996 -38
- package/skills/gsd-path-roadmap/scripts/loop_run.py +9 -4
- package/skills/gsd-path-roadmap/scripts/members.py +17 -3
- package/skills/gsd-path-roadmap/scripts/pipeline_state.py +10 -2
- package/skills/gsd-path-roadmap/scripts/state_checkpoint.py +15 -0
- package/skills/gsd-path-roadmap/scripts/worktree_paths.py +45 -0
- package/skills/gsd-path-ship/SKILL.md +41 -8
- package/skills/gsd-path-ship/references/reviewer-final.md +10 -0
- package/skills/gsd-path-ship/references/reviewer-wave.md +61 -22
- package/skills/gsd-path-ship/references/reviewer.md +8 -6
- package/skills/gsd-path-ship/references/runtime-selection.md +36 -0
- package/skills/gsd-path-ship/references/spec-probes.md +19 -1
- package/skills/gsd-path-ship/scripts/_common.py +76 -2
- package/skills/gsd-path-ship/scripts/archive_milestone.py +129 -7
- package/skills/gsd-path-ship/scripts/build_recovery.py +25 -2
- package/skills/gsd-path-ship/scripts/build_state.py +333 -8
- package/skills/gsd-path-ship/scripts/check_handoffs.py +437 -30
- package/skills/gsd-path-ship/scripts/check_task_briefs.py +135 -11
- package/skills/gsd-path-ship/scripts/discussion_validate.py +46 -10
- package/skills/gsd-path-ship/scripts/integration.py +196 -39
- package/skills/gsd-path-ship/scripts/isolation.py +996 -38
- package/skills/gsd-path-ship/scripts/lean_verification.py +28 -13
- package/skills/gsd-path-ship/scripts/loop_run.py +9 -4
- package/skills/gsd-path-ship/scripts/members.py +17 -3
- package/skills/gsd-path-ship/scripts/pipeline_state.py +10 -2
- package/skills/gsd-path-ship/scripts/state_checkpoint.py +15 -0
- package/skills/gsd-path-ship/scripts/workflow_run.py +10 -3
- package/skills/gsd-path-ship/scripts/worktree_paths.py +45 -0
- package/skills/gsd-path-ship/templates/wave-review.md +24 -8
- package/skills/gsd-path-undo/references/runtime-selection.md +36 -0
- package/skills/gsd-path-undo/scripts/_common.py +76 -2
- package/skills/gsd-path-undo/scripts/loop_run.py +9 -4
- package/skills/gsd-path-undo/scripts/worktree_paths.py +45 -0
- package/skills/path/BUILD.md +3 -1
- package/skills/path/DEFINE.md +2 -1
- package/skills/path/PLAN.md +10 -0
- package/skills/path/SHIP.md +41 -8
- package/skills/path/SKILL.md +16 -1
- package/skills/path/references/build-native.md +20 -3
- package/skills/path/references/coder.md +6 -3
- package/skills/path/references/reviewer-final.md +10 -0
- package/skills/path/references/reviewer-wave.md +61 -22
- package/skills/path/references/reviewer.md +8 -6
- package/skills/path/references/runtime-selection.md +36 -0
- package/skills/path/references/spec-probes.md +19 -1
- package/skills/path/scripts/_common.py +76 -2
- package/skills/path/scripts/archive_milestone.py +129 -7
- package/skills/path/scripts/build_recovery.py +25 -2
- package/skills/path/scripts/build_state.py +333 -8
- package/skills/path/scripts/check_handoffs.py +437 -30
- package/skills/path/scripts/check_task_briefs.py +135 -11
- package/skills/path/scripts/discussion_validate.py +46 -10
- package/skills/path/scripts/dispatch_driver.py +28 -3
- package/skills/path/scripts/git_guard.py +44 -4
- package/skills/path/scripts/integration.py +196 -39
- package/skills/path/scripts/isolation.py +996 -38
- package/skills/path/scripts/lean_verification.py +28 -13
- package/skills/path/scripts/loop_run.py +9 -4
- package/skills/path/scripts/members.py +17 -3
- package/skills/path/scripts/pipeline_state.py +10 -2
- package/skills/path/scripts/state_checkpoint.py +15 -0
- package/skills/path/scripts/status_runtime.py +73 -2
- package/skills/path/scripts/workflow_run.py +10 -3
- package/skills/path/scripts/worktree_paths.py +45 -0
- package/skills/path/templates/intent.md +5 -3
- package/skills/path/templates/task.md +8 -1
- package/skills/path/templates/wave-review.md +24 -8
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,27 @@ 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.7.0] - 2026-10-30
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
- Publish runtime-store entries without installing them, and adopt approved successor runtimes on closed milestones.
|
|
18
|
+
- Prepare member task environments with a declared command and retry tasks after an environment failure.
|
|
19
|
+
- Recover member work that already reached its default branch, including published ancestors and rebase-merged pull requests.
|
|
20
|
+
- Trace accepted task evidence through a generated evidence manifest.
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
- Check task brief paths in batches and report progress during larger plans.
|
|
24
|
+
- Record the actual verification directory and native agent in member task receipts.
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
- Resume patch work after a ship-stage review without losing its checkpoint.
|
|
28
|
+
- Keep blocked plans and repair waves on the correct recovery route.
|
|
29
|
+
- Require wave reviews to account for every task claim and authorize claim dispositions through owner corrections.
|
|
30
|
+
- Preserve disabled hooks, permit safe read-only fetches, and tighten PowerShell and repository deletion guards.
|
|
31
|
+
- Remove isolated worktrees without traversing symlinks or Windows junctions.
|
|
32
|
+
- Pin sibling task files during member activation and resolve member markers in final-review sidecars.
|
|
33
|
+
- Retry project verification after a host failure and run long verification scripts correctly on Windows.
|
|
34
|
+
|
|
14
35
|
## [1.6.2] - 2026-10-17
|
|
15
36
|
|
|
16
37
|
### Changed
|
package/DOCS.md
CHANGED
|
@@ -412,9 +412,35 @@ A new machine needs the selected runtime installed before status and guards can
|
|
|
412
412
|
run. `gsd-path --runtime-restore --project /absolute/project --source-root
|
|
413
413
|
/path/to/matching/package` restores that exact version without changing project
|
|
414
414
|
files. The supplied package must match both the version and digest; custom source
|
|
415
|
-
builds may share a release version while having different digests.
|
|
415
|
+
builds may share a release version while having different digests. One exception:
|
|
416
|
+
a pin on a runtime that names successors (below) restores from its existing
|
|
417
|
+
stamped store entry when the package has the same version and files, since only
|
|
418
|
+
the successors stamp differs. No command
|
|
416
419
|
silently substitutes a newer runtime, and status/guards never install or download.
|
|
417
420
|
|
|
421
|
+
To publish a runtime to the shared store without selecting it for any project,
|
|
422
|
+
run `python3 -B <absolute runtime_store.py> publish --source /path/to/package`
|
|
423
|
+
(and optionally `--provenance "hotfix for <issue>"`). It prints the runtime's
|
|
424
|
+
version, digest, and verified store path as JSON. Publication alone neither
|
|
425
|
+
authorizes execution nor changes a project's trusted runtime — a project runs
|
|
426
|
+
new bytes only after its own pin selects that digest, or after the pinned
|
|
427
|
+
runtime's own hashed manifest names them (below). `--expected-version`
|
|
428
|
+
with `--expected-digest` refuses a source that does not match, `--dry-run`
|
|
429
|
+
computes the identity without writing, and `--repair` replaces an entry that
|
|
430
|
+
fails verification.
|
|
431
|
+
|
|
432
|
+
A runtime can bless successors: publish the successor package first, then
|
|
433
|
+
publish its predecessor with `--name-successor <successor digest>` (repeatable).
|
|
434
|
+
The list is recorded inside the predecessor's hashed `manifest.json`, so the
|
|
435
|
+
blessing is covered by the predecessor's own digest — nothing outside its bytes
|
|
436
|
+
can add to it. On a closed milestone, where the pin can no longer change, the
|
|
437
|
+
guard accepts a helper from a successor only when the pinned runtime's hashed
|
|
438
|
+
manifest names it (directly or through a chain of blessed runtimes, each hop
|
|
439
|
+
hash-locked); every unlisted runtime stays refused. Runtimes published before
|
|
440
|
+
successors existed can never name one without changing their digest, so pins
|
|
441
|
+
on those runtimes keep the run-from-another-cwd workaround until an
|
|
442
|
+
owner-approved repin on a legal branch.
|
|
443
|
+
|
|
418
444
|
For an old `.gsd-path/runtime/`, run `gsd-path --runtime-migrate --project
|
|
419
445
|
/absolute/project --dry-run`, then the same command without `--dry-run` and review
|
|
420
446
|
the Git diff. Migration removes the generated files from the checkout and writes
|
package/FULL.md
CHANGED
|
@@ -464,7 +464,8 @@ approves.
|
|
|
464
464
|
from two sessions at the same time.
|
|
465
465
|
- Do not push `gsd-path/M00N` by hand, and do not merge another branch into
|
|
466
466
|
it.
|
|
467
|
-
- Do not squash
|
|
467
|
+
- Do not squash the integration pull request or merge request, and do not
|
|
468
|
+
rebase or fast-forward an `external-landing` merge request.
|
|
468
469
|
|
|
469
470
|
---
|
|
470
471
|
|
package/HOOKS.md
CHANGED
|
@@ -11,13 +11,18 @@ Optional enforcement for pipeline invariants prompt contracts cannot guarantee:
|
|
|
11
11
|
|
|
12
12
|
Closed branches still allow read-only Git inspection (`status`, `log`, `diff`, plain
|
|
13
13
|
`cat-file` reads of one object, branch and worktree listing) and the router handoff helpers, including
|
|
14
|
-
`git fetch
|
|
15
|
-
|
|
14
|
+
`git fetch [REMOTE]`, SHA resolution, and next-base selection. They also allow a
|
|
15
|
+
simple literal delete or move whose every operand is outside the closed repository,
|
|
16
|
+
its member checkouts, and the pipeline state roots, and is no ancestor of them.
|
|
17
|
+
Branch switching remains router-owned. The [ship contract](skills/gsd-path-ship/SKILL.md) owns integration
|
|
16
18
|
conflict handling and published-validation recovery.
|
|
17
19
|
|
|
18
20
|
`cat-file` options are restricted by `CLOSED_LISTING_GIT_OPTIONS` in
|
|
19
21
|
[`scripts/guard_hook.py`](scripts/guard_hook.py); filter and text-conversion forms
|
|
20
|
-
are blocked because they can execute external commands.
|
|
22
|
+
are blocked because they can execute external commands. A closed-branch fetch takes
|
|
23
|
+
no global option but `-C`, no command-line refspec or option, and a configured remote
|
|
24
|
+
whose repo config fetches read-only; `_closed_fetch_allowed` in the same file owns the
|
|
25
|
+
exact proof.
|
|
21
26
|
|
|
22
27
|
**Docs:** [DOCS.md](DOCS.md) (hub) · [UPDATE.md](UPDATE.md) (refresh hooks) · [QUICK.md](QUICK.md) (first install with `--hooks`)
|
|
23
28
|
|
|
@@ -94,6 +99,11 @@ hook.
|
|
|
94
99
|
the rename when the directory is tracked.
|
|
95
100
|
- **`<hook>.gsd-path-chained` already exists beside a foreign hook:** the
|
|
96
101
|
installer stops. Decide which of the two files you want to keep.
|
|
102
|
+
- **You disabled a hook by renaming it to `<hook>.disabled*`:** while
|
|
103
|
+
`<hook>` itself is absent, the installer and `--member-of` leave it disabled
|
|
104
|
+
(`(left disabled)`), and doctor reports it as deliberately disabled instead
|
|
105
|
+
of missing. To enable the guard again, remove or rename the disabled file
|
|
106
|
+
and rerun the installer.
|
|
97
107
|
|
|
98
108
|
Git hooks are local to a clone. A bot that merges on the server, such as
|
|
99
109
|
Renovate, never runs them, and its commits on the default branch do not
|
|
@@ -195,6 +205,18 @@ See [UPDATE.md](UPDATE.md).
|
|
|
195
205
|
target a routing control (`.git`, `.project/STATE.md`, `.project/next`,
|
|
196
206
|
`.gsd-path`) while `.project/STATE.md` exists, or target a product file
|
|
197
207
|
outside the routed build phase (same rule as direct file-edit tools)
|
|
208
|
+
- PowerShell expression forms the guard cannot inspect: a .NET static member
|
|
209
|
+
call (`[System.IO.File]::WriteAllText`) outside a small read-only type list
|
|
210
|
+
(`[Math]`, `[string]`, number, date, and similar types), anywhere in the
|
|
211
|
+
command, including script blocks; dynamic-execution commands and their
|
|
212
|
+
aliases (`Invoke-Expression`/`iex`, `Invoke-Command`, `Add-Type`,
|
|
213
|
+
`Start-Process`, `Start-Job`, `New-Object`) as the program or inside a
|
|
214
|
+
script block; the `&` call operator; and the same forms inside wrapped
|
|
215
|
+
`-Command` payloads. A `$name = value` or `$env:NAME = value` assignment
|
|
216
|
+
with only plain literal words is process-local and passes; a bare-word
|
|
217
|
+
value is checked as the command it runs. A richer value keeps the
|
|
218
|
+
unresolvable-executable denial, and `$env:HOME`, `$env:XDG_CONFIG_HOME`,
|
|
219
|
+
and `$env:GIT_*` are denied like the `env` form
|
|
198
220
|
- repeated assignments to the same shell variable within one command; split
|
|
199
221
|
these into separate tool calls so write targets can be resolved
|
|
200
222
|
- copies whose destinations, including nested recursive-copy entries, resolve
|
|
@@ -220,6 +242,10 @@ See [UPDATE.md](UPDATE.md).
|
|
|
220
242
|
backticks, glob characters (`*`, `?`, `[`, `]`), braces, or `..` path components,
|
|
221
243
|
and must not start with `~`. Quoted spaces in paths and quoted punctuation
|
|
222
244
|
within arguments, such as the shipment event's semicolon, are allowed
|
|
245
|
+
- deletion or move commands, in every phase, that target or contain a
|
|
246
|
+
pipeline state root (`~/.gsd-path`, `GSD_PATH_WORKTREE_ROOT`, or a pinned
|
|
247
|
+
workspace root). Deletes inside the agent's own managed worktree under such
|
|
248
|
+
a root stay allowed. Workspace receipts that cannot be read deny the command
|
|
223
249
|
- deletion or move commands outside a single simple segment, including command
|
|
224
250
|
chains, pipes, newlines, grouping, directory changes, and command substitution
|
|
225
251
|
- deletion or move commands with any argument outside the literal-path character
|
|
@@ -229,6 +255,10 @@ See [UPDATE.md](UPDATE.md).
|
|
|
229
255
|
tilde paths are denied even when quoted. This also applies to
|
|
230
256
|
`find -delete` and supported destructive aliases
|
|
231
257
|
- destructive Git commands nested in supported shell and command wrappers
|
|
258
|
+
- the operator-only `isolation.py runtime-hotfix` recovery, and every Git
|
|
259
|
+
command with a word that contains `refs/gsd-path/` or `runtime-hotfix`;
|
|
260
|
+
see [runtime selection](skills/gsd-path/references/runtime-selection.md)
|
|
261
|
+
for the receipt, the covered spellings, and the limits
|
|
232
262
|
- Git arguments that contain a shell parameter. Only plain `$name` and
|
|
233
263
|
`${name}` expansions pass, and only inside plain double-quoted spans of
|
|
234
264
|
read-only subcommands (`status`, `log`, `diff`, `show`, and the other
|
|
@@ -339,7 +369,11 @@ such as `rm -rf scratch` passes the archive check; other guard rules still apply
|
|
|
339
369
|
`files:`, and a `Files:` list of exactly the staged paths. Outside build the
|
|
340
370
|
reviewed HEAD is frozen; product fixes reopen through the patch plan.
|
|
341
371
|
The validated integration merge is allowed to complete before publication
|
|
342
|
-
proof exists; an ordinary commit with an integration subject is not exempt
|
|
372
|
+
proof exists; an ordinary commit with an integration subject is not exempt.
|
|
373
|
+
A landing commit may also stage `.gsd-path/runtime.json` only when a
|
|
374
|
+
runtime-hotfix receipt exists for that task and base and the staged pin
|
|
375
|
+
names exactly the receipt's runtime; see
|
|
376
|
+
[runtime selection](skills/gsd-path/references/runtime-selection.md)
|
|
343
377
|
- deleting the recorded branch or switching to another branch while carrying
|
|
344
378
|
unfinished state does not release product commit protection
|
|
345
379
|
- pushes (to any remote) that move a `gsd-path/M###` ref anywhere but its
|
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.
|
|
17
|
+
Code have a passing live milestone for release 1.7.0. 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.
|
|
12
|
+
**Hosts with a passing full live milestone for release 1.7.0:** 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.
|
|
16
|
+
release 1.7.0:** 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,16 +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
|
-
## What's new in 1.
|
|
27
|
+
## What's new in 1.7.0 — 2026-10-30
|
|
28
28
|
|
|
29
|
-
**Latest npm release:** [@opengsd/gsd-path@1.
|
|
29
|
+
**Latest npm release:** [@opengsd/gsd-path@1.7.0](https://www.npmjs.com/package/@opengsd/gsd-path/v/1.7.0) — [release notes](CHANGELOG.md#170---2026-10-30)
|
|
30
30
|
|
|
31
31
|
**Recent highlights**
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
-
|
|
35
|
-
-
|
|
36
|
-
-
|
|
32
|
+
- translate every app screen through the message tables
|
|
33
|
+
- add the app language setting with message tables
|
|
34
|
+
- accept blessed successor runtimes on closed milestones
|
|
35
|
+
- add a publish-only runtime store CLI
|
|
36
|
+
- add evidence-manifest joining tasks to accepted evidence
|
|
37
37
|
|
|
38
38
|
<!-- /release-docs -->
|
|
39
39
|
|
|
@@ -196,7 +196,8 @@ What the app does:
|
|
|
196
196
|
- **Project setup:** runtime version, guard hooks, health check, and, for a
|
|
197
197
|
multi-repo coordinator, member hooks and marker repair.
|
|
198
198
|
- **Settings:** updates, Path settings (shipping mode, models, review panel),
|
|
199
|
-
watched folders, model prices, launch at login, and
|
|
199
|
+
watched folders, model prices, launch at login, appearance, and language
|
|
200
|
+
(English or Simplified Chinese).
|
|
200
201
|
- **Stats** and the per-project **Environment** editor.
|
|
201
202
|
- **Tray:** a compact project list, items that need attention, and controls
|
|
202
203
|
to start, restart, and quit.
|
|
@@ -379,7 +380,7 @@ default branch of the coordinator and of the other members. See
|
|
|
379
380
|
[ADR 0002](docs/adr/0002-multi-repo-coordinator.md) for the design.
|
|
380
381
|
|
|
381
382
|
`MEMBERS.md` has one section per member, in ship order. `members.py` writes
|
|
382
|
-
it; do not edit it by hand:
|
|
383
|
+
it; do not edit it by hand (except `Prepare`, below):
|
|
383
384
|
|
|
384
385
|
```markdown
|
|
385
386
|
## web
|
|
@@ -394,6 +395,13 @@ records it one time, from the member's local `origin/HEAD`; run
|
|
|
394
395
|
`git remote set-head origin --auto` in the member first. A section without
|
|
395
396
|
the field means `main`. See [default branch](WORKFLOW.md#default-branch).
|
|
396
397
|
|
|
398
|
+
`Prepare` is optional. It is a shell command that the runtime runs inside
|
|
399
|
+
each fresh member isolate before any Verify, to provision what a bare
|
|
400
|
+
worktree lacks (submodules, gitignored build inputs). It must copy, never
|
|
401
|
+
link. `members.py` has no subcommand for it: add it to the member's section
|
|
402
|
+
through a reviewed change. The `gsd-path` skill describes the rules and an
|
|
403
|
+
offline submodule recipe.
|
|
404
|
+
|
|
397
405
|
- Add members at a milestone boundary: ask the router to add an existing repo
|
|
398
406
|
(`members.py add`) or to create a new one (`members.py add --create`, after
|
|
399
407
|
you approve the exact owner, name, visibility, and checkout; a new
|
package/RUNTIME.md
CHANGED
|
@@ -33,8 +33,10 @@ never changes a recorded base, retries a failed mutation, or repairs state itsel
|
|
|
33
33
|
Checkpoint inputs must use the top-level `.project` entries defined by
|
|
34
34
|
[`isolation.PROJECT_ENTRIES`](scripts/isolation.py); ship verification uses the
|
|
35
35
|
same allowlist. `isolation.py checkpoint` refuses pending paths under any other
|
|
36
|
-
top-level entry and asks for their removal
|
|
37
|
-
|
|
36
|
+
top-level entry that still exist in the worktree and asks for their removal; a
|
|
37
|
+
pending removal (`git rm`, `git mv`, or a deleted file) passes and the checkpoint
|
|
38
|
+
commits it. This check does not inspect nested entries inside an allowed
|
|
39
|
+
directory.
|
|
38
40
|
|
|
39
41
|
For serial work, keep the returned verification sidecar until the coder finishes.
|
|
40
42
|
Validate the full task diff, reproduce only that patch in the sidecar, compare the
|
|
@@ -68,7 +70,7 @@ skeptic attempts under `skeptics/`.
|
|
|
68
70
|
| `skeptics --repo <root> --wave <N> --cycle <C> --child-command '<cmd>' [--wait <s>] [--child-timeout <s>]` | Step 7 skeptic branch: one read-only skeptic per `skeptic_groups` locator in its own sidecar at the recorded review base, briefed with the criterion, every observation, and the cited tasks verbatim; validate with `review_findings.py`, collect, retire, then rerun the helper. `none` without skeptic groups; `escalate` for structural blockers or the cycle cap. The skeptic files stay uncommitted for the fix-task or ruling checkpoint. |
|
|
69
71
|
| `fix-tasks --repo <root> --wave <N> --cycle <C>` | Step 7 batching: one fix task per `review_findings.py collect` batch with the failed criteria and observations verbatim, lint-checked; `escalate` for structural blockers, skeptic groups, cycle cap, or all-refuted. |
|
|
70
72
|
| `complete --repo <root>` | Completion: clean primary at `build/active` (or a crash-left `build/done`), no unfinished wave, the archive-time review cycle check (`discussion_validate.review_cycle_counts`: contiguous cycles, passing last cycle, panel and skeptic artifacts present); `workflow_run.py build-evidence` at HEAD; `pipeline_state.py transition` to `ship/active` with event `build done; final review pending`; checkpoint `build: complete milestone`. |
|
|
71
|
-
| `finish --repo <root> --task-id <id>` | Recover first; return a proven landing without repeating Verify, or verify, land, record, and retire one returned task. Without a dispatch record, or when the newest record is settled and its base differs from the task's current in-progress base (a native retry after a driver block), read the primary task's in-progress state; if it lacks a recorded base, use the parallel task isolate's state only if that worktree is authorized. A blocked record at the same base still refuses the retry, except a host-dispatched record (no child command) while the task is still in progress at that base: `finish` may run again after a failed Verify. A deactivated isolate stays blocked. |
|
|
73
|
+
| `finish --repo <root> --task-id <id>` | Recover first; return a proven landing without repeating Verify, or verify, land, record, and retire one returned task. Without a dispatch record, or when the newest record is settled and its base differs from the task's current in-progress base (a native retry after a driver block), read the primary task's in-progress state; if it lacks a recorded base, use the parallel task isolate's state only if that worktree is authorized. A blocked record at the same base still refuses the retry, except a host-dispatched record (no child command) while the task is still in progress at that base: `finish` may run again after a failed Verify. A deactivated isolate stays blocked. Receipt status `landed`, `question` (the child ended in a question), or `blocked`; a blocked receipt without a blocked entry carries a `reason`. |
|
|
72
74
|
| `answer --repo <root> --task-id <id> --answer '<text>'` | Append `Orchestrator answer:` to the isolate's task Log; the next `round` redispatches that isolate. |
|
|
73
75
|
| `status --repo <root>` | Every task's newest dispatch record. |
|
|
74
76
|
|
package/WORKFLOW.md
CHANGED
|
@@ -232,8 +232,10 @@ the user's ruling per applicable edge in INTENT.md `## Edge coverage`:
|
|
|
232
232
|
`criterion SCn` (stated in a success criterion), `held-out` (a named test
|
|
233
233
|
pins it), or `dismissed` with a reason. The prohibition probe asks what each
|
|
234
234
|
criterion must never silently become and records kept items in
|
|
235
|
-
`## Prohibitions` as `criterion SCn`, `judgment`, or `dismissed`.
|
|
236
|
-
|
|
235
|
+
`## Prohibitions` as `criterion SCn`, `judgment`, or `dismissed`. An
|
|
236
|
+
absence criterion ("X does not exist") must state its search space, its
|
|
237
|
+
check, and the result that counts as absent; reviewers judge it over that
|
|
238
|
+
space only. Define never rules on an edge itself. `scripts/check_handoffs.py intent` checks
|
|
237
239
|
both tables before every approval question, and the `define/done` transition
|
|
238
240
|
refuses an intent whose tables are incomplete. An INTENT.md without these
|
|
239
241
|
sections predates the probes and is not rechecked.
|
|
@@ -334,6 +336,11 @@ exists).
|
|
|
334
336
|
`.project/research/SYNTHESIS.md`, plus relevant existing code. **Output:**
|
|
335
337
|
`.project/plan/PLAN.md` and one full task file per task in `.project/tasks/`.
|
|
336
338
|
|
|
339
|
+
A failed plan revision records `plan/blocked`. The router stops there and asks
|
|
340
|
+
for the correction or user decision named by the plan gate. It does not
|
|
341
|
+
automatically restart planning. After resolving the blocker, invoke
|
|
342
|
+
`$gsd-path-plan` explicitly; its resume records `planning resumed`.
|
|
343
|
+
|
|
337
344
|
Order work by risk and dependency:
|
|
338
345
|
|
|
339
346
|
1. Wave one burns down assumptions that could invalidate the plan.
|
package/package.json
CHANGED
package/scripts/_common.py
CHANGED
|
@@ -14,6 +14,7 @@ import sys
|
|
|
14
14
|
# Runtime helpers must not modify their immutable installation.
|
|
15
15
|
sys.dont_write_bytecode = True
|
|
16
16
|
|
|
17
|
+
import atexit
|
|
17
18
|
import contextlib
|
|
18
19
|
import errno
|
|
19
20
|
import json
|
|
@@ -23,9 +24,10 @@ import shlex
|
|
|
23
24
|
import shutil
|
|
24
25
|
import signal
|
|
25
26
|
import subprocess
|
|
27
|
+
import tempfile
|
|
26
28
|
import time
|
|
27
29
|
from pathlib import Path
|
|
28
|
-
from typing import BinaryIO, Iterable, Iterator, List, Optional, Sequence, Tuple
|
|
30
|
+
from typing import BinaryIO, Iterable, Iterator, List, Optional, Sequence, Set, Tuple
|
|
29
31
|
|
|
30
32
|
|
|
31
33
|
# Chosen by what imports, not os.name, so tests can emulate Windows on POSIX.
|
|
@@ -65,6 +67,23 @@ VERIFY_LEDGER_PATH = ".project/build/verify-ledger.jsonl"
|
|
|
65
67
|
VERIFY_RESULTS = ("pass", "fail")
|
|
66
68
|
VERIFY_LEDGER_SCHEMA = "gsd-path/verify-ledger/v2"
|
|
67
69
|
VERIFY_BLOCK_PATTERN = re.compile(r"```bash[ \t]*\n(?P<block>.*?)```", re.DOTALL)
|
|
70
|
+
# The owner citation a withdrawn task tombstone must carry in its Log (#483).
|
|
71
|
+
# One definition shared by every gate that reads the representation.
|
|
72
|
+
WITHDRAWN_CITATION = re.compile(
|
|
73
|
+
r"^- Withdrawn — (?P<date>\d{4}-\d{2}-\d{2}): (?P<ruling>.+)$", re.MULTILINE)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def withdrawn_citations(task_text: str) -> list[str]:
|
|
77
|
+
"""The owner citations in a withdrawn task's Log, as `date: ruling`.
|
|
78
|
+
|
|
79
|
+
The Log is the append-only record of rulings; a Withdrawn line in any
|
|
80
|
+
other section is not the citation the gates accept (#483).
|
|
81
|
+
"""
|
|
82
|
+
body = section_body(task_text, "Log")
|
|
83
|
+
if body is None:
|
|
84
|
+
return []
|
|
85
|
+
return [f"{m.group('date')}: {m.group('ruling')}"
|
|
86
|
+
for m in WITHDRAWN_CITATION.finditer(body)]
|
|
68
87
|
|
|
69
88
|
REVIEW_BULLET_HEADING_PATTERN = re.compile(r"^#{2,3} ")
|
|
70
89
|
REVIEW_BULLET_FIELD_LABEL_PATTERN = re.compile(r"^- \*\*[^*]+\*\*:")
|
|
@@ -197,6 +216,19 @@ def section_body(text: str, heading: str) -> Optional[str]:
|
|
|
197
216
|
return match.group("body") if match else None
|
|
198
217
|
|
|
199
218
|
|
|
219
|
+
def contract_shapes(body: str) -> Set[str]:
|
|
220
|
+
"""The whitespace-normalized `- ` entries of an Interface contract body.
|
|
221
|
+
|
|
222
|
+
HTML comments are dropped and continuation lines join their entry. A
|
|
223
|
+
body that says only `None` has no shapes.
|
|
224
|
+
"""
|
|
225
|
+
cleaned = re.sub(r"(?s)<!--.*?-->", "", body).strip()
|
|
226
|
+
if " ".join(cleaned.split()) in {"", "None", "- None"}:
|
|
227
|
+
return set()
|
|
228
|
+
entries = re.split(r"(?m)^-[ \t]+", cleaned)
|
|
229
|
+
return {shape for shape in (" ".join(entry.split()) for entry in entries) if shape}
|
|
230
|
+
|
|
231
|
+
|
|
200
232
|
def overlapping_paths(left: Sequence[str], right: Sequence[str]) -> List[str]:
|
|
201
233
|
"""Return the sorted paths that two task `files` lists both cover.
|
|
202
234
|
|
|
@@ -875,8 +907,50 @@ def find_bash() -> str:
|
|
|
875
907
|
)
|
|
876
908
|
|
|
877
909
|
|
|
910
|
+
def _remove_quietly(path: str) -> None:
|
|
911
|
+
try:
|
|
912
|
+
os.remove(path)
|
|
913
|
+
except OSError:
|
|
914
|
+
pass
|
|
915
|
+
|
|
916
|
+
|
|
917
|
+
def _command_script_dir() -> str:
|
|
918
|
+
"""A temp directory for command scripts that cannot dirty the running tree.
|
|
919
|
+
|
|
920
|
+
TMPDIR/TEMP/TMP may point into the tree being verified, and a script file
|
|
921
|
+
there shows up in git status and blocks landings (#448), so fall back to a
|
|
922
|
+
system directory when the environment temp sits inside the current tree.
|
|
923
|
+
"""
|
|
924
|
+
candidate = os.path.realpath(tempfile.gettempdir())
|
|
925
|
+
tree = os.path.realpath(os.getcwd())
|
|
926
|
+
if candidate != tree and not candidate.startswith(tree + os.sep):
|
|
927
|
+
return candidate
|
|
928
|
+
for fallback in (
|
|
929
|
+
os.path.join(os.environ.get("SystemRoot", r"C:\Windows"), "Temp"),
|
|
930
|
+
"/var/tmp",
|
|
931
|
+
"/tmp",
|
|
932
|
+
):
|
|
933
|
+
resolved = os.path.realpath(fallback)
|
|
934
|
+
if os.path.isdir(resolved) and resolved != tree and not resolved.startswith(tree + os.sep):
|
|
935
|
+
return resolved
|
|
936
|
+
raise OSError("no temp directory outside the current worktree for a command script")
|
|
937
|
+
|
|
938
|
+
|
|
878
939
|
def bash_argv(command: str) -> List[str]:
|
|
879
|
-
|
|
940
|
+
if os.name != "nt":
|
|
941
|
+
return [find_bash(), "-c", command]
|
|
942
|
+
# Git for Windows' bash (MSYS2) cuts an argument that carries a double
|
|
943
|
+
# quote near 8 KB before bash sees it, so a long Verify dies mid-script
|
|
944
|
+
# or silently passes when the cut lands in comments (#448). Run the exact
|
|
945
|
+
# bytes from a temp file outside every worktree instead.
|
|
946
|
+
bash = find_bash()
|
|
947
|
+
descriptor, path = tempfile.mkstemp(
|
|
948
|
+
prefix="gsd-path-command-", suffix=".sh", dir=_command_script_dir()
|
|
949
|
+
)
|
|
950
|
+
with os.fdopen(descriptor, "w", encoding="utf-8", newline="\n") as handle:
|
|
951
|
+
handle.write(command)
|
|
952
|
+
atexit.register(_remove_quietly, path)
|
|
953
|
+
return [bash, path]
|
|
880
954
|
|
|
881
955
|
|
|
882
956
|
# cmd.exe re-parses the arguments of a .cmd or .bat target, so these would
|
|
@@ -781,7 +781,14 @@ def validate_panel_skip_receipt(path: Path, expected_mode: str) -> None:
|
|
|
781
781
|
not isinstance(item, dict)
|
|
782
782
|
or set(item) != {"family", "reason"}
|
|
783
783
|
or item["family"] not in KNOWN_FAMILIES
|
|
784
|
-
|
|
784
|
+
# review_panel resolve writes every one of these reasons (#460);
|
|
785
|
+
# the receipt must validate whatever the helper can produce.
|
|
786
|
+
or item["reason"] not in {
|
|
787
|
+
"parent family",
|
|
788
|
+
"not advertised",
|
|
789
|
+
"cap",
|
|
790
|
+
"excluded reviewer family",
|
|
791
|
+
}
|
|
785
792
|
for item in skipped
|
|
786
793
|
):
|
|
787
794
|
raise ArchiveError(f"{path.name} has invalid skipped families")
|
|
@@ -1998,6 +2005,62 @@ def member_ship_rows(project: Path, archive: Path, ship_commit: str) -> list:
|
|
|
1998
2005
|
return rows
|
|
1999
2006
|
|
|
2000
2007
|
|
|
2008
|
+
def tag_metadata_fields(project: Path, tag_ref: str) -> dict[str, str]:
|
|
2009
|
+
"""The Key: value fields of an annotated milestone tag's message."""
|
|
2010
|
+
message = require_git_success(
|
|
2011
|
+
run_git(project, "for-each-ref", "--format=%(contents)", tag_ref),
|
|
2012
|
+
"inspect milestone tag",
|
|
2013
|
+
)
|
|
2014
|
+
fields: dict[str, str] = {}
|
|
2015
|
+
for line in message.splitlines():
|
|
2016
|
+
match = re.fullmatch(r"(Mode|Pull-Request|Ship|Landing): (.+)", line)
|
|
2017
|
+
if match:
|
|
2018
|
+
if match.group(1) in fields:
|
|
2019
|
+
raise ArchiveError("milestone tag repeats metadata")
|
|
2020
|
+
fields[match.group(1)] = match.group(2)
|
|
2021
|
+
return fields
|
|
2022
|
+
|
|
2023
|
+
|
|
2024
|
+
def accept_rebased_ship(
|
|
2025
|
+
project: Path, archive: Path, ship_commit: str, reviewed_head: str
|
|
2026
|
+
) -> Optional[str]:
|
|
2027
|
+
"""Accept the rebased ship copy a rebase merge landed, keyed by the milestone tag.
|
|
2028
|
+
|
|
2029
|
+
Historical validation only (issue #450): after a rebase merge the landed
|
|
2030
|
+
ship copy's parent is never the reviewed HEAD, so the strict check cannot
|
|
2031
|
+
pass over landed history. Acceptance requires the milestone tag to name
|
|
2032
|
+
exactly this commit as its Landing, the original ship commit the tag
|
|
2033
|
+
names to still resolve, the strict invariant re-proven against the
|
|
2034
|
+
original ship's parent, and the rebase replay to hold. Returns the tag's
|
|
2035
|
+
original Ship sha, or None when the landing is not a proven rebase: a
|
|
2036
|
+
landing whose originals no longer resolve anywhere is refused, because
|
|
2037
|
+
the per-task landing proofs elsewhere in validation pin the original
|
|
2038
|
+
commits and a tag receipt alone cannot carry a full validation.
|
|
2039
|
+
"""
|
|
2040
|
+
tag_ref = f"refs/tags/milestone/{archive.name}"
|
|
2041
|
+
if run_git(project, "rev-parse", "--verify", "--quiet", tag_ref).returncode != 0:
|
|
2042
|
+
return None
|
|
2043
|
+
try:
|
|
2044
|
+
fields = tag_metadata_fields(project, tag_ref)
|
|
2045
|
+
except ArchiveError:
|
|
2046
|
+
return None
|
|
2047
|
+
if fields.get("Mode") != "pull-request":
|
|
2048
|
+
return None
|
|
2049
|
+
original = fields.get("Ship")
|
|
2050
|
+
if not original or fields.get("Landing") != ship_commit:
|
|
2051
|
+
return None
|
|
2052
|
+
if run_git(project, "cat-file", "-e", f"{original}^{{commit}}").returncode != 0:
|
|
2053
|
+
return None
|
|
2054
|
+
original_parent = require_git_success(
|
|
2055
|
+
run_git(project, "rev-parse", f"{original}^"), "resolve original ship parent"
|
|
2056
|
+
)
|
|
2057
|
+
if not reviewed_head_covers(project, reviewed_head, original_parent):
|
|
2058
|
+
return None
|
|
2059
|
+
if _common.rebase_landing_problem(project, original, ship_commit) is not None:
|
|
2060
|
+
return None
|
|
2061
|
+
return original
|
|
2062
|
+
|
|
2063
|
+
|
|
2001
2064
|
def validate(repo: Path, *, historical: bool = False) -> dict:
|
|
2002
2065
|
project, _, state, configured, archive, archived_files, reviewed_head = prepared_transaction(
|
|
2003
2066
|
repo,
|
|
@@ -2093,11 +2156,17 @@ def validate(repo: Path, *, historical: bool = False) -> dict:
|
|
|
2093
2156
|
|
|
2094
2157
|
members = member_ship_rows(project, archive, ship_commit)
|
|
2095
2158
|
ship_parent = require_git_success(run_git(project, "rev-parse", f"{ship_commit}^"), "resolve ship parent")
|
|
2159
|
+
rebased_ship: Optional[str] = None
|
|
2096
2160
|
if not reviewed_head_covers(project, reviewed_head, ship_parent):
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2161
|
+
rebased_ship = (
|
|
2162
|
+
accept_rebased_ship(project, archive, ship_commit, reviewed_head)
|
|
2163
|
+
if historical else None
|
|
2100
2164
|
)
|
|
2165
|
+
if rebased_ship is None:
|
|
2166
|
+
raise ArchiveError(
|
|
2167
|
+
f"FINAL.md Reviewed HEAD {reviewed_head} does not match ship parent {ship_parent}; "
|
|
2168
|
+
f"{_common.RUNTIME_PIN_ALLOWANCE}"
|
|
2169
|
+
)
|
|
2101
2170
|
require_canonical_commit_body(
|
|
2102
2171
|
project,
|
|
2103
2172
|
ship_commit,
|
|
@@ -2141,6 +2210,8 @@ def validate(repo: Path, *, historical: bool = False) -> dict:
|
|
|
2141
2210
|
)
|
|
2142
2211
|
|
|
2143
2212
|
result = {"archive": configured, "commit": ship_commit}
|
|
2213
|
+
if rebased_ship is not None:
|
|
2214
|
+
result["rebased_ship"] = rebased_ship
|
|
2144
2215
|
if members:
|
|
2145
2216
|
result["members"] = members
|
|
2146
2217
|
return result
|
|
@@ -2178,6 +2249,15 @@ def parser() -> argparse.ArgumentParser:
|
|
|
2178
2249
|
)
|
|
2179
2250
|
validate_integrated_parser.add_argument("--repo", required=True, type=Path)
|
|
2180
2251
|
validate_integrated_parser.add_argument("--slug", required=True)
|
|
2252
|
+
validate_integrated_parser.add_argument(
|
|
2253
|
+
"--historical",
|
|
2254
|
+
action="store_true",
|
|
2255
|
+
help=(
|
|
2256
|
+
"validate from the landed default branch after the milestone closed "
|
|
2257
|
+
"(the bound branch may be gone; a rebase-merged landing is proven "
|
|
2258
|
+
"through the milestone tag)"
|
|
2259
|
+
),
|
|
2260
|
+
)
|
|
2181
2261
|
|
|
2182
2262
|
refresh_parser = subparsers.add_parser(
|
|
2183
2263
|
"refresh-origin",
|
|
@@ -2190,6 +2270,17 @@ def parser() -> argparse.ArgumentParser:
|
|
|
2190
2270
|
help="after preflight, integrate each locked member in lock order before the ship commit",
|
|
2191
2271
|
)
|
|
2192
2272
|
close_members_parser.add_argument("--repo", required=True, type=Path)
|
|
2273
|
+
close_members_parser.add_argument(
|
|
2274
|
+
"--adopt-landed-member",
|
|
2275
|
+
action="append",
|
|
2276
|
+
default=[],
|
|
2277
|
+
metavar="MEMBER=SHA",
|
|
2278
|
+
help=(
|
|
2279
|
+
"user-approved recovery for a member whose reviewed HEAD already reached its "
|
|
2280
|
+
"default branch outside Path; MEMBER=SHA names the live default-branch tip "
|
|
2281
|
+
"to adopt as the landing"
|
|
2282
|
+
),
|
|
2283
|
+
)
|
|
2193
2284
|
|
|
2194
2285
|
integrate_parser = subparsers.add_parser(
|
|
2195
2286
|
"integrate",
|
|
@@ -2197,6 +2288,16 @@ def parser() -> argparse.ArgumentParser:
|
|
|
2197
2288
|
)
|
|
2198
2289
|
integrate_parser.add_argument("--repo", required=True, type=Path)
|
|
2199
2290
|
integrate_parser.add_argument("--slug", required=True)
|
|
2291
|
+
integrate_parser.add_argument(
|
|
2292
|
+
"--adopt-published-bound",
|
|
2293
|
+
metavar="SHA",
|
|
2294
|
+
help=(
|
|
2295
|
+
"user-approved recovery when origin's bound branch is a strict ancestor "
|
|
2296
|
+
"of the ship commit (a build task pushed it as a deploy pin, #464); "
|
|
2297
|
+
"the 40-hex SHA must equal the live remote tip exactly; the publish "
|
|
2298
|
+
"fast-forwards under the existing exact lease. Direct integration only"
|
|
2299
|
+
),
|
|
2300
|
+
)
|
|
2200
2301
|
|
|
2201
2302
|
abandon_parser = subparsers.add_parser(
|
|
2202
2303
|
"abandon",
|
|
@@ -2225,13 +2326,34 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
|
|
|
2225
2326
|
elif arguments.command == "abandon":
|
|
2226
2327
|
result = abandon(arguments.repo, arguments.slug, arguments.reason)
|
|
2227
2328
|
elif arguments.command == "validate-integrated":
|
|
2228
|
-
result = integration.validate_integrated(
|
|
2329
|
+
result = integration.validate_integrated(
|
|
2330
|
+
arguments.repo, arguments.slug, historical=arguments.historical
|
|
2331
|
+
)
|
|
2229
2332
|
elif arguments.command == "refresh-origin":
|
|
2230
2333
|
result = integration.refresh_origin(arguments.repo)
|
|
2231
2334
|
elif arguments.command == "close-members":
|
|
2232
|
-
|
|
2335
|
+
adopt: dict[str, str] = {}
|
|
2336
|
+
for item in arguments.adopt_landed_member:
|
|
2337
|
+
name, separator, sha = item.partition("=")
|
|
2338
|
+
if not name or not separator or not re.fullmatch(r"[0-9a-f]{40}", sha):
|
|
2339
|
+
parser_error = (
|
|
2340
|
+
f"--adopt-landed-member expects MEMBER=SHA (40-hex), got {item!r}"
|
|
2341
|
+
)
|
|
2342
|
+
print(parser_error, file=sys.stderr)
|
|
2343
|
+
return 2
|
|
2344
|
+
adopt[name] = sha
|
|
2345
|
+
result = integration.close_members(arguments.repo, adopt=adopt or None)
|
|
2233
2346
|
elif arguments.command == "integrate":
|
|
2234
|
-
|
|
2347
|
+
adopt_bound = arguments.adopt_published_bound
|
|
2348
|
+
if adopt_bound is not None:
|
|
2349
|
+
adopt_bound = adopt_bound.lower()
|
|
2350
|
+
if adopt_bound is not None and not re.fullmatch(r"[0-9a-f]{40}", adopt_bound):
|
|
2351
|
+
print(
|
|
2352
|
+
f"--adopt-published-bound expects a 40-hex SHA, got {adopt_bound!r}",
|
|
2353
|
+
file=sys.stderr,
|
|
2354
|
+
)
|
|
2355
|
+
return 2
|
|
2356
|
+
result = integration.integrate(arguments.repo, arguments.slug, adopt_bound)
|
|
2235
2357
|
else:
|
|
2236
2358
|
result = validate(arguments.repo)
|
|
2237
2359
|
except (ArchiveError, OSError) as error:
|