@opengsd/gsd-path 1.3.0 → 1.4.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/AGENTS.md +2 -207
- package/CHANGELOG.md +294 -0
- package/DOCS.md +19 -10
- package/FULL.md +9 -3
- package/GUIDE.md +1 -1
- package/HOOKS.md +9 -6
- package/QUICK.md +4 -1
- package/README.md +63 -11
- package/RUNTIME.md +1 -1
- package/UPDATE.md +61 -12
- package/WORKFLOW.md +22 -6
- package/package.json +9 -8
- package/scripts/_common.py +472 -19
- package/scripts/archive_milestone.py +146 -73
- package/scripts/bootstrap_repository.py +8 -5
- package/scripts/build_recovery.py +4 -1
- package/scripts/build_state.py +66 -23
- package/scripts/check_docs_audit.py +14 -3
- package/scripts/check_handoffs.py +116 -20
- package/scripts/check_task_briefs.py +60 -8
- package/scripts/check_update.py +2 -2
- package/scripts/core_hook_gate.py +10 -1
- package/scripts/core_hook_settings.py +11 -4
- package/scripts/detect_project.py +345 -15
- package/scripts/discussion_records.py +19 -13
- package/scripts/discussion_validate.py +66 -21
- package/scripts/dispatch_driver.py +111 -26
- package/scripts/git_guard.py +116 -29
- package/scripts/guard_hook.py +151 -29
- package/scripts/install.mjs +55 -39
- package/scripts/install.py +564 -41
- package/scripts/integration.py +434 -24
- package/scripts/isolation.py +1077 -28
- package/scripts/lean_verification.py +99 -19
- package/scripts/loop_run.py +20 -30
- package/scripts/members.py +704 -0
- package/scripts/migrate_core.py +2 -2
- package/scripts/path_config.py +2 -2
- package/scripts/pipeline_diagnose.py +82 -31
- package/scripts/pipeline_git.py +61 -22
- package/scripts/pipeline_state.py +176 -54
- package/scripts/pipeline_undo.py +117 -22
- package/scripts/promote_lookahead.py +1 -1
- package/scripts/review_panel.py +1 -1
- package/scripts/runtime_store.py +49 -11
- package/scripts/skill-resources.json +28 -3
- package/scripts/state_checkpoint.py +140 -31
- package/scripts/state_promote.py +2 -0
- package/scripts/status_runtime.py +36 -6
- package/scripts/sync_skill_resources.py +8 -7
- package/scripts/token_budget.py +12 -8
- package/scripts/workflow_run.py +103 -29
- package/scripts/worktree_paths.py +18 -8
- package/skills/gsd-path/BUILD.md +7 -1
- package/skills/gsd-path/DECIDE.md +11 -4
- package/skills/gsd-path/DEFINE.md +8 -1
- package/skills/gsd-path/DOCS-AUDIT.md +6 -4
- package/skills/gsd-path/INSPECT.md +5 -4
- package/skills/gsd-path/PLAN.md +7 -1
- package/skills/gsd-path/RESEARCH.md +2 -1
- package/skills/gsd-path/ROADMAP.md +2 -1
- package/skills/gsd-path/SHIP.md +27 -10
- package/skills/gsd-path/SKILL.md +45 -1
- package/skills/gsd-path/references/build-native.md +47 -14
- package/skills/gsd-path/references/codebase-mapper.md +2 -1
- package/skills/gsd-path/references/docs-auditor.md +2 -1
- package/skills/gsd-path/references/operating-rules.md +214 -0
- package/skills/gsd-path/references/planner.md +8 -0
- package/skills/gsd-path/references/reviewer-wave.md +3 -1
- package/skills/gsd-path/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path/scripts/__pycache__/check_handoffs.cpython-312.pyc +0 -0
- package/skills/gsd-path/scripts/_common.py +472 -19
- package/skills/gsd-path/scripts/archive_milestone.py +146 -73
- package/skills/gsd-path/scripts/bootstrap_repository.py +8 -5
- package/skills/gsd-path/scripts/build_recovery.py +4 -1
- package/skills/gsd-path/scripts/build_state.py +66 -23
- package/skills/gsd-path/scripts/check_docs_audit.py +14 -3
- package/skills/gsd-path/scripts/check_handoffs.py +116 -20
- package/skills/gsd-path/scripts/check_task_briefs.py +60 -8
- package/skills/gsd-path/scripts/check_update.py +2 -2
- package/skills/gsd-path/scripts/detect_project.py +345 -15
- package/skills/gsd-path/scripts/discussion_records.py +19 -13
- package/skills/gsd-path/scripts/discussion_validate.py +66 -21
- package/skills/gsd-path/scripts/dispatch_driver.py +111 -26
- package/skills/gsd-path/scripts/git_guard.py +116 -29
- package/skills/gsd-path/scripts/integration.py +434 -24
- package/skills/gsd-path/scripts/isolation.py +1077 -28
- package/skills/gsd-path/scripts/lean_verification.py +99 -19
- package/skills/gsd-path/scripts/loop_run.py +20 -30
- package/skills/gsd-path/scripts/members.py +704 -0
- package/skills/gsd-path/scripts/path_config.py +2 -2
- package/skills/gsd-path/scripts/pipeline_git.py +61 -22
- package/skills/gsd-path/scripts/pipeline_state.py +176 -54
- package/skills/gsd-path/scripts/promote_lookahead.py +1 -1
- package/skills/gsd-path/scripts/review_panel.py +1 -1
- package/skills/gsd-path/scripts/state_checkpoint.py +140 -31
- package/skills/gsd-path/scripts/state_promote.py +2 -0
- package/skills/gsd-path/scripts/status_runtime.py +36 -6
- package/skills/gsd-path/scripts/token_budget.py +12 -8
- package/skills/gsd-path/scripts/workflow_run.py +103 -29
- package/skills/gsd-path/scripts/worktree_paths.py +18 -8
- package/skills/gsd-path/templates/codebase.md +1 -1
- package/skills/gsd-path/templates/docs-audit.md +1 -1
- package/skills/gsd-path/templates/final-review.md +5 -2
- package/skills/gsd-path/templates/gap-review.md +2 -1
- package/skills/gsd-path/templates/task.md +1 -0
- package/skills/gsd-path/templates/wave-review.md +3 -1
- package/skills/gsd-path-build/SKILL.md +7 -1
- package/skills/gsd-path-build/references/build-native.md +47 -14
- package/skills/gsd-path-build/references/operating-rules.md +214 -0
- package/skills/gsd-path-build/references/reviewer-wave.md +3 -1
- package/skills/gsd-path-build/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path-build/scripts/__pycache__/check_handoffs.cpython-312.pyc +0 -0
- package/skills/gsd-path-build/scripts/_common.py +472 -19
- package/skills/gsd-path-build/scripts/archive_milestone.py +146 -73
- package/skills/gsd-path-build/scripts/build_recovery.py +4 -1
- package/skills/gsd-path-build/scripts/build_state.py +66 -23
- package/skills/gsd-path-build/scripts/check_handoffs.py +116 -20
- package/skills/gsd-path-build/scripts/check_task_briefs.py +60 -8
- package/skills/gsd-path-build/scripts/detect_project.py +345 -15
- package/skills/gsd-path-build/scripts/discussion_records.py +19 -13
- package/skills/gsd-path-build/scripts/discussion_validate.py +66 -21
- package/skills/gsd-path-build/scripts/dispatch_driver.py +111 -26
- package/skills/gsd-path-build/scripts/integration.py +434 -24
- package/skills/gsd-path-build/scripts/isolation.py +1077 -28
- package/skills/gsd-path-build/scripts/loop_run.py +20 -30
- package/skills/gsd-path-build/scripts/members.py +704 -0
- package/skills/gsd-path-build/scripts/path_config.py +2 -2
- package/skills/gsd-path-build/scripts/pipeline_git.py +61 -22
- package/skills/gsd-path-build/scripts/pipeline_state.py +176 -54
- package/skills/gsd-path-build/scripts/promote_lookahead.py +1 -1
- package/skills/gsd-path-build/scripts/review_panel.py +1 -1
- package/skills/gsd-path-build/scripts/state_checkpoint.py +140 -31
- package/skills/gsd-path-build/scripts/state_promote.py +2 -0
- package/skills/gsd-path-build/scripts/token_budget.py +12 -8
- package/skills/gsd-path-build/scripts/workflow_run.py +103 -29
- package/skills/gsd-path-build/scripts/worktree_paths.py +18 -8
- package/skills/gsd-path-build/templates/task.md +1 -0
- package/skills/gsd-path-build/templates/wave-review.md +3 -1
- package/skills/gsd-path-decide/SKILL.md +11 -4
- package/skills/gsd-path-decide/references/operating-rules.md +214 -0
- package/skills/gsd-path-decide/scripts/_common.py +472 -19
- package/skills/gsd-path-decide/scripts/archive_milestone.py +146 -73
- package/skills/gsd-path-decide/scripts/build_recovery.py +4 -1
- package/skills/gsd-path-decide/scripts/check_handoffs.py +116 -20
- package/skills/gsd-path-decide/scripts/check_task_briefs.py +60 -8
- package/skills/gsd-path-decide/scripts/detect_project.py +345 -15
- package/skills/gsd-path-decide/scripts/discussion_records.py +19 -13
- package/skills/gsd-path-decide/scripts/discussion_validate.py +66 -21
- package/skills/gsd-path-decide/scripts/integration.py +434 -24
- package/skills/gsd-path-decide/scripts/isolation.py +1077 -28
- package/skills/gsd-path-decide/scripts/loop_run.py +20 -30
- package/skills/gsd-path-decide/scripts/members.py +704 -0
- package/skills/gsd-path-decide/scripts/path_config.py +2 -2
- package/skills/gsd-path-decide/scripts/pipeline_git.py +61 -22
- package/skills/gsd-path-decide/scripts/pipeline_state.py +176 -54
- package/skills/gsd-path-decide/scripts/promote_lookahead.py +1 -1
- package/skills/gsd-path-decide/scripts/review_panel.py +1 -1
- package/skills/gsd-path-decide/scripts/state_checkpoint.py +140 -31
- package/skills/gsd-path-decide/scripts/state_promote.py +2 -0
- package/skills/gsd-path-decide/scripts/worktree_paths.py +18 -8
- package/skills/gsd-path-define/SKILL.md +8 -1
- package/skills/gsd-path-define/references/operating-rules.md +214 -0
- package/skills/gsd-path-define/scripts/_common.py +472 -19
- package/skills/gsd-path-define/scripts/archive_milestone.py +146 -73
- package/skills/gsd-path-define/scripts/build_recovery.py +4 -1
- package/skills/gsd-path-define/scripts/check_handoffs.py +116 -20
- package/skills/gsd-path-define/scripts/check_task_briefs.py +60 -8
- package/skills/gsd-path-define/scripts/detect_project.py +345 -15
- package/skills/gsd-path-define/scripts/discussion_records.py +19 -13
- package/skills/gsd-path-define/scripts/discussion_validate.py +66 -21
- package/skills/gsd-path-define/scripts/integration.py +434 -24
- package/skills/gsd-path-define/scripts/isolation.py +1077 -28
- package/skills/gsd-path-define/scripts/loop_run.py +20 -30
- package/skills/gsd-path-define/scripts/members.py +704 -0
- package/skills/gsd-path-define/scripts/path_config.py +2 -2
- package/skills/gsd-path-define/scripts/pipeline_git.py +61 -22
- package/skills/gsd-path-define/scripts/pipeline_state.py +176 -54
- package/skills/gsd-path-define/scripts/promote_lookahead.py +1 -1
- package/skills/gsd-path-define/scripts/review_panel.py +1 -1
- package/skills/gsd-path-define/scripts/state_checkpoint.py +140 -31
- package/skills/gsd-path-define/scripts/state_promote.py +2 -0
- package/skills/gsd-path-define/scripts/worktree_paths.py +18 -8
- package/skills/gsd-path-discuss/SKILL.md +2 -1
- package/skills/gsd-path-discuss/references/operating-rules.md +214 -0
- package/skills/gsd-path-discuss/scripts/_common.py +472 -19
- package/skills/gsd-path-discuss/scripts/discussion_records.py +19 -13
- package/skills/gsd-path-discuss/scripts/loop_run.py +20 -30
- package/skills/gsd-path-discuss/scripts/path_config.py +2 -2
- package/skills/gsd-path-discuss/scripts/review_panel.py +1 -1
- package/skills/gsd-path-discuss/scripts/worktree_paths.py +18 -8
- package/skills/gsd-path-docs-audit/SKILL.md +6 -4
- package/skills/gsd-path-docs-audit/references/docs-auditor.md +2 -1
- package/skills/gsd-path-docs-audit/references/operating-rules.md +214 -0
- package/skills/gsd-path-docs-audit/scripts/_common.py +472 -19
- package/skills/gsd-path-docs-audit/scripts/archive_milestone.py +146 -73
- package/skills/gsd-path-docs-audit/scripts/build_recovery.py +4 -1
- package/skills/gsd-path-docs-audit/scripts/check_docs_audit.py +14 -3
- package/skills/gsd-path-docs-audit/scripts/check_handoffs.py +116 -20
- package/skills/gsd-path-docs-audit/scripts/check_task_briefs.py +60 -8
- package/skills/gsd-path-docs-audit/scripts/detect_project.py +345 -15
- package/skills/gsd-path-docs-audit/scripts/discussion_records.py +19 -13
- package/skills/gsd-path-docs-audit/scripts/discussion_validate.py +66 -21
- package/skills/gsd-path-docs-audit/scripts/integration.py +434 -24
- package/skills/gsd-path-docs-audit/scripts/isolation.py +1077 -28
- package/skills/gsd-path-docs-audit/scripts/loop_run.py +20 -30
- package/skills/gsd-path-docs-audit/scripts/members.py +704 -0
- package/skills/gsd-path-docs-audit/scripts/path_config.py +2 -2
- package/skills/gsd-path-docs-audit/scripts/pipeline_git.py +61 -22
- package/skills/gsd-path-docs-audit/scripts/pipeline_state.py +176 -54
- package/skills/gsd-path-docs-audit/scripts/promote_lookahead.py +1 -1
- package/skills/gsd-path-docs-audit/scripts/review_panel.py +1 -1
- package/skills/gsd-path-docs-audit/scripts/state_checkpoint.py +140 -31
- package/skills/gsd-path-docs-audit/scripts/state_promote.py +2 -0
- package/skills/gsd-path-docs-audit/scripts/worktree_paths.py +18 -8
- package/skills/gsd-path-docs-audit/templates/docs-audit.md +1 -1
- package/skills/gsd-path-forensics/SKILL.md +2 -1
- package/skills/gsd-path-forensics/references/operating-rules.md +214 -0
- package/skills/gsd-path-forensics/scripts/_common.py +472 -19
- package/skills/gsd-path-forensics/scripts/discussion_records.py +19 -13
- package/skills/gsd-path-forensics/scripts/loop_run.py +20 -30
- package/skills/gsd-path-forensics/scripts/path_config.py +2 -2
- package/skills/gsd-path-forensics/scripts/pipeline_diagnose.py +82 -31
- package/skills/gsd-path-forensics/scripts/pipeline_undo.py +117 -22
- package/skills/gsd-path-forensics/scripts/review_panel.py +1 -1
- package/skills/gsd-path-forensics/scripts/worktree_paths.py +18 -8
- package/skills/gsd-path-inspect/SKILL.md +5 -4
- package/skills/gsd-path-inspect/references/codebase-mapper.md +2 -1
- package/skills/gsd-path-inspect/references/docs-auditor.md +2 -1
- package/skills/gsd-path-inspect/references/operating-rules.md +214 -0
- package/skills/gsd-path-inspect/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path-inspect/scripts/_common.py +472 -19
- package/skills/gsd-path-inspect/scripts/archive_milestone.py +146 -73
- package/skills/gsd-path-inspect/scripts/build_recovery.py +4 -1
- package/skills/gsd-path-inspect/scripts/check_docs_audit.py +14 -3
- package/skills/gsd-path-inspect/scripts/check_handoffs.py +116 -20
- package/skills/gsd-path-inspect/scripts/check_task_briefs.py +60 -8
- package/skills/gsd-path-inspect/scripts/detect_project.py +345 -15
- package/skills/gsd-path-inspect/scripts/discussion_records.py +19 -13
- package/skills/gsd-path-inspect/scripts/discussion_validate.py +66 -21
- package/skills/gsd-path-inspect/scripts/integration.py +434 -24
- package/skills/gsd-path-inspect/scripts/isolation.py +1077 -28
- package/skills/gsd-path-inspect/scripts/loop_run.py +20 -30
- package/skills/gsd-path-inspect/scripts/members.py +704 -0
- package/skills/gsd-path-inspect/scripts/path_config.py +2 -2
- package/skills/gsd-path-inspect/scripts/pipeline_git.py +61 -22
- package/skills/gsd-path-inspect/scripts/pipeline_state.py +176 -54
- package/skills/gsd-path-inspect/scripts/promote_lookahead.py +1 -1
- package/skills/gsd-path-inspect/scripts/review_panel.py +1 -1
- package/skills/gsd-path-inspect/scripts/state_checkpoint.py +140 -31
- package/skills/gsd-path-inspect/scripts/state_promote.py +2 -0
- package/skills/gsd-path-inspect/scripts/workflow_run.py +103 -29
- package/skills/gsd-path-inspect/scripts/worktree_paths.py +18 -8
- package/skills/gsd-path-inspect/templates/codebase.md +1 -1
- package/skills/gsd-path-inspect/templates/docs-audit.md +1 -1
- package/skills/gsd-path-loop/SKILL.md +2 -1
- package/skills/gsd-path-loop/references/operating-rules.md +214 -0
- package/skills/gsd-path-loop/scripts/_common.py +472 -19
- package/skills/gsd-path-loop/scripts/discussion_records.py +19 -13
- package/skills/gsd-path-loop/scripts/loop_run.py +20 -30
- package/skills/gsd-path-loop/scripts/path_config.py +2 -2
- package/skills/gsd-path-loop/scripts/review_panel.py +1 -1
- package/skills/gsd-path-loop/scripts/worktree_paths.py +18 -8
- package/skills/gsd-path-migrate/SKILL.md +2 -1
- package/skills/gsd-path-migrate/references/operating-rules.md +214 -0
- package/skills/gsd-path-migrate/scripts/_common.py +472 -19
- package/skills/gsd-path-migrate/scripts/core_hook_gate.py +10 -1
- package/skills/gsd-path-migrate/scripts/core_hook_settings.py +11 -4
- package/skills/gsd-path-migrate/scripts/discussion_records.py +19 -13
- package/skills/gsd-path-migrate/scripts/loop_run.py +20 -30
- package/skills/gsd-path-migrate/scripts/migrate_core.py +2 -2
- package/skills/gsd-path-migrate/scripts/path_config.py +2 -2
- package/skills/gsd-path-migrate/scripts/review_panel.py +1 -1
- package/skills/gsd-path-plan/SKILL.md +7 -1
- package/skills/gsd-path-plan/references/operating-rules.md +214 -0
- package/skills/gsd-path-plan/references/planner.md +8 -0
- package/skills/gsd-path-plan/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path-plan/scripts/_common.py +472 -19
- package/skills/gsd-path-plan/scripts/archive_milestone.py +146 -73
- package/skills/gsd-path-plan/scripts/build_recovery.py +4 -1
- package/skills/gsd-path-plan/scripts/check_handoffs.py +116 -20
- package/skills/gsd-path-plan/scripts/check_task_briefs.py +60 -8
- package/skills/gsd-path-plan/scripts/detect_project.py +345 -15
- package/skills/gsd-path-plan/scripts/discussion_records.py +19 -13
- package/skills/gsd-path-plan/scripts/discussion_validate.py +66 -21
- package/skills/gsd-path-plan/scripts/integration.py +434 -24
- package/skills/gsd-path-plan/scripts/isolation.py +1077 -28
- package/skills/gsd-path-plan/scripts/loop_run.py +20 -30
- package/skills/gsd-path-plan/scripts/members.py +704 -0
- package/skills/gsd-path-plan/scripts/path_config.py +2 -2
- package/skills/gsd-path-plan/scripts/pipeline_git.py +61 -22
- package/skills/gsd-path-plan/scripts/pipeline_state.py +176 -54
- package/skills/gsd-path-plan/scripts/promote_lookahead.py +1 -1
- package/skills/gsd-path-plan/scripts/review_panel.py +1 -1
- package/skills/gsd-path-plan/scripts/state_checkpoint.py +140 -31
- package/skills/gsd-path-plan/scripts/state_promote.py +2 -0
- package/skills/gsd-path-plan/scripts/workflow_run.py +103 -29
- package/skills/gsd-path-plan/scripts/worktree_paths.py +18 -8
- package/skills/gsd-path-plan/templates/task.md +1 -0
- package/skills/gsd-path-research/SKILL.md +2 -1
- package/skills/gsd-path-research/references/operating-rules.md +214 -0
- package/skills/gsd-path-research/scripts/_common.py +472 -19
- package/skills/gsd-path-research/scripts/archive_milestone.py +146 -73
- package/skills/gsd-path-research/scripts/build_recovery.py +4 -1
- package/skills/gsd-path-research/scripts/check_handoffs.py +116 -20
- package/skills/gsd-path-research/scripts/check_task_briefs.py +60 -8
- package/skills/gsd-path-research/scripts/detect_project.py +345 -15
- package/skills/gsd-path-research/scripts/discussion_records.py +19 -13
- package/skills/gsd-path-research/scripts/discussion_validate.py +66 -21
- package/skills/gsd-path-research/scripts/integration.py +434 -24
- package/skills/gsd-path-research/scripts/isolation.py +1077 -28
- package/skills/gsd-path-research/scripts/loop_run.py +20 -30
- package/skills/gsd-path-research/scripts/members.py +704 -0
- package/skills/gsd-path-research/scripts/path_config.py +2 -2
- package/skills/gsd-path-research/scripts/pipeline_git.py +61 -22
- package/skills/gsd-path-research/scripts/pipeline_state.py +176 -54
- package/skills/gsd-path-research/scripts/promote_lookahead.py +1 -1
- package/skills/gsd-path-research/scripts/review_panel.py +1 -1
- package/skills/gsd-path-research/scripts/state_checkpoint.py +140 -31
- package/skills/gsd-path-research/scripts/state_promote.py +2 -0
- package/skills/gsd-path-research/scripts/worktree_paths.py +18 -8
- package/skills/gsd-path-roadmap/SKILL.md +2 -1
- package/skills/gsd-path-roadmap/references/operating-rules.md +214 -0
- package/skills/gsd-path-roadmap/scripts/_common.py +472 -19
- package/skills/gsd-path-roadmap/scripts/archive_milestone.py +146 -73
- package/skills/gsd-path-roadmap/scripts/build_recovery.py +4 -1
- package/skills/gsd-path-roadmap/scripts/check_handoffs.py +116 -20
- package/skills/gsd-path-roadmap/scripts/check_task_briefs.py +60 -8
- package/skills/gsd-path-roadmap/scripts/detect_project.py +345 -15
- package/skills/gsd-path-roadmap/scripts/discussion_records.py +19 -13
- package/skills/gsd-path-roadmap/scripts/discussion_validate.py +66 -21
- package/skills/gsd-path-roadmap/scripts/integration.py +434 -24
- package/skills/gsd-path-roadmap/scripts/isolation.py +1077 -28
- package/skills/gsd-path-roadmap/scripts/loop_run.py +20 -30
- package/skills/gsd-path-roadmap/scripts/members.py +704 -0
- package/skills/gsd-path-roadmap/scripts/path_config.py +2 -2
- package/skills/gsd-path-roadmap/scripts/pipeline_git.py +61 -22
- package/skills/gsd-path-roadmap/scripts/pipeline_state.py +176 -54
- package/skills/gsd-path-roadmap/scripts/promote_lookahead.py +1 -1
- package/skills/gsd-path-roadmap/scripts/review_panel.py +1 -1
- package/skills/gsd-path-roadmap/scripts/state_checkpoint.py +140 -31
- package/skills/gsd-path-roadmap/scripts/state_promote.py +2 -0
- package/skills/gsd-path-roadmap/scripts/worktree_paths.py +18 -8
- package/skills/gsd-path-ship/SKILL.md +27 -10
- package/skills/gsd-path-ship/references/operating-rules.md +214 -0
- package/skills/gsd-path-ship/references/reviewer-wave.md +3 -1
- package/skills/gsd-path-ship/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path-ship/scripts/_common.py +472 -19
- package/skills/gsd-path-ship/scripts/archive_milestone.py +146 -73
- package/skills/gsd-path-ship/scripts/build_recovery.py +4 -1
- package/skills/gsd-path-ship/scripts/build_state.py +66 -23
- package/skills/gsd-path-ship/scripts/check_handoffs.py +116 -20
- package/skills/gsd-path-ship/scripts/check_task_briefs.py +60 -8
- package/skills/gsd-path-ship/scripts/detect_project.py +345 -15
- package/skills/gsd-path-ship/scripts/discussion_records.py +19 -13
- package/skills/gsd-path-ship/scripts/discussion_validate.py +66 -21
- package/skills/gsd-path-ship/scripts/integration.py +434 -24
- package/skills/gsd-path-ship/scripts/isolation.py +1077 -28
- package/skills/gsd-path-ship/scripts/lean_verification.py +99 -19
- package/skills/gsd-path-ship/scripts/loop_run.py +20 -30
- package/skills/gsd-path-ship/scripts/members.py +704 -0
- package/skills/gsd-path-ship/scripts/path_config.py +2 -2
- package/skills/gsd-path-ship/scripts/pipeline_git.py +61 -22
- package/skills/gsd-path-ship/scripts/pipeline_state.py +176 -54
- package/skills/gsd-path-ship/scripts/promote_lookahead.py +1 -1
- package/skills/gsd-path-ship/scripts/review_panel.py +1 -1
- package/skills/gsd-path-ship/scripts/state_checkpoint.py +140 -31
- package/skills/gsd-path-ship/scripts/state_promote.py +2 -0
- package/skills/gsd-path-ship/scripts/workflow_run.py +103 -29
- package/skills/gsd-path-ship/scripts/worktree_paths.py +18 -8
- package/skills/gsd-path-ship/templates/final-review.md +5 -2
- package/skills/gsd-path-ship/templates/gap-review.md +2 -1
- package/skills/gsd-path-ship/templates/wave-review.md +3 -1
- package/skills/gsd-path-undo/SKILL.md +2 -1
- package/skills/gsd-path-undo/references/operating-rules.md +214 -0
- package/skills/gsd-path-undo/scripts/_common.py +472 -19
- package/skills/gsd-path-undo/scripts/discussion_records.py +19 -13
- package/skills/gsd-path-undo/scripts/loop_run.py +20 -30
- package/skills/gsd-path-undo/scripts/path_config.py +2 -2
- package/skills/gsd-path-undo/scripts/pipeline_undo.py +117 -22
- package/skills/gsd-path-undo/scripts/review_panel.py +1 -1
- package/skills/gsd-path-undo/scripts/worktree_paths.py +18 -8
- package/skills/path/BUILD.md +7 -1
- package/skills/path/DECIDE.md +11 -4
- package/skills/path/DEFINE.md +8 -1
- package/skills/path/DOCS-AUDIT.md +6 -4
- package/skills/path/INSPECT.md +5 -4
- package/skills/path/PLAN.md +7 -1
- package/skills/path/RESEARCH.md +2 -1
- package/skills/path/ROADMAP.md +2 -1
- package/skills/path/SHIP.md +27 -10
- package/skills/path/SKILL.md +45 -1
- package/skills/path/references/build-native.md +47 -14
- package/skills/path/references/codebase-mapper.md +2 -1
- package/skills/path/references/docs-auditor.md +2 -1
- package/skills/path/references/operating-rules.md +214 -0
- package/skills/path/references/planner.md +8 -0
- package/skills/path/references/reviewer-wave.md +3 -1
- package/skills/path/scripts/_common.py +472 -19
- package/skills/path/scripts/archive_milestone.py +146 -73
- package/skills/path/scripts/bootstrap_repository.py +8 -5
- package/skills/path/scripts/build_recovery.py +4 -1
- package/skills/path/scripts/build_state.py +66 -23
- package/skills/path/scripts/check_docs_audit.py +14 -3
- package/skills/path/scripts/check_handoffs.py +116 -20
- package/skills/path/scripts/check_task_briefs.py +60 -8
- package/skills/path/scripts/check_update.py +2 -2
- package/skills/path/scripts/detect_project.py +345 -15
- package/skills/path/scripts/discussion_records.py +19 -13
- package/skills/path/scripts/discussion_validate.py +66 -21
- package/skills/path/scripts/dispatch_driver.py +111 -26
- package/skills/path/scripts/git_guard.py +116 -29
- package/skills/path/scripts/integration.py +434 -24
- package/skills/path/scripts/isolation.py +1077 -28
- package/skills/path/scripts/lean_verification.py +99 -19
- package/skills/path/scripts/loop_run.py +20 -30
- package/skills/path/scripts/members.py +704 -0
- package/skills/path/scripts/path_config.py +2 -2
- package/skills/path/scripts/pipeline_git.py +61 -22
- package/skills/path/scripts/pipeline_state.py +176 -54
- package/skills/path/scripts/promote_lookahead.py +1 -1
- package/skills/path/scripts/review_panel.py +1 -1
- package/skills/path/scripts/state_checkpoint.py +140 -31
- package/skills/path/scripts/state_promote.py +2 -0
- package/skills/path/scripts/status_runtime.py +36 -6
- package/skills/path/scripts/token_budget.py +12 -8
- package/skills/path/scripts/workflow_run.py +103 -29
- package/skills/path/scripts/worktree_paths.py +18 -8
- package/skills/path/templates/codebase.md +1 -1
- package/skills/path/templates/docs-audit.md +1 -1
- package/skills/path/templates/final-review.md +5 -2
- package/skills/path/templates/gap-review.md +2 -1
- package/skills/path/templates/task.md +1 -0
- package/skills/path/templates/wave-review.md +3 -1
package/scripts/guard_hook.py
CHANGED
|
@@ -25,6 +25,7 @@ import json
|
|
|
25
25
|
import os
|
|
26
26
|
import re
|
|
27
27
|
import shlex
|
|
28
|
+
import stat
|
|
28
29
|
import subprocess
|
|
29
30
|
import sys
|
|
30
31
|
from pathlib import Path, PurePosixPath
|
|
@@ -474,6 +475,20 @@ def collect(
|
|
|
474
475
|
)
|
|
475
476
|
|
|
476
477
|
|
|
478
|
+
def is_link_like(path):
|
|
479
|
+
"""A symlink, or on Windows any name-surrogate reparse point such as a junction.
|
|
480
|
+
|
|
481
|
+
Path.is_symlink() is false for a junction, which still redirects the path.
|
|
482
|
+
"""
|
|
483
|
+
try:
|
|
484
|
+
status = os.lstat(path)
|
|
485
|
+
except OSError:
|
|
486
|
+
return False
|
|
487
|
+
return stat.S_ISLNK(status.st_mode) or bool(
|
|
488
|
+
getattr(status, "st_reparse_tag", 0) & 0x20000000 # IO_REPARSE_TAG name surrogate
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
|
|
477
492
|
def normalize_posix(path):
|
|
478
493
|
text = path.replace("\\", "/").strip()
|
|
479
494
|
if not text:
|
|
@@ -536,7 +551,7 @@ def repository_root():
|
|
|
536
551
|
result = subprocess.run(
|
|
537
552
|
["git", "rev-parse", "--show-toplevel"],
|
|
538
553
|
cwd=candidate,
|
|
539
|
-
|
|
554
|
+
encoding="utf-8", errors="replace",
|
|
540
555
|
capture_output=True,
|
|
541
556
|
check=False,
|
|
542
557
|
)
|
|
@@ -544,7 +559,7 @@ def repository_root():
|
|
|
544
559
|
state = candidate / ".project" / "STATE.md"
|
|
545
560
|
if (
|
|
546
561
|
candidate.is_dir()
|
|
547
|
-
and not candidate
|
|
562
|
+
and not is_link_like(candidate)
|
|
548
563
|
and os.path.lexists(state)
|
|
549
564
|
):
|
|
550
565
|
return candidate
|
|
@@ -560,7 +575,7 @@ def project_status(repo):
|
|
|
560
575
|
result = subprocess.run(
|
|
561
576
|
[sys.executable, "-B", str(launcher), "--repo", str(repo)],
|
|
562
577
|
cwd=repo,
|
|
563
|
-
|
|
578
|
+
encoding="utf-8", errors="replace",
|
|
564
579
|
capture_output=True,
|
|
565
580
|
check=False,
|
|
566
581
|
)
|
|
@@ -697,13 +712,29 @@ def valid_status_branch(value):
|
|
|
697
712
|
|
|
698
713
|
def target_paths(path, working_directories, repo):
|
|
699
714
|
repo = repo.resolve()
|
|
700
|
-
candidate = Path(path)
|
|
715
|
+
candidate = Path(native_path_text(path))
|
|
701
716
|
if not candidate.is_absolute():
|
|
702
717
|
base = Path(working_directories[0]) if working_directories else repo
|
|
703
718
|
if not base.is_absolute():
|
|
704
719
|
base = repo / base
|
|
705
720
|
candidate = base / candidate
|
|
706
|
-
return Path(os.path.abspath(candidate)), candidate
|
|
721
|
+
return Path(os.path.abspath(candidate)), resolve_lenient(candidate)
|
|
722
|
+
|
|
723
|
+
|
|
724
|
+
def resolve_lenient(path):
|
|
725
|
+
"""Path.resolve(strict=False) that still follows links on Windows.
|
|
726
|
+
|
|
727
|
+
Python 3.9 raises OSError on Windows for names it cannot open (rep*) and
|
|
728
|
+
for some links to missing files; the archive checks must not skip those
|
|
729
|
+
(a symlinked archive path would be allowed), so fall back to
|
|
730
|
+
os.path.realpath, which resolves what it can and keeps the remainder.
|
|
731
|
+
"""
|
|
732
|
+
try:
|
|
733
|
+
return path.resolve(strict=False)
|
|
734
|
+
except OSError:
|
|
735
|
+
if os.name != "nt":
|
|
736
|
+
raise
|
|
737
|
+
return Path(os.path.realpath(path))
|
|
707
738
|
|
|
708
739
|
|
|
709
740
|
@lru_cache(maxsize=None)
|
|
@@ -712,7 +743,7 @@ def repository_control_roots(repo):
|
|
|
712
743
|
result = subprocess.run(
|
|
713
744
|
["git", "rev-parse", "--git-common-dir"],
|
|
714
745
|
cwd=repo,
|
|
715
|
-
|
|
746
|
+
encoding="utf-8", errors="replace",
|
|
716
747
|
capture_output=True,
|
|
717
748
|
env={**os.environ, "GIT_OPTIONAL_LOCKS": "0"},
|
|
718
749
|
check=False,
|
|
@@ -789,6 +820,51 @@ def path_kind(candidate, repo, control_roots=()):
|
|
|
789
820
|
return "product"
|
|
790
821
|
|
|
791
822
|
|
|
823
|
+
def member_target_kind(candidate, repo):
|
|
824
|
+
"""'product' when the path lies in a verified member of this coordinator.
|
|
825
|
+
|
|
826
|
+
A member follows its coordinator's phase. A stale member of this
|
|
827
|
+
coordinator raises MembersError, so the caller denies the write.
|
|
828
|
+
"""
|
|
829
|
+
directory = candidate if candidate.is_dir() else candidate.parent
|
|
830
|
+
while not directory.is_dir() and directory != directory.parent:
|
|
831
|
+
directory = directory.parent
|
|
832
|
+
found = subprocess.run(
|
|
833
|
+
["git", "-C", str(directory), "rev-parse", "--path-format=absolute",
|
|
834
|
+
"--show-toplevel", "--git-common-dir"],
|
|
835
|
+
encoding="utf-8", errors="replace", capture_output=True, check=False,
|
|
836
|
+
env={**os.environ, "GIT_OPTIONAL_LOCKS": "0"},
|
|
837
|
+
)
|
|
838
|
+
lines = found.stdout.splitlines()
|
|
839
|
+
if found.returncode != 0:
|
|
840
|
+
if found.stderr.strip().startswith("fatal: not a git repository"):
|
|
841
|
+
return None
|
|
842
|
+
raise RuntimeError(found.stderr.strip() or "Git inspection failed")
|
|
843
|
+
if len(lines) != 2:
|
|
844
|
+
raise RuntimeError("Git inspection returned an unexpected result")
|
|
845
|
+
toplevel, common = Path(lines[0]), Path(lines[1])
|
|
846
|
+
if not os.path.lexists(common / "gsd-path" / "member.json"):
|
|
847
|
+
return None
|
|
848
|
+
import members # the guard's runtime directory; only paths in a member need it
|
|
849
|
+
coordinator = members.marker_coordinator(toplevel)
|
|
850
|
+
if coordinator is None:
|
|
851
|
+
return None
|
|
852
|
+
if coordinator.resolve() != repo.resolve():
|
|
853
|
+
for member in members.read_members(repo):
|
|
854
|
+
try:
|
|
855
|
+
listed_common = members._common_dir(Path(member["checkout"]))
|
|
856
|
+
except (members.MembersError, OSError):
|
|
857
|
+
continue
|
|
858
|
+
if listed_common == common.resolve():
|
|
859
|
+
raise members.MembersError(
|
|
860
|
+
"member marker names an old coordinator; "
|
|
861
|
+
"run members.py repair --repo <coordinator>"
|
|
862
|
+
)
|
|
863
|
+
return None
|
|
864
|
+
members.member_role(toplevel)
|
|
865
|
+
return "product"
|
|
866
|
+
|
|
867
|
+
|
|
792
868
|
def target_kind(path, working_directories, repo, control_roots=None):
|
|
793
869
|
lexical, resolved = target_paths(path, working_directories, repo)
|
|
794
870
|
repo = repo.resolve()
|
|
@@ -798,10 +874,21 @@ def target_kind(path, working_directories, repo, control_roots=None):
|
|
|
798
874
|
_canonical_control_alias(lexical, repo, control_roots),
|
|
799
875
|
_canonical_control_alias(resolved, repo, control_roots),
|
|
800
876
|
)
|
|
801
|
-
|
|
877
|
+
resolved_kind = path_kind(resolved, repo, control_roots)
|
|
878
|
+
kinds = {resolved_kind} | {
|
|
802
879
|
path_kind(candidate, repo, control_roots)
|
|
803
|
-
for candidate in (lexical,
|
|
880
|
+
for candidate in (lexical, *aliases)
|
|
804
881
|
}
|
|
882
|
+
if resolved_kind == "external" and "protected" not in kinds:
|
|
883
|
+
try:
|
|
884
|
+
member_kind = member_target_kind(resolved, repo)
|
|
885
|
+
except Exception as error:
|
|
886
|
+
deny(
|
|
887
|
+
f"member write refused: {error}; "
|
|
888
|
+
"run members.py repair --repo <coordinator>"
|
|
889
|
+
)
|
|
890
|
+
if member_kind:
|
|
891
|
+
kinds.add(member_kind)
|
|
805
892
|
for kind in ("protected", "product", "artifact", "external"):
|
|
806
893
|
if kind in kinds:
|
|
807
894
|
return kind
|
|
@@ -824,12 +911,12 @@ def pipeline_target_kinds(targets):
|
|
|
824
911
|
def closed_target_reason(targets):
|
|
825
912
|
for path, working_directories in targets:
|
|
826
913
|
for target in dict.fromkeys(target_paths(path, working_directories, repository_root())):
|
|
827
|
-
directory = target if target.is_dir() and not target
|
|
914
|
+
directory = target if target.is_dir() and not is_link_like(target) else target.parent
|
|
828
915
|
while not directory.exists() and directory != directory.parent:
|
|
829
916
|
directory = directory.parent
|
|
830
917
|
root = subprocess.run(
|
|
831
918
|
["git", "-C", str(directory), "rev-parse", "--show-toplevel"],
|
|
832
|
-
capture_output=True,
|
|
919
|
+
capture_output=True, encoding="utf-8", errors="replace",
|
|
833
920
|
)
|
|
834
921
|
if root.returncode == 0:
|
|
835
922
|
reason = closed_milestone_reason(Path(root.stdout.strip()))
|
|
@@ -919,10 +1006,35 @@ def expansion_can_match_archive(path):
|
|
|
919
1006
|
)
|
|
920
1007
|
|
|
921
1008
|
|
|
1009
|
+
# Git Bash (Claude Code's shell on Windows) names drive C: as /c or /cygdrive/c.
|
|
1010
|
+
MSYS_DRIVE_PATH = re.compile(r"^/(?:cygdrive/)?([A-Za-z])(?=/|$)")
|
|
1011
|
+
|
|
1012
|
+
|
|
1013
|
+
def native_path_text(value):
|
|
1014
|
+
"""On Windows, the drive path that a Git Bash path such as /c/repo names.
|
|
1015
|
+
|
|
1016
|
+
Python reads /c/repo as a folder named c at the root of the current drive,
|
|
1017
|
+
which would hide an archive from the containment checks.
|
|
1018
|
+
"""
|
|
1019
|
+
if os.name != "nt":
|
|
1020
|
+
return value
|
|
1021
|
+
text = value.replace("\\", "/")
|
|
1022
|
+
match = MSYS_DRIVE_PATH.match(text)
|
|
1023
|
+
if match is None:
|
|
1024
|
+
return value
|
|
1025
|
+
return f"{match.group(1).upper()}:{text[match.end():] or '/'}"
|
|
1026
|
+
|
|
1027
|
+
|
|
922
1028
|
def path_values(value):
|
|
923
|
-
|
|
1029
|
+
values = [value]
|
|
924
1030
|
if value.startswith("-") and "=" in value:
|
|
925
|
-
|
|
1031
|
+
values.append(value.split("=", 1)[1])
|
|
1032
|
+
for candidate in values:
|
|
1033
|
+
yield candidate
|
|
1034
|
+
# Deny when either reading of an ambiguous Windows path reaches an archive.
|
|
1035
|
+
native = native_path_text(candidate)
|
|
1036
|
+
if native != candidate:
|
|
1037
|
+
yield native
|
|
926
1038
|
|
|
927
1039
|
|
|
928
1040
|
def is_absolute_path(path):
|
|
@@ -932,7 +1044,7 @@ def is_absolute_path(path):
|
|
|
932
1044
|
|
|
933
1045
|
def contains_existing_archive(path, working_directories):
|
|
934
1046
|
try:
|
|
935
|
-
target = Path(path)
|
|
1047
|
+
target = resolve_lenient(Path(path))
|
|
936
1048
|
except (OSError, RuntimeError):
|
|
937
1049
|
return False
|
|
938
1050
|
search_paths = [target, Path.cwd(), *(Path(value) for value in working_directories)]
|
|
@@ -940,7 +1052,7 @@ def contains_existing_archive(path, working_directories):
|
|
|
940
1052
|
if os.name != "nt" and re.match(r"^[A-Za-z]:[/\\]", str(search_path)):
|
|
941
1053
|
continue
|
|
942
1054
|
try:
|
|
943
|
-
resolved = search_path
|
|
1055
|
+
resolved = resolve_lenient(search_path)
|
|
944
1056
|
except (OSError, RuntimeError):
|
|
945
1057
|
continue
|
|
946
1058
|
for parent in (resolved, *resolved.parents):
|
|
@@ -973,7 +1085,7 @@ def path_in_archive(path, working_directories=(), ancestors=True):
|
|
|
973
1085
|
if os.name != "nt" and re.match(r"^[A-Za-z]:[/\\]", combined):
|
|
974
1086
|
continue
|
|
975
1087
|
try:
|
|
976
|
-
resolved = Path(combined)
|
|
1088
|
+
resolved = resolve_lenient(Path(combined))
|
|
977
1089
|
except (OSError, RuntimeError):
|
|
978
1090
|
continue
|
|
979
1091
|
if in_archive(resolved.as_posix()) or (
|
|
@@ -997,7 +1109,7 @@ def working_directory_in_archive(path):
|
|
|
997
1109
|
if os.name != "nt" and re.match(r"^[A-Za-z]:[/\\]", value):
|
|
998
1110
|
continue
|
|
999
1111
|
try:
|
|
1000
|
-
resolved = Path(value)
|
|
1112
|
+
resolved = resolve_lenient(Path(value))
|
|
1001
1113
|
except (OSError, RuntimeError):
|
|
1002
1114
|
continue
|
|
1003
1115
|
if in_archive(resolved.as_posix()):
|
|
@@ -1159,7 +1271,7 @@ def copy_destinations(arguments, directories, assignments=None):
|
|
|
1159
1271
|
lexical, source_path = target_paths(source, directories, repository_root())
|
|
1160
1272
|
if source_path.is_dir():
|
|
1161
1273
|
# ponytail: reject ambiguous directory links instead of emulating cp flags.
|
|
1162
|
-
if lexical
|
|
1274
|
+
if is_link_like(lexical):
|
|
1163
1275
|
raise ValueError("cp directory source is a symlink; pass a literal directory")
|
|
1164
1276
|
|
|
1165
1277
|
def unreadable(error):
|
|
@@ -1168,7 +1280,7 @@ def copy_destinations(arguments, directories, assignments=None):
|
|
|
1168
1280
|
for root, folders, files in os.walk(source_path, onerror=unreadable):
|
|
1169
1281
|
for name in folders + files:
|
|
1170
1282
|
entry = Path(root) / name
|
|
1171
|
-
if entry
|
|
1283
|
+
if is_link_like(entry) and entry.is_dir():
|
|
1172
1284
|
raise ValueError("cp source contains a directory symlink; copy it separately")
|
|
1173
1285
|
for output in outputs:
|
|
1174
1286
|
yield str(output / entry.relative_to(source_path))
|
|
@@ -1648,7 +1760,7 @@ def has_short_option(arguments, option):
|
|
|
1648
1760
|
def git_alias(command, git_options):
|
|
1649
1761
|
result = subprocess.run(
|
|
1650
1762
|
["git", *git_options, "config", "--get", f"alias.{command}"],
|
|
1651
|
-
|
|
1763
|
+
encoding="utf-8", errors="replace",
|
|
1652
1764
|
capture_output=True,
|
|
1653
1765
|
check=False,
|
|
1654
1766
|
)
|
|
@@ -1791,13 +1903,13 @@ def closed_milestone_reason(repo=None):
|
|
|
1791
1903
|
repo = repo or repository_root()
|
|
1792
1904
|
branch = subprocess.run(
|
|
1793
1905
|
["git", "-C", str(repo), "branch", "--show-current"],
|
|
1794
|
-
capture_output=True,
|
|
1906
|
+
capture_output=True, encoding="utf-8", errors="replace",
|
|
1795
1907
|
)
|
|
1796
1908
|
if not re.fullmatch(r"gsd-path/M\d{3,}", branch.stdout.strip()):
|
|
1797
1909
|
return None
|
|
1798
1910
|
result = subprocess.run(
|
|
1799
1911
|
[sys.executable, "-B", str(Path(__file__).with_name("git_guard.py")),
|
|
1800
|
-
"closed-milestone"], cwd=repo, capture_output=True,
|
|
1912
|
+
"closed-milestone"], cwd=repo, capture_output=True, encoding="utf-8", errors="replace",
|
|
1801
1913
|
)
|
|
1802
1914
|
if result.returncode:
|
|
1803
1915
|
return result.stderr.strip() or "closed milestone inspection failed"
|
|
@@ -1807,7 +1919,7 @@ def closed_milestone_reason(repo=None):
|
|
|
1807
1919
|
def bundled_helper_invocation(command, tokens, working_directories, helpers=PIPELINE_HELPERS):
|
|
1808
1920
|
"""Allow one plain ``python[3] [-B] <script>`` from the guard-owned runtime.
|
|
1809
1921
|
|
|
1810
|
-
The interpreter
|
|
1922
|
+
The interpreter must be exactly python, python3, or py -3, and the script
|
|
1811
1923
|
operand is the exact parsed token, with expansions and parent traversal refused.
|
|
1812
1924
|
The resolved helper must be a regular file inside runtime/ beside this guard,
|
|
1813
1925
|
or beside the guard itself in the repository layout. Resolve only in supplied
|
|
@@ -1826,9 +1938,12 @@ def bundled_helper_invocation(command, tokens, working_directories, helpers=PIPE
|
|
|
1826
1938
|
_, substitutions = split_command_substitutions(command)
|
|
1827
1939
|
if substitutions or wrapped_command_tokens(segments[0]) is not None:
|
|
1828
1940
|
return False
|
|
1829
|
-
if tokens[
|
|
1941
|
+
if tokens[:2] == ["py", "-3"]:
|
|
1942
|
+
rest = tokens[2:]
|
|
1943
|
+
elif tokens[0] in ("python", "python3"):
|
|
1944
|
+
rest = tokens[1:]
|
|
1945
|
+
else:
|
|
1830
1946
|
return False
|
|
1831
|
-
rest = tokens[1:]
|
|
1832
1947
|
if rest[:1] == ["-B"]:
|
|
1833
1948
|
rest = rest[1:]
|
|
1834
1949
|
if not rest:
|
|
@@ -1853,7 +1968,8 @@ def bundled_helper_invocation(command, tokens, working_directories, helpers=PIPE
|
|
|
1853
1968
|
runtime = runtime.resolve()
|
|
1854
1969
|
for base in working_directories or [os.getcwd()]:
|
|
1855
1970
|
try:
|
|
1856
|
-
|
|
1971
|
+
# A Git Bash /c/... operand names the C: drive, not a folder named c.
|
|
1972
|
+
script = (Path(base) / native_path_text(operand)).resolve()
|
|
1857
1973
|
if (
|
|
1858
1974
|
script.name in helpers
|
|
1859
1975
|
and (script.is_relative_to(runtime) or (
|
|
@@ -2035,7 +2151,7 @@ def closed_shell_execution_reason(command, tokens, working_directories):
|
|
|
2035
2151
|
for directory in directories:
|
|
2036
2152
|
result = subprocess.run(
|
|
2037
2153
|
["git", *options, "rev-parse", "--show-toplevel", "--absolute-git-dir"],
|
|
2038
|
-
cwd=directory, capture_output=True,
|
|
2154
|
+
cwd=directory, capture_output=True, encoding="utf-8", errors="replace",
|
|
2039
2155
|
)
|
|
2040
2156
|
if result.returncode:
|
|
2041
2157
|
raise ValueError("git target repository cannot be resolved")
|
|
@@ -2068,8 +2184,8 @@ def command_denial(command, working_directories, allow_destructive=True):
|
|
|
2068
2184
|
try:
|
|
2069
2185
|
tokens = shell_tokens(outer)
|
|
2070
2186
|
if tokens in (
|
|
2071
|
-
[interpreter, "-B", "-c", "import sys; raise SystemExit(sys.version_info < (3, 9))"]
|
|
2072
|
-
for interpreter in ("python3", "python")
|
|
2187
|
+
[*interpreter.split(), "-B", "-c", "import sys; raise SystemExit(sys.version_info < (3, 9))"]
|
|
2188
|
+
for interpreter in ("python3", "python", "py -3")
|
|
2073
2189
|
):
|
|
2074
2190
|
return None
|
|
2075
2191
|
destructive = list(destructive_shell_invocations(tokens))
|
|
@@ -2116,8 +2232,14 @@ def allow(event):
|
|
|
2116
2232
|
|
|
2117
2233
|
|
|
2118
2234
|
def main():
|
|
2235
|
+
# Hosts exchange hook input and output as UTF-8; a Windows pipe would
|
|
2236
|
+
# default to cp1252. In-process callers may substitute text streams.
|
|
2237
|
+
for stream in (sys.stdout, sys.stderr):
|
|
2238
|
+
if hasattr(stream, "reconfigure"):
|
|
2239
|
+
stream.reconfigure(encoding="utf-8", errors="replace")
|
|
2119
2240
|
try:
|
|
2120
|
-
|
|
2241
|
+
stdin = getattr(sys.stdin, "buffer", None)
|
|
2242
|
+
event = json.loads(stdin.read().decode("utf-8") if stdin is not None else sys.stdin.read())
|
|
2121
2243
|
if not isinstance(event, dict):
|
|
2122
2244
|
raise ValueError("hook event must be an object")
|
|
2123
2245
|
evaluate(event)
|
package/scripts/install.mjs
CHANGED
|
@@ -14,32 +14,12 @@ export const CLAUDE_BRIDGE = "@../AGENTS.md\n@../WORKFLOW.md\n";
|
|
|
14
14
|
export const HOOKS_DIRECTORY = ".gsd-path";
|
|
15
15
|
export const GUARD_SCRIPTS = ["guard_hook.py", "git_guard.py"];
|
|
16
16
|
export const GUARD_MARKER = "gsd-path guard";
|
|
17
|
-
export const PROJECT_RUNTIME_SCRIPTS = [
|
|
18
|
-
"pipeline_state.py",
|
|
19
|
-
"roadmap.py",
|
|
20
|
-
"check_handoffs.py",
|
|
21
|
-
"check_task_briefs.py",
|
|
22
|
-
"isolation.py",
|
|
23
|
-
"discussion_records.py",
|
|
24
|
-
"pipeline_git.py",
|
|
25
|
-
"promote_lookahead.py",
|
|
26
|
-
"detect_project.py",
|
|
27
|
-
"pipeline_diagnose.py",
|
|
28
|
-
"pipeline_undo.py",
|
|
29
|
-
"archive_milestone.py",
|
|
30
|
-
"review_panel.py",
|
|
31
|
-
"_common.py",
|
|
32
|
-
"worktree_paths.py",
|
|
33
|
-
"build_recovery.py",
|
|
34
|
-
"state_checkpoint.py",
|
|
35
|
-
"state_promote.py",
|
|
36
|
-
"discussion_validate.py",
|
|
37
|
-
"integration.py",
|
|
38
|
-
];
|
|
39
17
|
export const PROJECT_RUNTIME_MARKER = "gsd-path project runtime";
|
|
40
18
|
export const PROJECT_STATUS_LAUNCHER = "status_runtime.py";
|
|
41
19
|
export const PROJECT_STATUS_MARKER = "gsd-path project status launcher";
|
|
42
20
|
const INSTALL_LOCK_NAME = ".gsd-path-install-lock";
|
|
21
|
+
const LOCAL_EDITS_NOTE =
|
|
22
|
+
"; local edits inside these skills are not carried forward (see UPDATE.md#local-additions)";
|
|
43
23
|
const INSTALL_LOCK_OWNER = "owner.json";
|
|
44
24
|
const INSTALL_LOCK_SCHEMA = "gsd-path/install-lock/v2";
|
|
45
25
|
export const CLAUDE_MATCHER = ".*";
|
|
@@ -151,15 +131,22 @@ export const GIT_HOOKS = [
|
|
|
151
131
|
];
|
|
152
132
|
export const GIT_HOOK_NAMES = GIT_HOOKS.map(([name]) => name);
|
|
153
133
|
|
|
154
|
-
// Every interpreter a managed hook may legitimately be pinned to.
|
|
155
|
-
|
|
134
|
+
// Every interpreter a managed hook may legitimately be pinned to. Each is a
|
|
135
|
+
// command prefix: python.org Windows installs may put only `py` on PATH.
|
|
136
|
+
const INTERPRETER_CANDIDATES = ["python3", "python", "py -3"];
|
|
137
|
+
|
|
138
|
+
// Run a Python interpreter command prefix (for example "py -3") with args.
|
|
139
|
+
function spawnPython(interpreter, args, options) {
|
|
140
|
+
const [command, ...prefix] = interpreter.split(" ");
|
|
141
|
+
return spawnSync(command, [...prefix, ...args], options);
|
|
142
|
+
}
|
|
156
143
|
|
|
157
|
-
// Probe for a runnable Python interpreter (python3, then python
|
|
158
|
-
// hooks never hard-code an interpreter that does not exist on this
|
|
159
|
-
// (python3 is typically absent on Windows).
|
|
144
|
+
// Probe for a runnable Python interpreter (python3, then python, then py -3)
|
|
145
|
+
// so emitted hooks never hard-code an interpreter that does not exist on this
|
|
146
|
+
// machine (python3 is typically absent on Windows).
|
|
160
147
|
export function detectPythonInterpreter() {
|
|
161
148
|
for (const candidate of INTERPRETER_CANDIDATES) {
|
|
162
|
-
const result =
|
|
149
|
+
const result = spawnPython(
|
|
163
150
|
candidate,
|
|
164
151
|
["-B", "-c", "import sys; raise SystemExit(sys.version_info < (3, 9))"],
|
|
165
152
|
{ stdio: "ignore" }
|
|
@@ -475,12 +462,17 @@ function materializedResourceBytes(source, destination) {
|
|
|
475
462
|
return Buffer.from(rewriteRouterAliasSkill(data.toString("utf8"), alias, canonical));
|
|
476
463
|
}
|
|
477
464
|
|
|
465
|
+
// The JavaScript realpath keeps Windows 8.3 short names (C:\Users\LONGNA~1);
|
|
466
|
+
// Python's resolve() expands them, and both installers must hash one lock
|
|
467
|
+
// identity per target, so Windows asks the OS for the final path as Python does.
|
|
468
|
+
const realpath = process.platform === "win32" ? fs.realpathSync.native : fs.realpathSync;
|
|
469
|
+
|
|
478
470
|
function resolveNonStrict(value) {
|
|
479
471
|
let prefix = path.resolve(value);
|
|
480
472
|
const suffix = [];
|
|
481
473
|
for (;;) {
|
|
482
474
|
try {
|
|
483
|
-
return path.join(
|
|
475
|
+
return path.join(realpath(prefix), ...suffix);
|
|
484
476
|
} catch {
|
|
485
477
|
const parent = path.dirname(prefix);
|
|
486
478
|
if (parent === prefix) return path.join(prefix, ...suffix);
|
|
@@ -1090,7 +1082,7 @@ function rollbackTarget(transaction) {
|
|
|
1090
1082
|
removePath(destination);
|
|
1091
1083
|
}
|
|
1092
1084
|
for (const [original, stored] of [...transaction.moved].reverse()) {
|
|
1093
|
-
if (lexists(stored))
|
|
1085
|
+
if (lexists(stored)) renameRetrying(stored, original);
|
|
1094
1086
|
}
|
|
1095
1087
|
if (transaction.backup !== null) {
|
|
1096
1088
|
try {
|
|
@@ -1314,13 +1306,34 @@ function plannedBackupRoots(legacyRoot, deployments) {
|
|
|
1314
1306
|
return planned;
|
|
1315
1307
|
}
|
|
1316
1308
|
|
|
1309
|
+
// Access denied and busy: on Windows a scanner or indexer that briefly opens a
|
|
1310
|
+
// freshly written file blocks moving it or the directory holding it.
|
|
1311
|
+
const WINDOWS_BUSY_CODES = new Set(["EPERM", "EACCES", "EBUSY"]);
|
|
1312
|
+
const RENAME_ATTEMPTS = 20;
|
|
1313
|
+
|
|
1314
|
+
function renameRetrying(from, to) {
|
|
1315
|
+
if (process.platform !== "win32") return fs.renameSync(from, to);
|
|
1316
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
1317
|
+
try {
|
|
1318
|
+
return fs.renameSync(from, to);
|
|
1319
|
+
} catch (error) {
|
|
1320
|
+
// A directory moved onto an existing directory also reports EPERM; that
|
|
1321
|
+
// is not transient, so never retry it.
|
|
1322
|
+
if (!WINDOWS_BUSY_CODES.has(error.code)
|
|
1323
|
+
|| fs.lstatSync(to, { throwIfNoEntry: false })?.isDirectory()
|
|
1324
|
+
|| attempt === RENAME_ATTEMPTS - 1) throw error;
|
|
1325
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, Math.min(10 * 2 ** attempt, 250));
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1317
1330
|
// Test-injection points; production callers never touch these.
|
|
1318
1331
|
export const hooks = {
|
|
1319
1332
|
mismatches,
|
|
1320
1333
|
applyTarget,
|
|
1321
|
-
rename:
|
|
1334
|
+
rename: renameRetrying,
|
|
1322
1335
|
renameInstallLock: fs.renameSync.bind(fs),
|
|
1323
|
-
renameInstallStage:
|
|
1336
|
+
renameInstallStage: renameRetrying,
|
|
1324
1337
|
processIdentity,
|
|
1325
1338
|
reserveDirectory,
|
|
1326
1339
|
reserveFile,
|
|
@@ -1332,7 +1345,7 @@ export const hooks = {
|
|
|
1332
1345
|
// Project installation already requires Python; keep runtime lifecycle ownership there.
|
|
1333
1346
|
function projectAdapter(source, project, action, payload = {}, env = process.env) {
|
|
1334
1347
|
const interpreter = requiredPythonRuntime("project runtime");
|
|
1335
|
-
const result =
|
|
1348
|
+
const result = spawnPython(interpreter, ["-B", path.join(SCRIPT_DIRECTORY, "install.py"), "--adapter-request"], {
|
|
1336
1349
|
input: JSON.stringify({ source, project, action, ...payload }), encoding: "utf8", env: { ...process.env, ...env },
|
|
1337
1350
|
});
|
|
1338
1351
|
if (result.error || result.status !== 0) {
|
|
@@ -1484,7 +1497,7 @@ export async function install(sourceRoot, plans, options = {}) {
|
|
|
1484
1497
|
if (legacyTransaction.backup !== null) {
|
|
1485
1498
|
results.push(
|
|
1486
1499
|
`codex-legacy: backed up ${legacyTransaction.moved.length} entries to ` +
|
|
1487
|
-
`${legacyTransaction.backup}`
|
|
1500
|
+
`${legacyTransaction.backup}${LOCAL_EDITS_NOTE}`
|
|
1488
1501
|
);
|
|
1489
1502
|
}
|
|
1490
1503
|
}
|
|
@@ -1503,7 +1516,7 @@ export async function install(sourceRoot, plans, options = {}) {
|
|
|
1503
1516
|
if (transaction.backup !== null) {
|
|
1504
1517
|
results.push(
|
|
1505
1518
|
`${plan.targets.join("+")}: backed up ${transaction.moved.length} entries ` +
|
|
1506
|
-
`to ${transaction.backup}`
|
|
1519
|
+
`to ${transaction.backup}${LOCAL_EDITS_NOTE}`
|
|
1507
1520
|
);
|
|
1508
1521
|
}
|
|
1509
1522
|
}
|
|
@@ -1559,6 +1572,7 @@ export function parseCli(argv) {
|
|
|
1559
1572
|
"dry-run": { type: "boolean", default: false },
|
|
1560
1573
|
local: { type: "boolean", default: false },
|
|
1561
1574
|
project: { type: "string" },
|
|
1575
|
+
"member-of": { type: "string" },
|
|
1562
1576
|
doctor: { type: "boolean", default: false },
|
|
1563
1577
|
hooks: { type: "boolean", default: false },
|
|
1564
1578
|
"hooks-init": { type: "boolean", default: false },
|
|
@@ -1653,9 +1667,10 @@ function usage() {
|
|
|
1653
1667
|
" --doctor read-only health check of installs, hooks, and state\n" +
|
|
1654
1668
|
" --hooks with --project: install guard hooks (see HOOKS.md)\n" +
|
|
1655
1669
|
" --hooks-init add guards to an existing project without changing its contracts\n" +
|
|
1670
|
+
" --member-of PATH with --project: install member git hooks that run PATH's guard\n" +
|
|
1656
1671
|
" --runtime-restore restore the exact declared runtime from --source-root\n" +
|
|
1657
1672
|
" --runtime-upgrade explicitly select the supplied package runtime\n" +
|
|
1658
|
-
" --runtime-migrate migrate a
|
|
1673
|
+
" --runtime-migrate migrate a legacy runtime for review; add --update to continue upgrading\n" +
|
|
1659
1674
|
" --hooks-refresh validate the selected runtime; keep its version\n" +
|
|
1660
1675
|
" --hooks-refresh-full refresh native settings/git hooks; target flags create missing configs\n" +
|
|
1661
1676
|
" --dry-run preview without writing\n" +
|
|
@@ -1702,9 +1717,10 @@ export async function main(argv, env = process.env) {
|
|
|
1702
1717
|
ui.error("--runtime-migrate --update requires --project and cannot be combined with another runtime, doctor, or hook operation");
|
|
1703
1718
|
return 2;
|
|
1704
1719
|
}
|
|
1705
|
-
if (!migrateAndUpdate && ["
|
|
1720
|
+
if (!migrateAndUpdate && (values["member-of"] !== undefined
|
|
1721
|
+
|| ["runtime-restore", "runtime-upgrade", "runtime-migrate"].some(name => values[name]))) {
|
|
1706
1722
|
const interpreter = requiredPythonRuntime("project runtime");
|
|
1707
|
-
const result =
|
|
1723
|
+
const result = spawnPython(interpreter, ["-B", path.join(SCRIPT_DIRECTORY, "install.py"),
|
|
1708
1724
|
...argv.filter(arg => arg !== "--no-color")], { stdio: "inherit", env });
|
|
1709
1725
|
return result.status ?? 1;
|
|
1710
1726
|
}
|
|
@@ -1809,7 +1825,7 @@ export async function main(argv, env = process.env) {
|
|
|
1809
1825
|
}
|
|
1810
1826
|
if (migrateAndUpdate) {
|
|
1811
1827
|
const interpreter = requiredPythonRuntime("project runtime");
|
|
1812
|
-
const migration =
|
|
1828
|
+
const migration = spawnPython(interpreter, ["-B", path.join(SCRIPT_DIRECTORY, "install.py"),
|
|
1813
1829
|
"--runtime-migrate", "--project", project, "--source-root", sourceRoot,
|
|
1814
1830
|
...(values["dry-run"] ? ["--dry-run"] : [])], { stdio: "inherit", env });
|
|
1815
1831
|
if (migration.error || migration.status !== 0) {
|