@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
|
@@ -8,19 +8,45 @@ tasks in this wave as the rubric. For each task:
|
|
|
8
8
|
Never infer either SHA from task ids, branch position, or nearby history.
|
|
9
9
|
2. Fail a missing, malformed, or invalid SHA. Inspect the commit with
|
|
10
10
|
`git show --format=fuller --stat --patch <commit> --`.
|
|
11
|
-
3.
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
11
|
+
3. Select the inspection mode your brief supplies and record it — never
|
|
12
|
+
improvise a third:
|
|
13
|
+
- **Reconstructed product** (default): the brief supplies a verify
|
|
14
|
+
sidecar at `base`. In it, apply only the complete binary patch from
|
|
15
|
+
`commit^..commit` for the task's declared product files and inspect
|
|
16
|
+
the diff. Use the orchestrator's recorded isolated Verify output in
|
|
17
|
+
that task's Log as the Verify evidence.
|
|
18
|
+
- **Immutable patch**: the brief supplies one later combined checkout
|
|
19
|
+
with report-write authority only. Inspect the immutable objects
|
|
20
|
+
directly — `git show`/`git diff` on the recorded commits — and prove
|
|
21
|
+
the identity before judging: (a) the landing commit's parent
|
|
22
|
+
`commit^` equals the task file's recorded `base` (or the patch's
|
|
23
|
+
resulting blobs at `base` equal the landing commit's blobs for the
|
|
24
|
+
declared files), and (b) any file judged from the combined checkout
|
|
25
|
+
has its blob there equal to the landing commit's blob at that path.
|
|
26
|
+
Without both identities the review blocks: a mismatch means the
|
|
27
|
+
supplied checkout is not the reviewed product.
|
|
28
|
+
In both modes: do not re-run the task Verify command or PLAN.md's
|
|
29
|
+
project Verify; re-run a command only when the recorded output plus
|
|
30
|
+
the diff cannot check the criterion. Never use the primary worktree
|
|
31
|
+
or branch tip as task evidence. Do not create or remove Git worktrees
|
|
32
|
+
yourself.
|
|
33
|
+
4. Mark `pass` or `fail`. Before the evidence separator (` — `), name the
|
|
34
|
+
criterion (see Review file format below): copy the complete task
|
|
35
|
+
acceptance or interface criterion verbatim, or — on a `pass` line only —
|
|
36
|
+
cite the criterion by its stable id in the task's numbered acceptance or
|
|
37
|
+
interface list (`T012/AC3`, `T012/IF2`) together with the evidence. A
|
|
38
|
+
`fail` line always copies the criterion verbatim: finding grouping binds
|
|
39
|
+
that text to the task. Give the observed result, `file:line`, and
|
|
23
40
|
concrete fix direction for each failure.
|
|
41
|
+
5. Account for every claim. Each acceptance criterion (`T012/AC3`) and
|
|
42
|
+
interface bullet (`T012/IF2`) of the task must appear in its
|
|
43
|
+
section as an evidence reference, a finding, or an explicitly
|
|
44
|
+
authorized disposition — a bare line naming the id with
|
|
45
|
+
`disposition: <authority and date> (<Cn>): <reason>`, where `Cn` is a
|
|
46
|
+
numbered INTENT.md correction. No citation, or a `Cn` that
|
|
47
|
+
`## Corrections` does not define, fails the gate. One observation may
|
|
48
|
+
support several claims when each is identified by its id. The wave gate
|
|
49
|
+
rejects a review that leaves a claim unaccounted for.
|
|
24
50
|
|
|
25
51
|
A Log entry `orchestrator regression Verify of <task id> retired: superseded
|
|
26
52
|
by <task id>` is not evidence: judge the superseding task's acceptance
|
|
@@ -30,8 +56,12 @@ criteria. The same applies to the entry `orchestrator regression Verify of
|
|
|
30
56
|
Then check each INTENT.md success criterion owned by a task in this wave
|
|
31
57
|
against that same isolated product. In each `### SCn —` heading, copy the
|
|
32
58
|
complete criterion text from INTENT.md `## Success criteria` (see Review
|
|
33
|
-
file format below).
|
|
34
|
-
|
|
59
|
+
file format below). Write the headings in ascending SC id order (SC2 before
|
|
60
|
+
SC4), and separate the id from the text with an em dash (—). Mark `pass`
|
|
61
|
+
or `fail` with evidence; a failed owned SC blocks the wave. Under a `pass`
|
|
62
|
+
SC, record each subclaim a numbered INTENT.md correction excludes as
|
|
63
|
+
`- Excluded by <Cn>: <subclaim>`; the gate rejects an exclusion under a
|
|
64
|
+
failing SC or one that cites an undefined correction. Omit the Intent coverage section when no task in the wave owns
|
|
35
65
|
an SC. When INTENT.md `## Edge coverage` or `## Prohibitions` has rows for
|
|
36
66
|
an owned SC, judge it against those rulings too: cite each `held-out` edge
|
|
37
67
|
id with its held-out test's recorded output and each `judgment` prohibition
|
|
@@ -40,8 +70,12 @@ fails the SC.
|
|
|
40
70
|
|
|
41
71
|
Allow changed paths only in the task's `files` plus its assigned task file.
|
|
42
72
|
Within the task file, allow orchestrator-owned `base`, `worktree`,
|
|
43
|
-
`task_branch`, `status`, and `agent` fields
|
|
44
|
-
entries; block contract-body changes
|
|
73
|
+
`task_branch`, `status`, and `agent` fields — and, on a member task,
|
|
74
|
+
`member_base` — plus append-only Log entries; block contract-body changes
|
|
75
|
+
in the task commit. `base` names the task's starting commit in the
|
|
76
|
+
coordinator repository; `member_base` names the starting commit in the
|
|
77
|
+
member repository from which the member task branch and isolate were
|
|
78
|
+
created. Warn on disabled tests
|
|
45
79
|
or Verify commands that cannot fail unless either defeats a criterion. Set the
|
|
46
80
|
wave verdict to `pass` only when every task passes and every owned INTENT
|
|
47
81
|
success criterion for the wave passes.
|
|
@@ -65,10 +99,14 @@ criterion text does not match. Apply the rules before you write the file.
|
|
|
65
99
|
`- ❌ ` for fail. Each task section and each `### SCn —` block needs one
|
|
66
100
|
or more lines with the marker of its own verdict. A failed task needs a
|
|
67
101
|
`- ❌ ` criterion line, also when other text describes the failure.
|
|
68
|
-
- **Criterion text.** In a
|
|
69
|
-
copy every word of the criterion. Join wrapped lines and
|
|
70
|
-
one space. You can keep or remove the `- ` markers of
|
|
71
|
-
A changed, added, removed or moved word does not
|
|
102
|
+
- **Criterion text.** In a `### SCn —` heading and on every `- ❌ ` fail
|
|
103
|
+
line, copy every word of the criterion. Join wrapped lines and
|
|
104
|
+
sub-bullets with one space. You can keep or remove the `- ` markers of
|
|
105
|
+
the sub-bullets. A changed, added, removed or moved word does not
|
|
106
|
+
match. On a `- ✅ ` pass line, name the criterion either by copying it
|
|
107
|
+
verbatim or by citing its stable id in the task's numbered acceptance
|
|
108
|
+
or interface list (`T001/AC1`, `T001/IF2`) together with the evidence;
|
|
109
|
+
the wave gate does not compare pass-line criterion text.
|
|
72
110
|
- **Angle brackets.** Text such as `<b>` or `<path>` outside backticks reads
|
|
73
111
|
as an unfilled template placeholder and is rejected. Write literal markup
|
|
74
112
|
and code in backticks inside a sentence, for example: the output has
|
|
@@ -116,8 +154,9 @@ runtime checks freshness and derives the final view from these records.
|
|
|
116
154
|
## Deep review lenses
|
|
117
155
|
|
|
118
156
|
A `deep` wave spawns two independent reviewers in parallel, each with a fresh
|
|
119
|
-
context and its own
|
|
120
|
-
|
|
157
|
+
context and its own isolated workspace at the recorded review base (a verify
|
|
158
|
+
sidecar in reconstructed mode, or the supplied combined checkout in immutable
|
|
159
|
+
mode). Every Wave mode rule above applies to both lenses.
|
|
121
160
|
|
|
122
161
|
- **Contract** — logical task name
|
|
123
162
|
`review_wave_<wave>_cycle_<cycle>_contract`; stages
|
|
@@ -5,16 +5,18 @@ Verify written GSD Path expectations and block failures. Never fix code.
|
|
|
5
5
|
## Brief contract
|
|
6
6
|
|
|
7
7
|
- Require the mode, all inputs, exact canonical output path, applicable
|
|
8
|
-
template, repository root
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
template, and repository root by absolute path. Wave mode also requires
|
|
9
|
+
the absolute INTENT.md path and names the inspection mode: a verify
|
|
10
|
+
sidecar root for reconstructed-product review, or one combined checkout
|
|
11
|
+
with report-write authority for immutable-patch review. The brief must
|
|
12
|
+
also name the staged output path under that workspace.
|
|
12
13
|
- Read AGENTS.md and the template before reviewing.
|
|
13
|
-
- Write only the exact assigned review output under the supplied
|
|
14
|
+
- Write only the exact assigned review output under the supplied workspace
|
|
14
15
|
at its staged `.project/review/` path. The orchestrator validates and
|
|
15
16
|
atomically transfers that file to the canonical primary `.project/review/`
|
|
16
17
|
path after collection. Temporary patch/test effects are allowed solely
|
|
17
|
-
inside supplied sidecars
|
|
18
|
+
inside supplied sidecars; an immutable-mode combined checkout carries
|
|
19
|
+
report-write authority only. Never edit a product path or any other
|
|
18
20
|
primary-worktree path. Stop on a missing input or template.
|
|
19
21
|
|
|
20
22
|
## Load the assigned mode
|
|
@@ -20,6 +20,42 @@ references to bundled copies of those helpers. Other skill helpers, templates,
|
|
|
20
20
|
and role briefs remain in the installed skill bundle. Pass `-B` to Python helper
|
|
21
21
|
invocations so execution does not create bytecode in the runtime store.
|
|
22
22
|
|
|
23
|
+
When the runtime enforcing an active task cannot finish it, one narrowly
|
|
24
|
+
authorized recovery exists:
|
|
25
|
+
`python3 -B <absolute isolation.py> runtime-hotfix --repo <absolute primary>
|
|
26
|
+
--task-id <id> --runtime <64-hex runtime digest printed by runtime_store.py publish>
|
|
27
|
+
--authorized-by <the owner ruling>` validates the replacement runtime,
|
|
28
|
+
re-runs that task's Verify in a fresh sidecar at the recorded base under the
|
|
29
|
+
replacement pin, and records the receipt twice: a Log line in the task file
|
|
30
|
+
naming the task, base, and both runtime digests, and an identical blob behind
|
|
31
|
+
the local ref `refs/gsd-path/runtime-hotfix/<task>-<base>`. Only a Log line
|
|
32
|
+
whose exact twin that ref holds later admits `.gsd-path/runtime.json` into that
|
|
33
|
+
task's landing, and only when the pin names exactly the receipt's runtime; any
|
|
34
|
+
other task, base, a Log line alone, or a bare mention authorizes nothing, and a
|
|
35
|
+
failing re-run refuses the hotfix. Limits: the command serves only a serial
|
|
36
|
+
task working in the primary and refuses parallel or member tasks; the ref is
|
|
37
|
+
local repository state, so a clone without it cannot land the pin and the
|
|
38
|
+
proof fails closed. The guard applies two rules to agent shell commands. It
|
|
39
|
+
denies any command where `runtime-hotfix` directly follows `isolation.py`, stdin
|
|
40
|
+
`-`, or an `isolation` module operand (`-m isolation`, `-Bm isolation`,
|
|
41
|
+
`-misolation`). It also denies every Git command with a word, including global
|
|
42
|
+
`-c` options and inline variable assignments, that contains `refs/gsd-path/` or
|
|
43
|
+
`runtime-hotfix`. This deliberately blocks read-only uses such as
|
|
44
|
+
`git log refs/gsd-path/<ref>` or `git log --grep runtime-hotfix`, and commit
|
|
45
|
+
messages or branch names that contain `runtime-hotfix`. Limits: it cannot see a copied or renamed
|
|
46
|
+
`isolation.py`, a refspec stored in a Git config file, a ref write made from
|
|
47
|
+
inside another program, a direct edit of files under `.git/`, or shell
|
|
48
|
+
expansion that splits both protected words, such as
|
|
49
|
+
`refs/gs{d,}-path/task-authorizations/<task>`.
|
|
50
|
+
|
|
51
|
+
Publishing a runtime to the shared store is project-free:
|
|
52
|
+
`python3 -B <absolute runtime_store.py> publish --source <absolute package root>`
|
|
53
|
+
verifies and publishes the content-addressed runtime, printing its version,
|
|
54
|
+
digest, and store path as JSON; `--provenance` records a note in that output
|
|
55
|
+
only. Publication alone neither authorizes execution nor changes a project's
|
|
56
|
+
trusted runtime — a project runs the bytes only when its own pin names that
|
|
57
|
+
digest.
|
|
58
|
+
|
|
23
59
|
The selected runtime is authoritative for each helper in its `manifest.json`;
|
|
24
60
|
this includes `review_findings.py` and `task_context.py`. A selected runtime
|
|
25
61
|
that was published before a helper became a runtime file does not list that
|
|
@@ -96,6 +96,23 @@ Present the kept items with a recommended ruling and record each as a row:
|
|
|
96
96
|
Work that affects no person directly records a single row instead, and no
|
|
97
97
|
other: `| N1 | all | none | dismissed | <why no person is affected> |`.
|
|
98
98
|
|
|
99
|
+
## Absence criteria
|
|
100
|
+
|
|
101
|
+
A criterion that claims absence ("X does not exist", "no X anywhere",
|
|
102
|
+
"never Y"), including one written from a prohibition, has no end until it
|
|
103
|
+
says where to look. Each review can look somewhere new, so the build loops
|
|
104
|
+
to the cycle cap. Every absence criterion states:
|
|
105
|
+
|
|
106
|
+
- the search space: the files, surfaces, or artifacts to check;
|
|
107
|
+
- the check: the command or observation that covers that space;
|
|
108
|
+
- the absent result: the output that counts as absent.
|
|
109
|
+
|
|
110
|
+
When one is missing, propose a bounded form in the same question round as
|
|
111
|
+
the edges, recommended form first, and amend the criterion after the user
|
|
112
|
+
rules. Do not choose the search space yourself: a narrower space is a scope
|
|
113
|
+
ruling. Under pre-approval, an unbounded absence criterion is a required
|
|
114
|
+
answer: stop and ask.
|
|
115
|
+
|
|
99
116
|
## INTENT.md tables
|
|
100
117
|
|
|
101
118
|
```markdown
|
|
@@ -131,7 +148,8 @@ rechecked; a near-miss heading fails the gate rather than switching it off.
|
|
|
131
148
|
the owning task's Approach so the coder knows the line.
|
|
132
149
|
- **Review.** A row pins the reading of its criterion. A reviewer does not
|
|
133
150
|
pass a reading that contradicts a ruling in either table, and cites each
|
|
134
|
-
held-out edge and judgment prohibition id with its evidence.
|
|
151
|
+
held-out edge and judgment prohibition id with its evidence. An absence
|
|
152
|
+
criterion is judged over its stated search space only.
|
|
135
153
|
- **Final review.** A criterion that carries a held-out edge or a judgment
|
|
136
154
|
prohibition is `met` only when its Check, Observed, or Reference cites
|
|
137
155
|
every such id with the evidence: the held-out test's recorded passing
|
|
@@ -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:
|
|
@@ -144,7 +144,13 @@ def settled_tasks(repo: Path) -> dict[str, tuple[str, dict]]:
|
|
|
144
144
|
raise state.PipelineStateError(f"invalid recovery task path: {path.name}")
|
|
145
145
|
text = state._read_real_file(path, path.name)
|
|
146
146
|
fields = task_fields(text, path.name)
|
|
147
|
-
if fields.get("status")
|
|
147
|
+
if fields.get("status") == "withdrawn":
|
|
148
|
+
# A cited tombstone is settled inventory; recovery skips it (#483).
|
|
149
|
+
if not state._common.withdrawn_citations(text):
|
|
150
|
+
raise state.PipelineStateError(
|
|
151
|
+
f"withdrawn task lacks an owner citation: {path.name}"
|
|
152
|
+
)
|
|
153
|
+
elif fields.get("status") not in {"pending", "done"}:
|
|
148
154
|
raise state.PipelineStateError(f"settle task ownership before recovery: {path.name}")
|
|
149
155
|
if fields["status"] == "pending" and any(
|
|
150
156
|
fields.get(key) != "null" for key in ("agent", "base", "worktree", "task_branch")
|
|
@@ -250,7 +256,24 @@ def validate_review_backup(repo: Path, recovery: dict, directory: Path) -> None:
|
|
|
250
256
|
for path in directory.rglob("*")
|
|
251
257
|
if path.is_file() and not state._common.is_ignored_junk(path)} if directory.exists() else {}
|
|
252
258
|
if actual != expected:
|
|
253
|
-
|
|
259
|
+
extra = sorted(set(actual) - set(expected))
|
|
260
|
+
changed = sorted(
|
|
261
|
+
name for name in set(actual) & set(expected) if actual[name] != expected[name]
|
|
262
|
+
)
|
|
263
|
+
missing = sorted(set(expected) - set(actual))
|
|
264
|
+
detail = ""
|
|
265
|
+
if extra:
|
|
266
|
+
detail += f" files in the review backup that the recovery base does not have: {', '.join(extra)}."
|
|
267
|
+
if changed:
|
|
268
|
+
detail += f" files differing from the recovery base: {', '.join(changed)}."
|
|
269
|
+
if missing:
|
|
270
|
+
detail += f" files at the recovery base missing from the review backup: {', '.join(missing)}."
|
|
271
|
+
raise state.PipelineStateError(
|
|
272
|
+
"review backup differs from the recovery base." + detail
|
|
273
|
+
+ " prepare-build-recovery must run before re-planning touches any recovery"
|
|
274
|
+
" artifact; remove the extra files and restore the missing or changed ones"
|
|
275
|
+
" to the recovery base's content, then rerun prepare"
|
|
276
|
+
)
|
|
254
277
|
|
|
255
278
|
|
|
256
279
|
def prepare(repo: Path) -> dict:
|