@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
|
@@ -231,8 +231,14 @@ def _retire_execution(repo, head, execution):
|
|
|
231
231
|
isolation.retire(repo, worktree, execution["branch"], force=False)
|
|
232
232
|
|
|
233
233
|
|
|
234
|
-
def verify_project(repo, expected_head):
|
|
235
|
-
"""Execute project Verify in isolation, retaining output for retry reuse.
|
|
234
|
+
def verify_project(repo, expected_head, *, rerun: bool = False):
|
|
235
|
+
"""Execute project Verify in isolation, retaining output for retry reuse.
|
|
236
|
+
|
|
237
|
+
With ``rerun`` (the owner's explicit ``prepare-final --rerun`` after a
|
|
238
|
+
host-caused failure diagnosed through the forensics contract), a recorded
|
|
239
|
+
execution at the same command and commit is retired and the command runs
|
|
240
|
+
again, appending a second ledger entry; the failed entry stays as history.
|
|
241
|
+
"""
|
|
236
242
|
repo = Path(repo).resolve()
|
|
237
243
|
_require_ship_inputs(repo, expected_head)
|
|
238
244
|
plan = contracts._read(repo, ".project/plan/PLAN.md")
|
|
@@ -247,7 +253,6 @@ def verify_project(repo, expected_head):
|
|
|
247
253
|
destination = repo / relative
|
|
248
254
|
if destination.is_symlink():
|
|
249
255
|
raise contracts.HandoffError("project verification evidence must be a real file")
|
|
250
|
-
expected_destination = hashlib.sha256(destination.read_bytes()).hexdigest() if destination.exists() else None
|
|
251
256
|
execution = entry.get("execution") if entry else None
|
|
252
257
|
if entry and execution is None:
|
|
253
258
|
raise contracts.HandoffError("recorded verification lacks output; preserve and reconcile its evidence")
|
|
@@ -263,11 +268,16 @@ def verify_project(repo, expected_head):
|
|
|
263
268
|
relative, relative, journal["expected_destination"],
|
|
264
269
|
)
|
|
265
270
|
_retire_execution(repo, expected_head, execution)
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
+
if not rerun:
|
|
272
|
+
view = _gap_view(command, expected_head, execution, waves)
|
|
273
|
+
if not destination.exists() or destination.read_text(encoding="utf-8") != view:
|
|
274
|
+
_common.atomic_write(destination, view)
|
|
275
|
+
return {"passed": entry["result"] == "pass", "reused": True,
|
|
276
|
+
"path": str(destination), "execution": execution}
|
|
277
|
+
# Hash after any prior execution's recovery: a rerun's old-collection
|
|
278
|
+
# recovery can rewrite the gap view, and the fresh collection must expect
|
|
279
|
+
# the file as it stands now, not before the recovery (#454).
|
|
280
|
+
expected_destination = hashlib.sha256(destination.read_bytes()).hexdigest() if destination.exists() else None
|
|
271
281
|
sidecar = isolation.isolate_verify(repo, expected_head, "project-verify")
|
|
272
282
|
worktree = Path(sidecar["worktree"])
|
|
273
283
|
try:
|
|
@@ -295,18 +305,20 @@ def verify_project(repo, expected_head):
|
|
|
295
305
|
execution["members"] = {name: record["tip"] for name, record in sidecars.items()}
|
|
296
306
|
else:
|
|
297
307
|
build_state.verify_record(str(repo), command, expected_head,
|
|
298
|
-
"pass" if passed else "fail", execution=execution
|
|
308
|
+
"pass" if passed else "fail", execution=execution,
|
|
309
|
+
rerun=rerun)
|
|
299
310
|
isolation.clean_verify(repo, worktree, expected_head, sidecar["branch"])
|
|
300
311
|
_common.atomic_write(worktree / relative, _gap_view(command, expected_head, execution, waves))
|
|
301
312
|
collection = isolation.collect_artifact(repo, worktree, expected_head, sidecar["branch"],
|
|
302
313
|
relative, relative, expected_destination)
|
|
303
314
|
isolation.retire(repo, worktree, sidecar["branch"], force=False)
|
|
304
315
|
return {"passed": passed, "reused": False, "path": str(destination),
|
|
305
|
-
"execution": execution, "collection": collection
|
|
316
|
+
"execution": execution, "collection": collection,
|
|
317
|
+
**({"rerun": True} if rerun else {})}
|
|
306
318
|
|
|
307
319
|
|
|
308
|
-
def prepare_final(repo, expected_head):
|
|
309
|
-
verification = verify_project(repo, expected_head)
|
|
320
|
+
def prepare_final(repo, expected_head, *, rerun: bool = False):
|
|
321
|
+
verification = verify_project(repo, expected_head, rerun=rerun)
|
|
310
322
|
if not verification["passed"]:
|
|
311
323
|
return {"status": "blocked", "verification": verification,
|
|
312
324
|
"reason": "project Verify failed; use its recorded output"}
|
|
@@ -327,9 +339,12 @@ def main(argv=None):
|
|
|
327
339
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
328
340
|
parser.add_argument("--repo", type=Path, required=True)
|
|
329
341
|
parser.add_argument("--expected-head", required=True)
|
|
342
|
+
parser.add_argument("--rerun", action="store_true",
|
|
343
|
+
help="re-execute project Verify at the same head after a host-caused "
|
|
344
|
+
"failure, appending a new ledger entry")
|
|
330
345
|
args = parser.parse_args(argv)
|
|
331
346
|
try:
|
|
332
|
-
result = prepare_final(args.repo.resolve(), args.expected_head)
|
|
347
|
+
result = prepare_final(args.repo.resolve(), args.expected_head, rerun=args.rerun)
|
|
333
348
|
except (contracts.HandoffError, isolation.IsolationError, build_state.BuildStateError,
|
|
334
349
|
OSError, subprocess.CalledProcessError) as error:
|
|
335
350
|
print(str(error), file=sys.stderr)
|
|
@@ -530,10 +530,15 @@ def run_shell(command: str, timeout: float) -> tuple[Optional[int], str]:
|
|
|
530
530
|
except subprocess.TimeoutExpired as drained:
|
|
531
531
|
stdout = drained.stdout or expired.stdout or b""
|
|
532
532
|
stderr = drained.stderr or expired.stderr or b""
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
533
|
+
# Windows communicate() reads in threads that hold each pipe's lock,
|
|
534
|
+
# so close() would block until an orphaned grandchild (bash forks
|
|
535
|
+
# commands it runs from a script file) exits; leave them to the
|
|
536
|
+
# daemon reader threads there.
|
|
537
|
+
if os.name != "nt":
|
|
538
|
+
if process.stdout:
|
|
539
|
+
process.stdout.close()
|
|
540
|
+
if process.stderr:
|
|
541
|
+
process.stderr.close()
|
|
537
542
|
process.kill()
|
|
538
543
|
process.wait(timeout=5)
|
|
539
544
|
output = stdout.decode("utf-8", errors="replace") + stderr.decode("utf-8", errors="replace")
|
|
@@ -34,9 +34,17 @@ MEMBERS_FILE = "MEMBERS.md"
|
|
|
34
34
|
MARKER_SCHEMA = "gsd-path/member/v1"
|
|
35
35
|
MARKER_KEYS = {"schema", "coordinator", "project", "name"}
|
|
36
36
|
NAME_RE = re.compile(r"^[a-z0-9][a-z0-9-]*$")
|
|
37
|
-
FIELDS = ("Checkout", "Remote", "Integration")
|
|
37
|
+
FIELDS = ("Checkout", "Remote", "Integration", "Prepare")
|
|
38
|
+
# The first three are required; Prepare (isolate provisioning, #463) and
|
|
39
|
+
# Default branch are optional.
|
|
40
|
+
REQUIRED_FIELDS = ("Checkout", "Remote", "Integration")
|
|
38
41
|
# Optional: `add` records it one time; a row without it means main.
|
|
39
42
|
DEFAULT_BRANCH_FIELD = "Default branch"
|
|
43
|
+
# Optional: a shell command run inside each fresh member isolate before any
|
|
44
|
+
# Verify (#463). It provisions what a bare worktree lacks — submodules from
|
|
45
|
+
# the primary checkout's object store, gitignored build inputs — and must
|
|
46
|
+
# copy, never link, outside the isolate (#462).
|
|
47
|
+
PREPARE_FIELD = "Prepare"
|
|
40
48
|
INTEGRATIONS = ("default", "direct", "pull-request")
|
|
41
49
|
GITHUB_REMOTE_RE = re.compile(
|
|
42
50
|
r"(?:https://github\.com/|git@github\.com:|ssh://git@github\.com/)([^/\s]+)/([^/\s]+)"
|
|
@@ -206,10 +214,15 @@ def read_members(coordinator: Path) -> list[dict[str, str]]:
|
|
|
206
214
|
return []
|
|
207
215
|
if path.is_symlink() or not path.is_file():
|
|
208
216
|
raise MembersError("MEMBERS.md must be a regular file")
|
|
217
|
+
return parse_members(path.read_text(encoding="utf-8"))
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def parse_members(text: str) -> list[dict[str, str]]:
|
|
221
|
+
"""Parse MEMBERS.md text."""
|
|
209
222
|
members: list[dict[str, str]] = []
|
|
210
223
|
current: Optional[dict[str, str]] = None
|
|
211
224
|
preamble: list[str] = []
|
|
212
|
-
for number, line in enumerate(
|
|
225
|
+
for number, line in enumerate(text.splitlines(), 1):
|
|
213
226
|
if line.startswith("## "):
|
|
214
227
|
name = line[3:].strip()
|
|
215
228
|
if not NAME_RE.fullmatch(name):
|
|
@@ -241,7 +254,7 @@ def read_members(coordinator: Path) -> list[dict[str, str]]:
|
|
|
241
254
|
if len(set(names)) != len(names):
|
|
242
255
|
raise MembersError("MEMBERS.md repeats a member name")
|
|
243
256
|
for member in members:
|
|
244
|
-
missing = [field for field in
|
|
257
|
+
missing = [field for field in REQUIRED_FIELDS if not member.get(field.lower())]
|
|
245
258
|
if missing:
|
|
246
259
|
raise MembersError(f"MEMBERS.md member {member['name']} lacks {', '.join(missing)}")
|
|
247
260
|
if member["integration"] not in INTEGRATIONS:
|
|
@@ -510,6 +523,7 @@ def render(members: Sequence[dict[str, str]]) -> str:
|
|
|
510
523
|
f"Checkout: {member['checkout']}\n"
|
|
511
524
|
f"Remote: {member['remote']}\n"
|
|
512
525
|
f"Integration: {member['integration']}\n"
|
|
526
|
+
+ (f"{PREPARE_FIELD}: {member['prepare']}\n" if "prepare" in member else "")
|
|
513
527
|
# A row that never had the field keeps none, so old rows are not rewritten.
|
|
514
528
|
+ (f"{DEFAULT_BRANCH_FIELD}: {member['default_branch']}\n" if "default_branch" in member else "")
|
|
515
529
|
for member in members
|
|
@@ -1407,6 +1407,12 @@ def _route_state(repo: Path, project_dir: str = ".project") -> dict[str, object]
|
|
|
1407
1407
|
mode="milestone" if status == "done" else "normal",
|
|
1408
1408
|
reason=f"state is {phase}/{status}",
|
|
1409
1409
|
)
|
|
1410
|
+
if phase == "plan" and status == "blocked":
|
|
1411
|
+
return _route_result(
|
|
1412
|
+
state,
|
|
1413
|
+
"block",
|
|
1414
|
+
reason="plan is blocked; resolve the correction or user decision named by the plan gate before resuming",
|
|
1415
|
+
)
|
|
1410
1416
|
if phase == "plan" and status != "done":
|
|
1411
1417
|
return _route_result(state, "run-phase", phase="plan", reason=f"state is {phase}/{status}")
|
|
1412
1418
|
if phase == "plan":
|
|
@@ -2373,9 +2379,9 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
|
|
|
2373
2379
|
args.project_dir,
|
|
2374
2380
|
)
|
|
2375
2381
|
elif args.command == "approve" and (args.defer_checkpoint or args.patch):
|
|
2376
|
-
if args.expected_head is not None:
|
|
2382
|
+
if args.defer_checkpoint and args.expected_head is not None:
|
|
2377
2383
|
raise PipelineStateError(
|
|
2378
|
-
"--expected-head is not accepted with --defer-checkpoint
|
|
2384
|
+
"--expected-head is not accepted with --defer-checkpoint"
|
|
2379
2385
|
)
|
|
2380
2386
|
result = state_checkpoint.defer_approval(
|
|
2381
2387
|
args.repo,
|
|
@@ -2384,6 +2390,8 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
|
|
|
2384
2390
|
args.milestone,
|
|
2385
2391
|
args.patch,
|
|
2386
2392
|
)
|
|
2393
|
+
if args.expected_head is not None:
|
|
2394
|
+
result["expected_head_accepted"] = args.expected_head
|
|
2387
2395
|
elif args.command == "approve":
|
|
2388
2396
|
if args.expected_head is None:
|
|
2389
2397
|
raise PipelineStateError(
|
|
@@ -458,6 +458,21 @@ def _validate_plan_briefs(repo: Path, kind: str, project_dir: str) -> None:
|
|
|
458
458
|
if head.returncode != 0:
|
|
459
459
|
# Pre-Git approval defers base-dependent checks to build.
|
|
460
460
|
return
|
|
461
|
+
if project_dir == ".project":
|
|
462
|
+
# Unsupported .project entries must surface at approval, before build,
|
|
463
|
+
# where removing them is cheapest (#466). Like the checkpoint, judge
|
|
464
|
+
# each git-visible entry by whether it still exists in the worktree.
|
|
465
|
+
project = repo / ".project"
|
|
466
|
+
unsupported = sorted(
|
|
467
|
+
name
|
|
468
|
+
for name in pipeline_state._common.git_visible_entries(project) - PROJECT_ENTRIES
|
|
469
|
+
if (project / name).exists() or (project / name).is_symlink()
|
|
470
|
+
)
|
|
471
|
+
if unsupported:
|
|
472
|
+
raise PipelineStateError(
|
|
473
|
+
"unsupported .project artifacts: " + ", ".join(unsupported)
|
|
474
|
+
+ "; remove or relocate them before approving the plan"
|
|
475
|
+
)
|
|
461
476
|
if __package__:
|
|
462
477
|
from .check_task_briefs import BriefError, _member_bases, plan_validation_bases, validate_task_briefs
|
|
463
478
|
else:
|
|
@@ -128,7 +128,76 @@ def manifest_digest(manifest: dict) -> str:
|
|
|
128
128
|
return hashlib.sha256(json.dumps(manifest, sort_keys=True, separators=(",", ":")).encode()).hexdigest()
|
|
129
129
|
|
|
130
130
|
|
|
131
|
+
def manifest_successors(manifest: dict) -> tuple:
|
|
132
|
+
"""The validated successor digests a runtime's hashed manifest names (#468).
|
|
133
|
+
|
|
134
|
+
`successors` is an optional list of unique 64-hex lowercase store digests.
|
|
135
|
+
It lives inside the manifest, so the runtime's own digest covers it: a
|
|
136
|
+
runtime can only bless successors its hashed bytes name. A malformed
|
|
137
|
+
field makes the whole runtime invalid — acceptance never widens on a
|
|
138
|
+
shape the store cannot prove.
|
|
139
|
+
"""
|
|
140
|
+
successors = manifest.get("successors", [])
|
|
141
|
+
if (not isinstance(successors, list)
|
|
142
|
+
or not all(
|
|
143
|
+
isinstance(item, str) and len(item) == 64
|
|
144
|
+
and all(char in "0123456789abcdef" for char in item)
|
|
145
|
+
for item in successors
|
|
146
|
+
)
|
|
147
|
+
or len(set(successors)) != len(successors)):
|
|
148
|
+
raise ValueError("invalid manifest successors")
|
|
149
|
+
return tuple(successors)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def accepted_runtime_dirs(repo: Path) -> set:
|
|
153
|
+
"""The pinned runtime's directory plus every validated successor (#468).
|
|
154
|
+
|
|
155
|
+
Owner-ruled option (i) on #468: acceptance after ship is anchored in the
|
|
156
|
+
pinned runtime's own hashed manifest. It names successor digests; each
|
|
157
|
+
hop is validated exactly like the pin and may name further successors.
|
|
158
|
+
A named entry that is missing or fails validation simply does not widen
|
|
159
|
+
the set — the pinned runtime keeps working — and a visited set ends
|
|
160
|
+
blessed cycles. Nothing outside the pin's hashed reach is ever accepted.
|
|
161
|
+
"""
|
|
162
|
+
pin = declaration(repo)
|
|
163
|
+
pin_root, pin_manifest = _validated_manifest(pin)
|
|
164
|
+
accepted = {pin_root.resolve()}
|
|
165
|
+
seen = {pin["digest"]}
|
|
166
|
+
pending = list(manifest_successors(pin_manifest))
|
|
167
|
+
while pending:
|
|
168
|
+
digest = pending.pop()
|
|
169
|
+
if digest in seen:
|
|
170
|
+
continue
|
|
171
|
+
seen.add(digest)
|
|
172
|
+
try:
|
|
173
|
+
# version None: the hop's trust anchor is the digest the pinned
|
|
174
|
+
# bytes named, never a version claim. The successors followed
|
|
175
|
+
# come from the same manifest read whose digest was proven —
|
|
176
|
+
# a mid-walk entry swap cannot smuggle a different list.
|
|
177
|
+
root, manifest = _validated_manifest(
|
|
178
|
+
{"schema": RUNTIME_SCHEMA, "version": None, "digest": digest}
|
|
179
|
+
)
|
|
180
|
+
named = manifest_successors(manifest)
|
|
181
|
+
except (OSError, ValueError, TypeError):
|
|
182
|
+
continue
|
|
183
|
+
pending.extend(named)
|
|
184
|
+
accepted.add(root.resolve())
|
|
185
|
+
return accepted
|
|
186
|
+
|
|
187
|
+
|
|
131
188
|
def validate_runtime(data: dict) -> Path:
|
|
189
|
+
return _validated_manifest(data)[0]
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def _validated_manifest(data: dict) -> tuple:
|
|
193
|
+
"""validate_runtime, returning the exact manifest whose digest was proven.
|
|
194
|
+
|
|
195
|
+
Callers that act on manifest content (the #468 successor walk) must use
|
|
196
|
+
this returned object, never a second read of manifest.json: a concurrent
|
|
197
|
+
swap between reads could otherwise validate one manifest while following
|
|
198
|
+
successors from another. `version` None anchors on the digest alone —
|
|
199
|
+
used by the walk for hops whose version no declaration supplies.
|
|
200
|
+
"""
|
|
132
201
|
root = runtime_home() / data["digest"]
|
|
133
202
|
hint = (f"Runtime {data['version']} ({data['digest']}) is unavailable or invalid at {root}. "
|
|
134
203
|
"Run gsd-path --runtime-restore --project <project-root> "
|
|
@@ -141,7 +210,8 @@ def validate_runtime(data: dict) -> Path:
|
|
|
141
210
|
raise ValueError("symlinked manifest")
|
|
142
211
|
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
|
143
212
|
if (not isinstance(manifest, dict) or manifest_digest(manifest) != data["digest"]
|
|
144
|
-
or
|
|
213
|
+
or (data["version"] is not None
|
|
214
|
+
and manifest.get("version") != data["version"])):
|
|
145
215
|
raise ValueError("manifest identity mismatch")
|
|
146
216
|
files = manifest.get("files")
|
|
147
217
|
if not isinstance(files, dict) or "pipeline_state.py" not in files:
|
|
@@ -164,9 +234,10 @@ def validate_runtime(data: dict) -> Path:
|
|
|
164
234
|
runtime_entries.add(path.name)
|
|
165
235
|
if runtime_entries != set(files) | {"manifest.json"}:
|
|
166
236
|
raise ValueError("unexpected runtime files")
|
|
237
|
+
manifest_successors(manifest)
|
|
167
238
|
except (OSError, ValueError, TypeError) as error:
|
|
168
239
|
raise ValueError(hint + f" ({error})") from error
|
|
169
|
-
return root
|
|
240
|
+
return root, manifest
|
|
170
241
|
|
|
171
242
|
|
|
172
243
|
def resolve_runtime(repo: Path) -> Path:
|
|
@@ -43,7 +43,8 @@ def _task_member(repo: Path, project_dir: str, task_id: str):
|
|
|
43
43
|
|
|
44
44
|
def run_workflow(repo: Path, action: str, project_dir: str, expected_head: str = None,
|
|
45
45
|
task_id: str = None, round_size: int = None, inspection: Path = None,
|
|
46
|
-
mapper_reviewed: bool = False, kind: str = None
|
|
46
|
+
mapper_reviewed: bool = False, kind: str = None,
|
|
47
|
+
rerun: bool = False) -> dict:
|
|
47
48
|
steps = []
|
|
48
49
|
outputs = {}
|
|
49
50
|
scripts = Path(__file__).resolve().parent
|
|
@@ -231,7 +232,8 @@ def run_workflow(repo: Path, action: str, project_dir: str, expected_head: str =
|
|
|
231
232
|
if pending["pending"]:
|
|
232
233
|
raise StepFailed("pending discussion requires its owner disposition")
|
|
233
234
|
step("build_state.py", "verify-landed", *common, "--head", expected_head)
|
|
234
|
-
|
|
235
|
+
rerun_flag = ("--rerun",) if rerun else ()
|
|
236
|
+
step("lean_verification.py", "--repo", str(repo), "--expected-head", expected_head, *rerun_flag)
|
|
235
237
|
elif action == "preauthorize":
|
|
236
238
|
# A block here is not a failure: the gate asks its owner as usual.
|
|
237
239
|
next_step = "ask the owner at this gate"
|
|
@@ -302,6 +304,9 @@ def main(argv=None):
|
|
|
302
304
|
parser.add_argument("--inspection", type=Path)
|
|
303
305
|
parser.add_argument("--mapper-reviewed", action="store_true")
|
|
304
306
|
parser.add_argument("--kind", choices=("intent", "plan"))
|
|
307
|
+
parser.add_argument("--rerun", action="store_true",
|
|
308
|
+
help="prepare-final: re-execute project Verify at the same head after a "
|
|
309
|
+
"host-caused failure, appending a new ledger entry")
|
|
305
310
|
arguments = parser.parse_args(argv)
|
|
306
311
|
if arguments.action in {"approve-plan", "build-evidence", "prepare-task", "prepare-final", "prepare-inspect", "finish-inspect"} and not arguments.expected_head:
|
|
307
312
|
parser.error(f"{arguments.action} requires --expected-head")
|
|
@@ -309,10 +314,12 @@ def main(argv=None):
|
|
|
309
314
|
parser.error("prepare-task requires --task-id and --round-size")
|
|
310
315
|
if arguments.action == "preauthorize" and not arguments.kind:
|
|
311
316
|
parser.error("preauthorize requires --kind")
|
|
317
|
+
if arguments.rerun and arguments.action != "prepare-final":
|
|
318
|
+
parser.error(f"--rerun applies only to prepare-final, not {arguments.action}")
|
|
312
319
|
result = run_workflow(arguments.repo.resolve(), arguments.action,
|
|
313
320
|
arguments.project_dir, arguments.expected_head,
|
|
314
321
|
arguments.task_id, arguments.round_size, arguments.inspection,
|
|
315
|
-
arguments.mapper_reviewed, arguments.kind)
|
|
322
|
+
arguments.mapper_reviewed, arguments.kind, arguments.rerun)
|
|
316
323
|
print(json.dumps(result, sort_keys=True))
|
|
317
324
|
return 0 if result["status"] == "complete" else 1
|
|
318
325
|
|
|
@@ -7,11 +7,13 @@ import sys
|
|
|
7
7
|
# Runtime helpers must not modify their immutable installation.
|
|
8
8
|
sys.dont_write_bytecode = True
|
|
9
9
|
|
|
10
|
+
import contextlib
|
|
10
11
|
import hashlib
|
|
11
12
|
import json
|
|
12
13
|
import os
|
|
13
14
|
import tempfile
|
|
14
15
|
from pathlib import Path
|
|
16
|
+
from typing import Optional
|
|
15
17
|
|
|
16
18
|
if __package__:
|
|
17
19
|
from . import _common
|
|
@@ -22,7 +24,39 @@ else:
|
|
|
22
24
|
from scripts import _common
|
|
23
25
|
|
|
24
26
|
|
|
27
|
+
# Run-scoped cache for read-only consumers (#469): recovery resolves a
|
|
28
|
+
# placement per task, and each resolution lists the whole worktree registry
|
|
29
|
+
# and re-reads the placement receipt. Within one read-only run none of that
|
|
30
|
+
# can change, so the listing and the derived workspace answer once. Flows
|
|
31
|
+
# that create, move, or retire worktrees never enter this scope.
|
|
32
|
+
_PLACEMENT_CACHE: Optional[dict] = None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@contextlib.contextmanager
|
|
36
|
+
def readonly_placement_cache():
|
|
37
|
+
global _PLACEMENT_CACHE
|
|
38
|
+
if _PLACEMENT_CACHE is not None: # already inside a read-only run
|
|
39
|
+
yield
|
|
40
|
+
return
|
|
41
|
+
_PLACEMENT_CACHE = {"porcelain": {}, "workspace": {}}
|
|
42
|
+
try:
|
|
43
|
+
yield
|
|
44
|
+
finally:
|
|
45
|
+
_PLACEMENT_CACHE = None
|
|
46
|
+
|
|
47
|
+
|
|
25
48
|
def _worktree_list_porcelain(primary: Path) -> tuple[str, bool]:
|
|
49
|
+
if _PLACEMENT_CACHE is not None:
|
|
50
|
+
key = str(primary)
|
|
51
|
+
if key in _PLACEMENT_CACHE["porcelain"]:
|
|
52
|
+
return _PLACEMENT_CACHE["porcelain"][key] # type: ignore[index]
|
|
53
|
+
output = _worktree_list_porcelain_uncached(primary)
|
|
54
|
+
_PLACEMENT_CACHE["porcelain"][key] = output
|
|
55
|
+
return output
|
|
56
|
+
return _worktree_list_porcelain_uncached(primary)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _worktree_list_porcelain_uncached(primary: Path) -> tuple[str, bool]:
|
|
26
60
|
try:
|
|
27
61
|
if __package__:
|
|
28
62
|
from . import pipeline_git as pg
|
|
@@ -58,6 +92,17 @@ def _identity(path: Path) -> str:
|
|
|
58
92
|
|
|
59
93
|
|
|
60
94
|
def _workspace(primary: Path, pin: bool) -> Path:
|
|
95
|
+
if _PLACEMENT_CACHE is not None and not pin:
|
|
96
|
+
key = str(primary)
|
|
97
|
+
if key in _PLACEMENT_CACHE["workspace"]:
|
|
98
|
+
return _PLACEMENT_CACHE["workspace"][key] # type: ignore[index]
|
|
99
|
+
workspace = _workspace_uncached(primary, False)
|
|
100
|
+
_PLACEMENT_CACHE["workspace"][key] = workspace
|
|
101
|
+
return workspace
|
|
102
|
+
return _workspace_uncached(primary, pin)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _workspace_uncached(primary: Path, pin: bool) -> Path:
|
|
61
106
|
common = Path(_git(primary, "rev-parse", "--git-common-dir"))
|
|
62
107
|
common = (primary / common).resolve()
|
|
63
108
|
identity = _identity(primary)
|
|
@@ -123,6 +123,8 @@ paragraph the user signed off on — do not edit without a new sign-off.>
|
|
|
123
123
|
|
|
124
124
|
## Corrections
|
|
125
125
|
|
|
126
|
-
<!-- Verbatim user corrections from playback and later phases. Append-only
|
|
127
|
-
|
|
128
|
-
|
|
126
|
+
<!-- Verbatim user corrections from playback and later phases. Append-only,
|
|
127
|
+
numbered from C1: the id gives a correction citable identity — wave
|
|
128
|
+
dispositions and scope exclusions authorize through it. These are the
|
|
129
|
+
highest-signal intent data in the file. -->
|
|
130
|
+
- C1 <YYYY-MM-DD>: "<what the user said>"
|
|
@@ -3,15 +3,22 @@ id: T001
|
|
|
3
3
|
title: <imperative, specific>
|
|
4
4
|
wave: 1
|
|
5
5
|
deps: [] # task ids whose output or landed effect this task needs
|
|
6
|
-
status: pending # orchestrator-owned: pending | in-progress | done | failed | blocked
|
|
6
|
+
status: pending # orchestrator-owned: pending | in-progress | done | failed | blocked | withdrawn
|
|
7
7
|
agent: null # orchestrator-owned: set at dispatch
|
|
8
8
|
base: null # orchestrator-owned: clean layer SHA for isolated Verify
|
|
9
9
|
worktree: null # orchestrator-owned: isolated task worktree while active
|
|
10
10
|
task_branch: null # orchestrator-owned: gsd-path-task/<id> while parallel; null when serial
|
|
11
|
+
# member_base: <sha> # orchestrator-owned, member-repo tasks only: the member repository's starting commit
|
|
12
|
+
# reads: # optional read-only cross-repository inputs, one per line (#471):
|
|
13
|
+
# - web: .github/workflows/build.yml # a member path at that member's validation base
|
|
14
|
+
# - coordinator: docs/decisions.tsv # from a member task: the coordinator's layer base
|
|
11
15
|
# Optional explicit dispatch overrides: model: <advertised slug|inherit>
|
|
12
16
|
# effort: <advertised effort|inherit>
|
|
13
17
|
# repo: <member> # multi-repo only: the MEMBERS.md member this task changes; omit for the coordinator
|
|
14
18
|
# supersedes: [<task id>] # only when INTENT requires it: earlier-wave tasks whose behavior this task replaces; retires their Verify
|
|
19
|
+
# Withdrawn — <YYYY-MM-DD>: "<owner ruling verbatim>" # only in the Log, only
|
|
20
|
+
# after an owner ruling: status: withdrawn keeps the id in inventory with no
|
|
21
|
+
# dispatch and no landing; the citation line is mandatory or the gate fails (#483)
|
|
15
22
|
files: # every file this task may touch — dispatch checks overlap
|
|
16
23
|
- <path>
|
|
17
24
|
---
|
|
@@ -31,15 +31,25 @@ Tasks reviewed: <count>
|
|
|
31
31
|
the task file. An evidence line starts with `- ✅ ` (pass) or `- ❌ `
|
|
32
32
|
(fail). A section needs one or more lines with the marker of its own
|
|
33
33
|
verdict, so a failed task needs a `- ❌ ` criterion line.
|
|
34
|
-
|
|
35
|
-
criterion before the em dash
|
|
36
|
-
spaces
|
|
37
|
-
|
|
34
|
+
On a `- ✅ ` line, name the criterion either by copying every word of
|
|
35
|
+
the acceptance/interface criterion before the em dash (join wrapped
|
|
36
|
+
lines and sub-bullets with spaces; the `- ` markers of sub-bullets are
|
|
37
|
+
optional) or by citing its stable id in the task's numbered list
|
|
38
|
+
(`T001/AC1`, `T001/IF2`) together with the evidence. A `- ❌ ` line
|
|
39
|
+
always copies the criterion verbatim: review_findings.py groups
|
|
40
|
+
findings by that text.
|
|
38
41
|
Write literal markup and code in backticks inside a sentence: text
|
|
39
42
|
such as <b> outside backticks reads as an unfilled placeholder. -->
|
|
40
43
|
- ✅ <criterion> — <evidence: verify output / file:line checked>
|
|
41
44
|
- ❌ <criterion> — found: <what exists instead, file:line>
|
|
42
45
|
fix: <concrete direction a coder can execute without re-investigating>
|
|
46
|
+
- <task id>/<ACn|IFn> — disposition: <authority and date> (<Cn>): <reason>
|
|
47
|
+
<!-- Only for a claim an INTENT.md correction defers or excludes; cite
|
|
48
|
+
its number (C1) — a disposition with no citation, or with a Cn
|
|
49
|
+
that INTENT.md `## Corrections` does not define, fails the gate. Every
|
|
50
|
+
acceptance criterion and interface bullet must otherwise appear
|
|
51
|
+
above, as evidence or a finding. One line may cite several ids
|
|
52
|
+
(T001/AC1, T001/AC2) when one observation covers them. -->
|
|
43
53
|
|
|
44
54
|
Warnings (non-blocking):
|
|
45
55
|
- <weakened test / unfailable verify / none>
|
|
@@ -52,18 +62,24 @@ Contract violations (blocking):
|
|
|
52
62
|
## Intent coverage
|
|
53
63
|
|
|
54
64
|
<!-- One heading per INTENT.md success criterion owned by a task in this
|
|
55
|
-
wave, ending `: pass` or `: fail
|
|
56
|
-
|
|
57
|
-
|
|
65
|
+
wave, ending `: pass` or `: fail`, in ascending SC id order (SC2
|
|
66
|
+
before SC4), with an em dash (—) after the id. `scripts/check_handoffs.py
|
|
67
|
+
wave --review <this file>` gates the SC ids, their order, and
|
|
68
|
+
verdicts. Omit this section when no task in the wave owns an SC.
|
|
58
69
|
Copy every word of the criterion from INTENT.md `## Success criteria`:
|
|
59
70
|
join indented continuation lines and sub-bullets with a single space
|
|
60
71
|
(the `- ` markers of sub-bullets are optional). The gate compares every
|
|
61
72
|
other word: no paraphrase, no dropped sub-bullet.
|
|
62
73
|
Each SC block needs an evidence line with the marker of its own
|
|
63
|
-
verdict: `- ✅ ` (pass) or `- ❌ ` (fail).
|
|
74
|
+
verdict: `- ✅ ` (pass) or `- ❌ ` (fail).
|
|
75
|
+
Under a `pass` SC only, record each subclaim an INTENT.md correction
|
|
76
|
+
excludes as `- Excluded by <Cn>: <subclaim>`; the Cn must exist in
|
|
77
|
+
INTENT.md `## Corrections`. An exclusion records retained scope, not
|
|
78
|
+
a failure. -->
|
|
64
79
|
|
|
65
80
|
### SC1 — <criterion from INTENT.md>: <pass | fail>
|
|
66
81
|
- ✅ <evidence: verify output / file:line checked>
|
|
82
|
+
- Excluded by <Cn>: <subclaim the correction removes>
|
|
67
83
|
- ❌ <criterion> — found: <what exists instead, file:line>
|
|
68
84
|
fix: <concrete direction a coder can execute without re-investigating>
|
|
69
85
|
<!-- Quick-lane final scope (Review scope: final): add after the evidence line:
|
|
@@ -132,7 +132,9 @@ brief unless the canonical `build: abandon milestone <slug>` checkpoint moves
|
|
|
132
132
|
that same brief into `.project/archive/`; build adds fix tasks and repairs
|
|
133
133
|
briefs, it never replans. It also refuses a `build:` checkpoint while the
|
|
134
134
|
committed STATE is `ship/active`: final-review outputs stay uncommitted until
|
|
135
|
-
the ship commit.
|
|
135
|
+
the ship commit. The one exception is the patch re-entry checkpoint: the
|
|
136
|
+
worktree STATE is `build/active` and its Log ends with `patch plan reopened`,
|
|
137
|
+
`patch plan approved`, `build started`.
|
|
136
138
|
|
|
137
139
|
## Execute from runtime receipts
|
|
138
140
|
|
|
@@ -207,7 +207,16 @@ dispatch contract and perform steps 1–5 by hand.
|
|
|
207
207
|
its exact base prove the remaining branch before deletion. Create the retry
|
|
208
208
|
from the new clean primary HEAD. A member task (`repo: <member>`) stays
|
|
209
209
|
`pending` in the coordinator; its rejected attempt lives in the member
|
|
210
|
-
sidecar.
|
|
210
|
+
sidecar. When the failure was environmental — the isolate could not build
|
|
211
|
+
a Verify for reasons outside the product, and the coder fixed the
|
|
212
|
+
environment inside the retained sidecar — do not re-isolate: rerun
|
|
213
|
+
`python3 <absolute isolation.py> activate-member-task --repo <absolute
|
|
214
|
+
primary> --member <member> --task-id <id> --reauthorize` after checking
|
|
215
|
+
that the live copy's Log stayed append-only and the member base is
|
|
216
|
+
unchanged; it re-arms the authorization the failed finish cleared, and the
|
|
217
|
+
next `finish` runs the full Verify set once and lands (#461). A
|
|
218
|
+
product-failure rejection instead follows the full retry: copy the live
|
|
219
|
+
copy's Log delta into the coordinator task file,
|
|
211
220
|
checkpoint it, then retire the sidecar with `python3 <absolute isolation.py>
|
|
212
221
|
retire-member-task --repo <absolute primary> --member <member> --task-id
|
|
213
222
|
<id> --task-file <task path> --force`. It refuses until the committed task
|
|
@@ -231,7 +240,11 @@ dispatch contract and perform steps 1–5 by hand.
|
|
|
231
240
|
`python3 <absolute check_handoffs.py> plan --repo <absolute repo root>`
|
|
232
241
|
before creating any worktree; a lint or coverage failure is a documented plan
|
|
233
242
|
defect — repair it against INTENT.md and SYNTHESIS.md, then re-establish
|
|
234
|
-
the base.
|
|
243
|
+
the base. The brief lint is read-only and idempotent; it reports progress
|
|
244
|
+
on stderr (one line every 20 tasks) and its stdout is exactly the result
|
|
245
|
+
JSON. A host timeout that cut it off with no failure output is not a
|
|
246
|
+
contract verdict — rerun it with a larger timeout and judge only the
|
|
247
|
+
completed run. Then isolate each ready task with
|
|
235
248
|
`python3 <absolute workflow_run.py> prepare-task --repo <absolute primary>
|
|
236
249
|
--expected-head <recorded base> --task-id <id> --round-size <N>` where N is the
|
|
237
250
|
number of tasks in this dispatch round. For a coordinator task, serial
|
|
@@ -619,7 +632,11 @@ dispatch contract and perform steps 1–5 by hand.
|
|
|
619
632
|
Log waiver — or, in program flow (ROADMAP.md exists), to abandon the
|
|
620
633
|
milestone under the Milestone abandon procedure — listing the
|
|
621
634
|
orchestrator's recommended option first marked `(recommended)` with a
|
|
622
|
-
one-line reason drawn from the review evidence.
|
|
635
|
+
one-line reason drawn from the review evidence. When the cap cycle is
|
|
636
|
+
later than 1 and a blocking group in its `groups` has `first_seen_cycle`
|
|
637
|
+
1, recommend sending it
|
|
638
|
+
to define: fix cycles since cycle 1 have not closed that criterion, so it
|
|
639
|
+
is likely unbounded. Never choose silently.
|
|
623
640
|
When the canonical verdict is `pass` and `wave-N.cycleC.panel.md` reports
|
|
624
641
|
`Actionable` greater than 0, do not advance silently: present **Outcome**
|
|
625
642
|
with the canonical pass plus the actionable count, **Review** linking the
|
|
@@ -35,7 +35,10 @@ reality:
|
|
|
35
35
|
item in INTENT.md `## Success criteria`.
|
|
36
36
|
- Confirm your Interface contract text appears verbatim in every sibling
|
|
37
37
|
task file it exchanges with. Sibling task files under `.project/tasks/`
|
|
38
|
-
are readable in your worktree — read-only; never edit them.
|
|
38
|
+
are readable in your worktree — read-only; never edit them. On a member
|
|
39
|
+
task the orchestrator pins them under
|
|
40
|
+
`.gsd-path-coordinator/.project/tasks/` beside your live task copy —
|
|
41
|
+
read them there.
|
|
39
42
|
- Confirm every path the Verify command references exists or is declared.
|
|
40
43
|
|
|
41
44
|
Route any mismatch before writing a line of product code: a clear defect — a
|
|
@@ -86,8 +89,8 @@ them.
|
|
|
86
89
|
|
|
87
90
|
- You are not alone in the repository. Other agents may use sibling worktrees;
|
|
88
91
|
do not inspect, revert, or overwrite their changes.
|
|
89
|
-
- Never edit `base`, `worktree`, `task_branch`, `status`,
|
|
90
|
-
The orchestrator owns them.
|
|
92
|
+
- Never edit `base`, `worktree`, `task_branch`, `status`, `agent`, or
|
|
93
|
+
`member_base` (member tasks). The orchestrator owns them.
|
|
91
94
|
- Never stage, commit, amend, rebase, reset, or otherwise mutate Git.
|
|
92
95
|
- Never add an unnamed dependency, weaken verification, or improve adjacent
|
|
93
96
|
code.
|
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
## Final integration mode
|
|
2
2
|
|
|
3
3
|
Use only final-review.md. Treat INTENT.md success criteria as the rubric.
|
|
4
|
+
To locate each task's authoritative accepted evidence instead of joining
|
|
5
|
+
task Logs, ledger rows, and gap artifacts by hand, run
|
|
6
|
+
`python3 <absolute build_state.py> evidence-manifest --repo
|
|
7
|
+
<absolute primary> --project-dir .project --head <reviewed HEAD>`:
|
|
8
|
+
it classifies every task as `ledger`, `log-only`, `rejected`,
|
|
9
|
+
`superseded`, or `missing` and names the receipt, and reports the member
|
|
10
|
+
project Verify as `gap-embedded` only from a passing `final-gap-1.md` at
|
|
11
|
+
the reviewed HEAD — a
|
|
12
|
+
`log-only` task is accepted through its retained isolated-Verify Log, and
|
|
13
|
+
an absent ledger row is explained, never treated as data loss.
|
|
4
14
|
Each `### SCn —` heading must have every word of the criterion in INTENT.md
|
|
5
15
|
`## Success criteria`: join indented continuation lines and sub-bullets
|
|
6
16
|
with a single space. You can keep or remove the `- ` markers of the
|