@danmoisan/drm-copilot-mcp 1.1.16 → 1.1.18
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/out/mcp-server.js +1139 -133
- package/package.json +4 -3
- package/resources/claude-customizations/.claude/agents/feature-review.md +10 -1
- package/resources/claude-customizations/.claude/agents/orchestrator.md +16 -3
- package/resources/claude-customizations/.claude/agents/parallel-orchestrator.md +18 -6
- package/resources/claude-customizations/.claude/agents/python-typed-engineer.md +3 -3
- package/resources/claude-customizations/.claude/hooks/{enforce-powershell-batch-budget-route.ps1 → enforce-batch-budget-route.ps1} +12 -10
- package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate-resolution.ps1 +221 -0
- package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate.ps1 +42 -66
- package/resources/claude-customizations/.claude/hooks/enforce-epic-wave-barrier.ps1 +58 -10
- package/resources/claude-customizations/.claude/hooks/enforce-epic-worktree-removal-gate-resolution.ps1 +144 -0
- package/resources/claude-customizations/.claude/hooks/enforce-epic-worktree-removal-gate.ps1 +36 -47
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-epic-scope.ps1 +179 -27
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +21 -38
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-abandon-gate.ps1 +4 -4
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-cohort-barrier.ps1 +58 -10
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-drift-gate.ps1 +59 -9
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-worktree-removal-gate.ps1 +114 -37
- package/resources/claude-customizations/.claude/hooks/enforce-powershell-batch-budget.ps1 +3 -3
- package/resources/claude-customizations/.claude/hooks/enforce-python-batch-budget.ps1 +99 -64
- package/resources/claude-customizations/.claude/lib/bash/abandon-parallel-item.sh +176 -0
- package/resources/claude-customizations/.claude/lib/bash/parallel-lane-assertion.sh +4 -2
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusConflict.psm1 +193 -14
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusGlob.psm1 +51 -28
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusScheduling.psm1 +5 -8
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorState.psm1 +6 -13
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateIssueAdoption.psm1 +375 -0
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateReceipts.psm1 +22 -15
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateRemediationAccounting.psm1 +337 -0
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateRoutingContract.psm1 +9 -1
- package/resources/claude-customizations/.claude/lib/parallel-drift/Invoke-ParallelDriftDetection.ps1 +336 -0
- package/resources/claude-customizations/.claude/lib/parallel-drift/ParallelDrift.psm1 +467 -0
- package/resources/claude-customizations/.claude/lib/parallel-drift/ParallelDriftHalt.psm1 +388 -0
- package/resources/claude-customizations/.claude/lib/worktree-resolution/EpicScopeResolution.psm1 +53 -32
- package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeItemResolution.psm1 +19 -4
- package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeRunResolution.psm1 +497 -0
- package/resources/claude-customizations/.claude/rules/orchestrator-state.md +119 -3
- package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +21 -0
- package/resources/claude-customizations/.claude/rules/quality-tiers.md +1 -1
- package/resources/claude-customizations/.claude/skills/acceptance-criteria-tracking/SKILL.md +8 -0
- package/resources/claude-customizations/.claude/skills/atomic-plan-contract/SKILL.md +2 -0
- package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/SKILL.md +11 -3
- package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup-worktrees.sh +11 -2
- package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_enumerate_lib.sh +169 -5
- package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_preserve_lib.sh +1 -1
- package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_report_records_lib.sh +6 -43
- package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_scan_helper.sh +4 -15
- package/resources/claude-customizations/.claude/skills/epic-orchestrate/SKILL.md +15 -6
- package/resources/claude-customizations/.claude/skills/evidence-and-timestamp-conventions/SKILL.md +4 -0
- package/resources/claude-customizations/.claude/skills/feature-promotion-lifecycle/SKILL.md +10 -0
- package/resources/claude-customizations/.claude/skills/feature-review-workflow/SKILL.md +3 -1
- package/resources/claude-customizations/.claude/skills/invoke-csharp-engineer/SKILL.md +4 -0
- package/resources/claude-customizations/.claude/skills/invoke-powershell-engineer/SKILL.md +4 -0
- package/resources/claude-customizations/.claude/skills/invoke-python-engineer/SKILL.md +6 -3
- package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +33 -13
- package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +50 -21
- package/resources/claude-customizations/.claude/skills/parallel-remove/SKILL.md +10 -10
- package/resources/claude-customizations/.claude/skills/python-change-budget-router/SKILL.md +11 -35
- package/resources/claude-customizations/.claude/skills/remediation-handoff-atomic-planner/SKILL.md +5 -1
- package/resources/claude-customizations/pack-manifests/core.json +10 -0
- package/resources/claude-customizations/pack-manifests/powershell.json +0 -1
- package/resources/codex-and-agents-customizations/.agents/skills/acceptance-criteria-tracking/SKILL.md +8 -0
- package/resources/codex-and-agents-customizations/.agents/skills/epic-orchestrate/SKILL.md +5 -3
- package/resources/codex-and-agents-customizations/.agents/skills/evidence-and-timestamp-conventions/SKILL.md +4 -0
- package/resources/codex-and-agents-customizations/.agents/skills/feature-promotion-lifecycle/SKILL.md +1 -1
- package/resources/codex-and-agents-customizations/.agents/skills/feature-review/SKILL.md +30 -6
- package/resources/codex-and-agents-customizations/.agents/skills/feature-review-workflow/SKILL.md +3 -1
- package/resources/codex-and-agents-customizations/.agents/skills/invoke-powershell-engineer/SKILL.md +0 -1
- package/resources/codex-and-agents-customizations/.agents/skills/invoke-python-engineer/SKILL.md +2 -3
- package/resources/codex-and-agents-customizations/.agents/skills/orchestrate/SKILL.md +30 -7
- package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-state/SKILL.md +41 -2
- package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-workflow/SKILL.md +18 -3
- package/resources/codex-and-agents-customizations/.agents/skills/python-change-budget-router/SKILL.md +10 -34
- package/resources/codex-and-agents-customizations/.agents/skills/quality-tiers/SKILL.md +1 -1
- package/resources/codex-and-agents-customizations/.agents/skills/remediation-handoff-atomic-planner/SKILL.md +2 -0
- package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c1.toml +1 -0
- package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c2.toml +1 -0
- package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c3-elevated.toml +1 -0
- package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c3.toml +1 -0
- package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c4.toml +1 -0
- package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer.toml +1 -0
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c1.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c2.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c3-elevated.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c3.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c4.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/python-orchestrator.toml +2 -2
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c1.toml +3 -3
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c2.toml +3 -3
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c3-elevated.toml +3 -3
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c3.toml +3 -3
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c4.toml +3 -3
- package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer.toml +3 -3
- package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-batch-budget-route.ps1 +139 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-powershell-batch-budget.ps1 +4 -124
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-python-batch-budget.ps1 +169 -79
- package/resources/codex-and-agents-customizations/pack-manifests/core.json +1 -0
- package/resources/customizations/.github/agents/python-orchestrator.agent.md +4 -4
- package/resources/customizations/.github/agents/python-typed-engineer.agent.md +7 -8
- package/resources/customizations/.github/prompts/orchestrate-python-work.prompt.md +3 -3
- package/resources/customizations/.github/skills/acceptance-criteria-tracking/SKILL.md +8 -0
- package/resources/customizations/.github/skills/evidence-and-timestamp-conventions/SKILL.md +4 -0
- package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +16 -1
|
@@ -24,7 +24,7 @@ description: Checkpoint invariants for the orchestration state artifact and the
|
|
|
24
24
|
|
|
25
25
|
# Orchestrator-State Remediation-Cycle and Human-Interaction Invariants
|
|
26
26
|
|
|
27
|
-
This rule governs remediation-cycle records and the optional `human_interaction` block in the orchestrator-state checkpoint at `artifacts/orchestration/orchestrator-state.json`. It documents
|
|
27
|
+
This rule governs remediation-cycle records and the optional `human_interaction` block in the orchestrator-state checkpoint at `artifacts/orchestration/orchestrator-state.json`. It documents the invariants that must hold for each remediation cycle and for the remediation-loop review outcomes and attempt count, plus three invariants for the `human_interaction` block, so that resume and review workflows do not depend on a structurally invalid checkpoint.
|
|
28
28
|
|
|
29
29
|
## Foreign Schema Warning (do not copy verbatim)
|
|
30
30
|
|
|
@@ -34,7 +34,7 @@ This prohibition is specific to the disqualified foreign schema identified by th
|
|
|
34
34
|
|
|
35
35
|
## Scope and Backward Compatibility
|
|
36
36
|
|
|
37
|
-
These invariants apply only when the checkpoint contains a top-level `remediation_loop
|
|
37
|
+
These invariants apply only when the checkpoint contains a top-level `remediation_loop`; the per-cycle invariants apply when it has a `cycles` array, and the review-outcome and attempt-count invariants apply when their keys are present. A checkpoint with no `remediation_loop` (the existing step-based checkpoint shape) is unaffected: it validates exactly as before and produces no new errors. The invariants are additive.
|
|
38
38
|
|
|
39
39
|
## Invariants (per remediation cycle)
|
|
40
40
|
|
|
@@ -44,6 +44,45 @@ These invariants apply only when the checkpoint contains a top-level `remediatio
|
|
|
44
44
|
|
|
45
45
|
3. **Exit gate requires zero blocking findings.** When a cycle's `exit_condition_met == true`, its `blocking_count` must be `0`. A non-zero `blocking_count` with `exit_condition_met == true` is a malformed cycle (the exit gate was marked satisfied while blocking findings remained).
|
|
46
46
|
|
|
47
|
+
4. **`candidate_applied` is a boolean (R5).** When a cycle object carries `candidate_applied`, its value must be a JSON boolean, and any other value is a malformed cycle.
|
|
48
|
+
|
|
49
|
+
5. **A completed attempt requires completed execution (R6).** A cycle whose `candidate_applied == true` must have `execution_status == "complete"`, and `candidate_applied == true` with any other execution status is a malformed cycle.
|
|
50
|
+
|
|
51
|
+
6. **`opened_by_review` references a remediation review (R11).** When a cycle object carries `opened_by_review`, its value must be a non-negative integer (not a boolean) that indexes an object entry of the `remediation_loop.review_outcomes` list whose `verdict` is `REMEDIATION_REQUIRED`, and a value that is not such an integer, is out of range, or references an outcome with any other verdict is a malformed cycle.
|
|
52
|
+
|
|
53
|
+
7. **`completed_attempts` counts completed attempts (R7).** When `remediation_loop.completed_attempts` is present, it must be a non-negative integer (not a boolean) equal to the number of object cycles whose `candidate_applied` is `true` (zero when `cycles` is absent or not a list), and any other value is a malformed loop.
|
|
54
|
+
|
|
55
|
+
## Invariants (remediation_loop.review_outcomes)
|
|
56
|
+
|
|
57
|
+
These invariants apply only when `remediation_loop` contains a `review_outcomes` key. The orchestrator appends one entry per review, including a `PASS` review, in the shape `{"verdict": <verdict>, "findings": [{"remediability": <class>}, ...]}`, where `findings` lists the review's blocking findings only. A halt or wait verdict records no remediation cycle.
|
|
58
|
+
|
|
59
|
+
Review verdicts (exact, case-sensitive):
|
|
60
|
+
|
|
61
|
+
| Verdict | Condition over the classes of the review's blocking findings |
|
|
62
|
+
|---|---|
|
|
63
|
+
| `PASS` | No blocking findings. |
|
|
64
|
+
| `REMEDIATION_REQUIRED` | At least one `autonomous` finding. |
|
|
65
|
+
| `HALT_NON_REMEDIABLE` | No `autonomous` finding, and at least one `external_dependency`, `policy_hold`, or `human_decision_required` finding. |
|
|
66
|
+
| `AWAITING_CI` | At least one finding, and every finding is `awaiting_ci`. |
|
|
67
|
+
|
|
68
|
+
Remediability classes (exact, case-sensitive) and the `blocked_reason` member (#523) that a halt or wait records for each class:
|
|
69
|
+
|
|
70
|
+
| Class | Meaning | `blocked_reason` member |
|
|
71
|
+
|---|---|---|
|
|
72
|
+
| `autonomous` | Repository remediation can resolve the finding without a human decision. | none (remediation loop) |
|
|
73
|
+
| `external_dependency` | A system, service, runtime, or published artifact outside the repository is unavailable or mismatched. | `external_dependency` |
|
|
74
|
+
| `policy_hold` | Proceeding requires a policy decision, exception, or authorization that the orchestrator may not grant. | `policy_hold` |
|
|
75
|
+
| `awaiting_ci` | The finding resolves when a CI result that has not completed becomes available. | `awaiting_ci` |
|
|
76
|
+
| `human_decision_required` | A human must choose between alternatives or approve a direction. | `human_decision_required` |
|
|
77
|
+
|
|
78
|
+
Derivation order. The verdict is a pure function of the findings' classes, evaluated in this order: no findings yields `PASS`; any `autonomous` finding yields `REMEDIATION_REQUIRED`; any `external_dependency`, `policy_hold`, or `human_decision_required` finding yields `HALT_NON_REMEDIABLE`; otherwise the verdict is `AWAITING_CI`.
|
|
79
|
+
|
|
80
|
+
1. **`review_outcomes` is a list of objects (R8).** `review_outcomes` must be a list, and each entry must be an object. A non-list value or a non-object entry is malformed.
|
|
81
|
+
|
|
82
|
+
2. **Verdict and class membership (R9).** Each outcome's `verdict` must be a string among the four verdicts, its `findings` must be a list (a missing key is not a list), and each finding must be an object whose `remediability` is a string among the five classes. Comparisons are case-sensitive, so a case variant such as `Pass` or `Autonomous` is malformed.
|
|
83
|
+
|
|
84
|
+
3. **Verdict matches its findings (R10).** When an outcome passes the membership checks, its `verdict` must equal the verdict that the derivation order yields for its findings' classes. A mismatch is a malformed outcome.
|
|
85
|
+
|
|
47
86
|
## Human-Interaction Scope and Backward Compatibility
|
|
48
87
|
|
|
49
88
|
These invariants apply only when the checkpoint contains a top-level `human_interaction` block. A checkpoint with no `human_interaction` key (the existing checkpoint shape) is unaffected: it validates exactly as before and produces no new errors. The invariants are additive and support the autonomous-execution mandate documented in `.claude/skills/orchestrate/SKILL.md`.
|
|
@@ -56,6 +95,41 @@ These invariants apply only when the checkpoint contains a top-level `human_inte
|
|
|
56
95
|
|
|
57
96
|
3. **Exception requires `runbook_path`.** A requirement whose `response == "exception"` must carry a non-empty `runbook_path` string. A missing, non-string, or empty/whitespace-only `runbook_path` on an `exception` requirement is a malformed requirement.
|
|
58
97
|
|
|
98
|
+
## Blocked-Reason Vocabulary
|
|
99
|
+
|
|
100
|
+
The checkpoint key `blocked_reason` accepts exactly the twelve members below. The authoritative definition is `scripts/dev_tools/_orchestrator_state_blocked_reason.py`; the TypeScript module `extensions/drm-copilot/src/lib/validate/orchestrator-state-blocked-reason.ts` and the grouped arrays in `.claude/lib/orchestrator-state/OrchestratorState.psm1` mirror it.
|
|
101
|
+
|
|
102
|
+
| Member | Partition | Definition |
|
|
103
|
+
|---|---|---|
|
|
104
|
+
| `none` | not blocked | The run is not blocked. JSON `null` is equivalent for classification. |
|
|
105
|
+
| `spawn_agent_unavailable` | mechanical | Existing member; meaning unchanged. |
|
|
106
|
+
| `delegation_launch_failed` | mechanical | Existing member; meaning unchanged. |
|
|
107
|
+
| `delegate_no_receipt` | mechanical | Existing member; meaning unchanged. |
|
|
108
|
+
| `delegate_contract_incomplete` | mechanical | Existing member; meaning unchanged. |
|
|
109
|
+
| `validator_failed` | mechanical | Existing member; meaning unchanged. |
|
|
110
|
+
| `user_requested_stop` | mechanical | Existing member; meaning unchanged. |
|
|
111
|
+
| `premise_falsified` | non-mechanical | Every delegation and validator succeeded, but evidence gathered during execution falsified the premise on which the plan was built, so continuing would implement an invalid plan. |
|
|
112
|
+
| `external_dependency` | non-mechanical | The run cannot proceed because a system, service, runtime, or artifact outside the repository's control is unavailable or mismatched, and no in-repository remediation can resolve it. |
|
|
113
|
+
| `policy_hold` | non-mechanical | The run is stopped because proceeding requires a policy decision, exception, or authorization that the orchestrator is not permitted to grant autonomously. |
|
|
114
|
+
| `awaiting_ci` | non-mechanical | The run is waiting for a CI result that has not yet completed, and no remediation is warranted until that result is available. |
|
|
115
|
+
| `human_decision_required` | non-mechanical | The run requires a human to choose between alternatives or approve a direction before it can continue. |
|
|
116
|
+
|
|
117
|
+
Partition definitions:
|
|
118
|
+
|
|
119
|
+
- **Not blocked:** `none`, JSON `null`, or key absent at a gate. Plain validation still reports an absent key through the required-key check.
|
|
120
|
+
- **Mechanical:** the orchestration process itself did not complete a step: a tool or delegation failed, a delegate returned no or an incomplete receipt, a validator failed, or the operator stopped execution. Membership is defined by the published constant, not by the wording.
|
|
121
|
+
- **Non-mechanical:** every process step could proceed, but the work cannot continue for a reason that no retry or remediation cycle of the process resolves.
|
|
122
|
+
|
|
123
|
+
The classification is recoverable from `blocked_reason` alone by looking the value up in the published partition (`classify_blocked_reason` in Python, `classifyBlockedReason` in TypeScript).
|
|
124
|
+
|
|
125
|
+
Enforcement parity: the Python, PowerShell, and TypeScript validators enforce this vocabulary identically, with case-sensitive comparison. A value outside the vocabulary, including a case variant of any member, is rejected with `Checkpoint has invalid blocked_reason: <value>`. Every non-`none` member, including the five non-mechanical members, blocks completion and PR-creation readiness with the existing messages.
|
|
126
|
+
|
|
127
|
+
Relationship to `human_interaction.requirements[].response == "halt"`: a human-decision halt may appear in both places. `blocked_reason: "human_decision_required"` classifies the run's halt, and a `human_interaction.requirements[]` entry with `response: "halt"` describes the specific requirement. There is no cross-field rule between them: either may appear without the other, and neither is validated against the other.
|
|
128
|
+
|
|
129
|
+
Producer guidance for `premise_falsified`: set it when every delegation and validator succeeded but evidence gathered during execution falsified the plan's premise. The evidence path is still recorded in free-form checkpoint keys; `blocked_reason` carries the classification only.
|
|
130
|
+
|
|
131
|
+
Extension point for #484: #484 consumes `external_dependency`, `policy_hold`, `awaiting_ci`, and `human_decision_required` without renaming, removing, or re-partitioning them; any further halt class is a separate contract change that appends the literal to the non-mechanical partition in all three runtimes, updates the partition oracle and corpus, and updates this section.
|
|
132
|
+
|
|
59
133
|
## Complexity-Assessment Scope and Backward Compatibility
|
|
60
134
|
|
|
61
135
|
These invariants apply only when the checkpoint contains a top-level `complexity_assessments` array. A checkpoint with no `complexity_assessments` key (the existing checkpoint shape) is unaffected: it validates exactly as before and produces no new errors. The invariants are additive and support the two-axis model-selection mechanism documented in `.claude/skills/orchestrate/SKILL.md` (`## Model Selection`). Enforcement is the Python validator, not an imported schema.
|
|
@@ -92,7 +166,11 @@ The session `model_budget.fable_policy` switch is a three-way enum `disabled | a
|
|
|
92
166
|
|
|
93
167
|
## Require-Model-Routing Mode Scope and Backward Compatibility
|
|
94
168
|
|
|
95
|
-
The complexity-assessment and model-routing-receipt invariants above are key-gated: they run only when their key is present, so a checkpoint that omits both arrays passes at every stage. The `require_model_routing` mode adds an existence gate that closes that gap without changing the default behavior. It is an opt-in keyword on `validate_orchestrator_state_text(..., require_model_routing=False)` (CLI flag `--require-model-routing
|
|
169
|
+
The complexity-assessment and model-routing-receipt invariants above are key-gated: they run only when their key is present, so a checkpoint that omits both arrays passes at every stage. The `require_model_routing` mode adds an existence gate that closes that gap without changing the default behavior. It is an opt-in keyword on `validate_orchestrator_state_text(..., require_model_routing=False)` (CLI flag `--require-model-routing`, accepted by the dispatcher `orchestrator-state` subcommand and by the bare-module CLI; MCP parameter `require_model_routing`), defaulting off. Plain, `require_complete`, and `require_pr_creation_ready` calls are unaffected and produce byte-identical results.
|
|
170
|
+
|
|
171
|
+
## Bare-Module CLI Contract
|
|
172
|
+
|
|
173
|
+
The validator module runs directly as `python -m scripts.dev_tools.validate_orchestrator_state <path> [--require-complete] [--require-model-routing] [--require-pr-creation-ready] [--require-codex-model-routing] [--require-codex-topology]`. The dispatcher `python -m scripts.dev_tools.validate_orchestration_artifacts orchestrator-state` stays available and accepts the same flags. Exit code 0 means the checkpoint passed and the success line `orchestrator-state validation passed: <path>` is written to stdout. Exit code 1 means validation errors, written one per stderr line. Exit code 2 means the path is missing, unreadable, or not UTF-8, with one stderr diagnostic and no traceback; argparse usage errors also exit 2.
|
|
96
174
|
|
|
97
175
|
## Invariants (require_model_routing mode)
|
|
98
176
|
|
|
@@ -146,6 +224,43 @@ not a cryptographic or security control.
|
|
|
146
224
|
|
|
147
225
|
Field checks 3 through 9 run in the fixed order `pr_url`, `issue_num`, `branch_name`, `authorized_by`, `authorized_at`, `basis`, `run_slug`, `session_id`; the first failure wins and the gate denies with `STANDALONE_MERGE_AUTHORIZATION_MALFORMED` naming that field.
|
|
148
226
|
|
|
227
|
+
## Issue-Adoption Scope and Backward Compatibility
|
|
228
|
+
|
|
229
|
+
These invariants apply only when a checkpoint contains a top-level `issue_adoption` key. The key records that the orchestration adopted a GitHub issue that already existed before orchestration started (transferred, filed by hand, or created by epic decomposition), so the issue was never created through `potential_to_issue`. The key is presence-gated: a checkpoint without it validates byte-identically to before and produces no new errors. It is evaluated only by the routing-contract completion check (`validate_routing_contract` and its TypeScript and PowerShell ports). It is not a member of `REQUIRED_STATE_KEYS`, it is not read by plain (non-completion) validation, and it is not read by the PR-creation-readiness gate. No JSON Schema file is authored, imported, or read for it.
|
|
230
|
+
|
|
231
|
+
Honest disclosure: the validators perform no network I/O. They cannot confirm that the issue exists, so `evidence` and `verified_via` are an auditable declaration of the read-only verification the orchestrator performed, not proof of it. This matches the `standalone_merge_authorizations` precedent above: the record is a policy-level declaration, not a security control.
|
|
232
|
+
|
|
233
|
+
## Invariants (issue_adoption object)
|
|
234
|
+
|
|
235
|
+
The object has the shape `{ issue_num, issue_url, origin, verified_via, verified_at, evidence, waived_tools, potential_record? }`.
|
|
236
|
+
|
|
237
|
+
| Field | Rule |
|
|
238
|
+
| --- | --- |
|
|
239
|
+
| `issue_num` | A string of decimal digits without a leading zero; equal to the checkpoint `issue-num` when that is a string. |
|
|
240
|
+
| `issue_url` | A string ending with `/issues/` followed by `issue_num`. |
|
|
241
|
+
| `origin` | One of `transferred`, `filed_before_orchestration`, `epic_decomposition`. |
|
|
242
|
+
| `verified_via` | One of `gh_issue_view`, `gh_api_get`, `github_mcp_issue_read`. |
|
|
243
|
+
| `verified_at` | Present, not `null`, and not a blank string. |
|
|
244
|
+
| `evidence` | A string with non-whitespace content. |
|
|
245
|
+
| `waived_tools` | A non-empty list of non-blank tool names that includes `potential_to_issue`. |
|
|
246
|
+
| `potential_record` | Required when waiving a promotion-entry tool: a path under `docs/features/potential/` ending in `.md`. |
|
|
247
|
+
|
|
248
|
+
The rules run in this order, and their errors accumulate:
|
|
249
|
+
|
|
250
|
+
1. A present value that is not an object (including `null`) yields `Checkpoint issue_adoption must be an object when present.` and evaluation stops.
|
|
251
|
+
2. `issue_num` must be a string of decimal digits without a leading zero (a JSON number is invalid). Only when it is valid and the checkpoint `issue-num` is a different string, `issue_num` must equal the checkpoint issue-num.
|
|
252
|
+
3. `issue_url` must end with `/issues/` followed by a valid `issue_num`.
|
|
253
|
+
4. `origin` must be one of the three origin values.
|
|
254
|
+
5. `verified_via` must be one of the three verification values.
|
|
255
|
+
6. `verified_at` must be present: absent, `null`, and blank strings are rejected; any other value passes.
|
|
256
|
+
7. `evidence` must be a non-empty string.
|
|
257
|
+
8. `waived_tools` must be a non-empty list of non-blank strings; otherwise no further rule-8 or rule-9 check runs. Each entry, in list order, receives the first applicable error of: listed more than once; not in the closed waivable set; not required by the selected route after promotion-type resolution; already holding a successful MCP receipt. The list must include `potential_to_issue`.
|
|
258
|
+
9. Only when the rule-8 shape check passed: each distinct waived promotion-entry tool (`new_potential_entry` or `new_potential_bug_entry`) requires a valid `potential_record`.
|
|
259
|
+
|
|
260
|
+
The closed waivable set is `potential_to_issue`, `new_potential_entry`, and `new_potential_bug_entry`. `new_active_feature_folder`, `collect_pr_context`, `validate_orchestration_artifacts`, and every other tool can never be waived. All comparisons are ordinal and case-sensitive.
|
|
261
|
+
|
|
262
|
+
Fail-closed behavior: any adoption error waives nothing, so each missing receipt is still reported as `Checkpoint missing successful MCP receipt: <tool>.`. Adoption errors are placed after the receipt-loop errors and before the `local_execution_overrides` errors. The waiver affects only receipt presence: the declared `required_mcp_tools` equality check with the routing matrix is unchanged, so a checkpoint must still declare every route tool it waives.
|
|
263
|
+
|
|
149
264
|
## Enforcement
|
|
150
265
|
|
|
151
266
|
- `scripts/dev_tools/validate_orchestrator_state.py` appends one error per violated invariant when a `remediation_loop` is present, using the existing validator message style (literal, checkpoint-context prefixed). The validator returns a list of error strings and does not mutate its input.
|
|
@@ -157,3 +272,4 @@ Field checks 3 through 9 run in the fixed order `pr_url`, `issue_num`, `branch_n
|
|
|
157
272
|
- The standalone-merge authorization invariants are enforced by the `PreToolUse` merge gate (`.claude/hooks/enforce-epic-merge-gate.ps1` with its dot-sourced helpers file, and `.codex/hooks/enforce-epic-merge-gate.ps1`) at merge time. The Python checkpoint validator
|
|
158
273
|
does not currently validate standalone_merge_authorizations
|
|
159
274
|
entries; adding that check is recorded as follow-up FU-3.
|
|
275
|
+
- The `issue_adoption` invariants are enforced at completion by the routing-contract check in all three runtimes: `scripts/dev_tools/_orchestrator_state_issue_adoption.py` (Python authority), `extensions/drm-copilot/src/lib/validate/orchestrator-state-issue-adoption.ts`, and `.claude/lib/orchestrator-state/OrchestratorStateIssueAdoption.psm1`, with byte-identical error strings pinned by the shared corpus under `tests/fixtures/orchestrator_state_issue_adoption/`.
|
|
@@ -611,6 +611,27 @@ in `tests/scripts/dev_tools/test_blast_radius_config_parity.py`, mirrored in
|
|
|
611
611
|
union of both copies' top-level keys is exhaustively covered by the three declared classes, and
|
|
612
612
|
an unclassified key or a key present in only one copy fails loudly and names itself.
|
|
613
613
|
|
|
614
|
+
**A destination records its own entries in `config/blast-radius.local.json` (issue #508).** The
|
|
615
|
+
push-down regenerates a destination's `config/blast-radius.json` on every push, so an entry
|
|
616
|
+
hand-added to that file does not survive the next push. The supported extension point is the
|
|
617
|
+
destination-owned overlay `config/blast-radius.local.json`. No payload contains it, and the
|
|
618
|
+
push-down never writes or publishes it: both implementations list it in `EXCLUDED_RELATIVE_PATHS`.
|
|
619
|
+
At push time the overlay is composed onto the freshly generated base document (the derived document
|
|
620
|
+
in TypeScript, the published document in Python), and the composed result is written to
|
|
621
|
+
`config/blast-radius.json`, so consumers continue to read one file. Per-key semantics: the
|
|
622
|
+
string-list keys `shared_surfaces`, `shared_surface_globs`, `mandate_reads`, `mergeable_paths`, and
|
|
623
|
+
`path_roots` take an ordered union (base entries first, then overlay-only entries, duplicates
|
|
624
|
+
removed); an overlay module absent from the base is added and an overlay module with the same name
|
|
625
|
+
replaces that module's glob list, with module names emitted in ordinal order; `conflict_tolerance`
|
|
626
|
+
merges recursively (nested lists by union, nested objects per member, nested scalars overlay-wins);
|
|
627
|
+
the scalars `over_breadth_fraction` and `write_intent_extraction` take the overlay value; overlay-only
|
|
628
|
+
keys are appended after the base keys. An overlay `version` must equal the base `version`, and the
|
|
629
|
+
forbidden-glob guard (`**`, `docs/**`, `tests/**`) applies to the composed module map, including
|
|
630
|
+
overlay-authored modules. A malformed overlay, a `version` mismatch, or a forbidden glob raises an
|
|
631
|
+
error before any write, so `config/blast-radius.json` keeps its prior bytes. With no overlay
|
|
632
|
+
present, the written file is byte-identical to the base document. The overlay cannot remove a
|
|
633
|
+
shipped surface or a derived module.
|
|
634
|
+
|
|
614
635
|
## Enforcement
|
|
615
636
|
|
|
616
637
|
- `scripts/dev_tools/validate_parallel_orchestrator_state.py`, with the helper modules `scripts/dev_tools/_parallel_state_common.py`, `scripts/dev_tools/_parallel_state_structures.py`, and `scripts/dev_tools/_parallel_state_records.py`, appends one error per violated orchestrator invariant. The completion-gate invariants 20 and 21 run only when the caller passes `require_complete=True`.
|
|
@@ -6,7 +6,7 @@ description: Module rigor tier system and uniform coverage thresholds.
|
|
|
6
6
|
|
|
7
7
|
# Module Rigor Tiers
|
|
8
8
|
|
|
9
|
-
This rule defines the T1–T4 module rigor tier system used by all CI gates in this repository. The tier system source of truth
|
|
9
|
+
This rule defines the T1–T4 module rigor tier system used by all CI gates in this repository. The tier definitions and gate matrix in this document are the tier system's source of truth; the file `quality-tiers.yml` at the repository root maps every project to a tier. Adding a project without a tier classification fails CI.
|
|
10
10
|
|
|
11
11
|
## Tiers
|
|
12
12
|
|
package/resources/claude-customizations/.claude/skills/acceptance-criteria-tracking/SKILL.md
CHANGED
|
@@ -70,6 +70,14 @@ During plan execution, after completing a task whose work satisfies an acceptanc
|
|
|
70
70
|
|
|
71
71
|
Timing: Check off AC items as soon as the corresponding plan task passes verification — do not defer all AC updates to the end of plan execution.
|
|
72
72
|
|
|
73
|
+
### CI-Dependent Criteria
|
|
74
|
+
|
|
75
|
+
A CI-dependent criterion is one whose verification requires the result of CI on the PR head. It cannot be verified before the pull request exists, so it stays unchecked and is listed as pending-CI in the AC verification artifact until CI has run.
|
|
76
|
+
|
|
77
|
+
- Owner: the item's own orchestrator run owns the check-off and performs it at S9; this is the one case in which an orchestrator run checks off AC. A parent or coordinating session never commits the check-off from its own root.
|
|
78
|
+
- Timing: after the CI green gate (S9) records `ci_gate.conclusion` as `success`, and before the run reports DONE.
|
|
79
|
+
- Push and re-verify: the check-off is committed in the item's own worktree and pushed to the PR branch, and the CI green gate is re-run against the new head SHA so that `ci_gate.head_sha` equals the final PR head before DONE.
|
|
80
|
+
|
|
73
81
|
### When Reviewers Check Off AC
|
|
74
82
|
|
|
75
83
|
During feature review (feature-audit phase):
|
|
@@ -212,6 +212,8 @@ Rules:
|
|
|
212
212
|
- **Observe a command's success-case output before asserting over that output (mandatory).** Run the command, or read a recorded run of it, and confirm the value you intend to assert is actually printed on a *successful* run. Do not infer it from the tool's documentation, from the plan, or from what the tool prints on failure. This requirement exists because the class of defect it prevents is not detectable by reading: an assertion on a line the tool prints only when it changed something is unsatisfiable on a clean run; a demand for a zero-diagnostic count from a tool that returns only an ok flag names a value with no source; a demand for separate line and branch percentages from a coverage run that prints one combined column reads two numbers that are never printed. G7 and G9 cover two decidable slices of this class. The remainder is covered by this requirement and by nothing else, so a reviewer cannot rely on the gate to catch it.
|
|
213
213
|
- **Fix the evidence in the plan; never leave the executor to select it.** An executor free to choose the evidence it is judged against cannot fail. Do not write an acceptance condition that asks the executor to identify "the known-genuine pair", to choose a survivor list, or to pick any suitable instance. Name the instances, or state the mechanical derivation that produces them, so a third party re-running it obtains the same set. This is authoring guidance and not a rule: a validator rule that scanned acceptance text for selection vocabulary was proposed and rejected, because that vocabulary is ordinary plan prose used in roles carrying no selection semantics and a keyword scan over prose is not statically decidable for the property it would claim to detect. The judgment is the author's, and it is not automated. `.claude/rules/plan-acceptance-gates.md` records the rejection and the reason for it.
|
|
214
214
|
- **Check that the task-ordering does not make the condition unsatisfiable.** No rule covers this. A gate that runs a test path containing deliberately-failing cases added by an earlier task, before the later task that makes them pass, cannot exit 0. A baseline captured after a write-mode formatter has already repaired pre-existing drift becomes either a blanket waiver or makes a later gate unsatisfiable. Read each acceptance condition against the state the plan will actually be in when its task runs.
|
|
215
|
+
- **Name the runner and job for a Pester acceptance criterion.** CI runs Pester in two jobs of `.github/workflows/_poshqc.yml`: `poshqc / PowerShell QC` runs every suite with coverage on `windows-latest`, and `poshqc / PowerShell hook suites (Linux)` runs only `tests/scripts/claude-hooks` and `tests/scripts/codex-hooks` on `ubuntu-latest`, with coverage disabled. A Pester acceptance criterion may cite `ubuntu-latest` only for suites under `tests/scripts/claude-hooks` or `tests/scripts/codex-hooks`, never for coverage, and must name the job it relies on (`poshqc / PowerShell QC` or `poshqc / PowerShell hook suites (Linux)`).
|
|
216
|
+
- **Do not assert a fixed-string search literal that contains a backslash.** Git for Windows grep 3.0 reads a doubled backslash in a `grep -F` pattern as one backslash, so the same check returns a different count locally than on a CI runner and cannot pass in both places. Assert a backslash-free substring of the literal, a named test, or a hash or byte-identity check instead.
|
|
215
217
|
|
|
216
218
|
## Plan-Path Continuity Contract (Mandatory)
|
|
217
219
|
|
|
@@ -128,9 +128,17 @@ The script emits pipe-delimited, `LC_ALL=C`-ordered records, one per line:
|
|
|
128
128
|
per-file outcome is reported by the companion `ACTION|preserve-stage|...` record.
|
|
129
129
|
- `ORPHAN_DIR|<path>|<size>` — a directory under a worktree-tracking root that carries
|
|
130
130
|
no `.git` pointer file and no `git worktree list` entry. `<size>` is best-effort and
|
|
131
|
-
may be the literal `unknown`. The
|
|
132
|
-
|
|
133
|
-
|
|
131
|
+
may be the literal `unknown`. The scanned roots are the default pair
|
|
132
|
+
`<main>/.claude/worktrees` and `<main>-wt`, or the `CLEANUP_WT_ORPHAN_ROOTS` entries
|
|
133
|
+
when that variable is set (the override replaces the default pair), plus the parent
|
|
134
|
+
directory of every non-main registered worktree, which is always added. A derived
|
|
135
|
+
parent is skipped when it is the main worktree or one of its ancestors, or is equal to
|
|
136
|
+
or inside a registered worktree. `CLEANUP_WT_ORPHAN_ROOTS` entries are separated by a
|
|
137
|
+
semicolon, a newline, or a colon; a colon that follows a single drive letter and
|
|
138
|
+
precedes `/` or `\` is part of the path, and empty or relative entries are dropped.
|
|
139
|
+
The record is advisory: it reports the directory, and nothing in apply mode acts on
|
|
140
|
+
it. For the disposition, see the Dirty Worktree Triage Procedure's step 7, which
|
|
141
|
+
governs how an orphaned directory is handled.
|
|
134
142
|
- `STALE_REF|<refname>` — a `refs/remotes/<name>/*` ref whose `<name>` is not a
|
|
135
143
|
configured remote, named in full ref form. Advisory only; no ref is ever pruned by
|
|
136
144
|
this tool.
|
|
@@ -139,8 +139,17 @@ Environment overrides:
|
|
|
139
139
|
nonexistent value is treated as missing (falls back to
|
|
140
140
|
the bundled cleanup_worktrees_scan_helper.sh). This is
|
|
141
141
|
the filesystem-scan test-stub seam.
|
|
142
|
-
CLEANUP_WT_ORPHAN_ROOTS
|
|
143
|
-
|
|
142
|
+
CLEANUP_WT_ORPHAN_ROOTS Worktree-tracking roots scanned for ORPHAN_DIR and
|
|
143
|
+
WARN|registration-lost. Entries are separated by a
|
|
144
|
+
semicolon, a newline, or a colon; a colon that follows
|
|
145
|
+
a single drive letter and precedes / or \ is part of
|
|
146
|
+
the path (C:/x). Empty and relative entries are
|
|
147
|
+
dropped. When set, it replaces the default pair
|
|
148
|
+
<main>/.claude/worktrees and <main>-wt. The parent of
|
|
149
|
+
every non-main registered worktree is always added,
|
|
150
|
+
except a parent that is the main worktree or its
|
|
151
|
+
ancestor, or is equal to or inside a registered
|
|
152
|
+
worktree.
|
|
144
153
|
CLEANUP_WT_STUB_SCENARIO Scenario directory consumed by the checked-in git and
|
|
145
154
|
scan stubs (tests only).
|
|
146
155
|
CLEANUP_WT_CONSOLIDATION_PATH Override the derived consolidation worktree path
|
|
@@ -4,16 +4,19 @@
|
|
|
4
4
|
# every file within the 500-line cap. Provides the git-binary test seam
|
|
5
5
|
# (cleanup_wt_git), branch/worktree enumeration (enumerate_branches,
|
|
6
6
|
# parse_worktree_list), path normalization (normalize_wt_path), the
|
|
7
|
-
# current-worktree/branch and base-branch protection set (compute_protected),
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
7
|
+
# current-worktree/branch and base-branch protection set (compute_protected), the
|
|
8
|
+
# main-freshness warning (check_main_freshness), the shared absolute-path predicate
|
|
9
|
+
# (cleanup_wt_is_absolute_path), and the worktree-tracking scan roots
|
|
10
|
+
# (cleanup_wt_split_roots, cleanup_wt_derive_scan_roots, cleanup_wt_scan_roots). The
|
|
11
|
+
# classification ladder, consolidation, deletion, and the CLI live in sibling files
|
|
12
|
+
# (cleanup_worktrees_lib.sh, cleanup_worktrees_actions_lib.sh, cleanup-worktrees.sh).
|
|
11
13
|
#
|
|
12
14
|
# Sourcing contract: defines functions and the single constant CLEANUP_WT_BASE_BRANCH
|
|
13
15
|
# and runs no other work at source time, so wrapper and bats suites source it safely.
|
|
14
16
|
# It MUST be sourced before cleanup_worktrees_lib.sh, whose classification functions
|
|
15
17
|
# call cleanup_wt_git, parse_worktree_list, compute_protected, and normalize_wt_path
|
|
16
|
-
# defined here.
|
|
18
|
+
# defined here. The filesystem-scan helper cleanup_worktrees_scan_helper.sh runs as a
|
|
19
|
+
# separate process and sources this library directly for cleanup_wt_is_absolute_path.
|
|
17
20
|
#
|
|
18
21
|
# All git commands go through cleanup_wt_git so tests can stub the git binary via
|
|
19
22
|
# CLEANUP_WT_GIT_BIN. Git exit-code capture rule: the `git worktree list --porcelain`
|
|
@@ -250,3 +253,164 @@ check_main_freshness() {
|
|
|
250
253
|
fi
|
|
251
254
|
return 0
|
|
252
255
|
}
|
|
256
|
+
|
|
257
|
+
cleanup_wt_is_absolute_path() {
|
|
258
|
+
# Return 0 when <path> is absolute, else 1. Pure: no filesystem access.
|
|
259
|
+
#
|
|
260
|
+
# Absolute forms are a leading `/` (POSIX paths, MSYS `/c/...` paths, and `//server`
|
|
261
|
+
# UNC paths) and a drive letter followed by `/` or `\` (`C:/...`, `c:/...`,
|
|
262
|
+
# `C:\...`), which is the form Git for Windows writes into a worktree's `.git`
|
|
263
|
+
# pointer file (issue #706). A drive letter with no separator (`C:rel`) is
|
|
264
|
+
# drive-relative and is not absolute; an empty path is not absolute. This is the one
|
|
265
|
+
# absolute-path predicate of the skill: the preserve library and the filesystem-scan
|
|
266
|
+
# helper both call it.
|
|
267
|
+
#
|
|
268
|
+
# Args: $1 = path. Returns 0 (absolute) or 1 (not absolute).
|
|
269
|
+
local path=${1:-}
|
|
270
|
+
[[ $path == /* || $path == [A-Za-z]:[/\\]* ]]
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
cleanup_wt_split_roots() {
|
|
274
|
+
# Split a CLEANUP_WT_ORPHAN_ROOTS value into absolute roots, one per line.
|
|
275
|
+
#
|
|
276
|
+
# Entries are separated by `;`, by a newline, or by `:`. A `:` is part of the path
|
|
277
|
+
# instead when the text before it in the current entry is exactly one ASCII letter
|
|
278
|
+
# and the character after it is `/` or `\` (a drive letter, as in `C:/x`). Empty
|
|
279
|
+
# entries are dropped. An entry that is not absolute (cleanup_wt_is_absolute_path) is
|
|
280
|
+
# dropped with a one-line stderr diagnostic naming it, because a relative root would
|
|
281
|
+
# be resolved against the process's current working directory.
|
|
282
|
+
#
|
|
283
|
+
# The value is walked one character at a time with parameter expansion and never
|
|
284
|
+
# word-split, so no pathname expansion occurs and a `*` is emitted literally.
|
|
285
|
+
#
|
|
286
|
+
# Args: $1 = the override value. Always returns 0.
|
|
287
|
+
local value=${1:-}
|
|
288
|
+
local -a entries=()
|
|
289
|
+
local entry="" ch next i
|
|
290
|
+
for ((i = 0; i < ${#value}; i++)); do
|
|
291
|
+
ch=${value:i:1}
|
|
292
|
+
if [[ $ch == ';' || $ch == $'\n' ]]; then
|
|
293
|
+
entries+=("$entry")
|
|
294
|
+
entry=""
|
|
295
|
+
elif [[ $ch == ':' ]]; then
|
|
296
|
+
next=${value:i+1:1}
|
|
297
|
+
if [[ ${#entry} -eq 1 && $entry == [A-Za-z] && $next == [/\\] ]]; then
|
|
298
|
+
entry+=$ch
|
|
299
|
+
else
|
|
300
|
+
entries+=("$entry")
|
|
301
|
+
entry=""
|
|
302
|
+
fi
|
|
303
|
+
else
|
|
304
|
+
entry+=$ch
|
|
305
|
+
fi
|
|
306
|
+
done
|
|
307
|
+
entries+=("$entry")
|
|
308
|
+
for entry in "${entries[@]}"; do
|
|
309
|
+
[[ -z $entry ]] && continue
|
|
310
|
+
if ! cleanup_wt_is_absolute_path "$entry"; then
|
|
311
|
+
printf 'cleanup-worktrees: CLEANUP_WT_ORPHAN_ROOTS entry is not absolute, dropped: %s\n' "$entry" >&2
|
|
312
|
+
continue
|
|
313
|
+
fi
|
|
314
|
+
printf '%s\n' "$entry"
|
|
315
|
+
done
|
|
316
|
+
return 0
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
cleanup_wt_derive_scan_roots() {
|
|
320
|
+
# Echo the registration-derived scan roots, one per line.
|
|
321
|
+
#
|
|
322
|
+
# Input is parse_worktree_list output; its first record is the main worktree. The
|
|
323
|
+
# candidate for every later record is the parent directory of its path (backslashes
|
|
324
|
+
# converted to `/`). A candidate is kept only when its normalize_wt_path value N:
|
|
325
|
+
# - is not the main worktree's normalized path and is not one of its ancestors
|
|
326
|
+
# (scanning an ancestor would list the main worktree's siblings), and
|
|
327
|
+
# - is not equal to, and not inside, any registered worktree, main included
|
|
328
|
+
# (its subdirectories are that worktree's own content, not worktrees).
|
|
329
|
+
# Kept candidates are emitted in LC_ALL=C order of N, once per N, using the first
|
|
330
|
+
# spelling seen in record order.
|
|
331
|
+
#
|
|
332
|
+
# Args: $1 = parse_worktree_list output. Always returns 0.
|
|
333
|
+
local records=${1:-}
|
|
334
|
+
local -a paths=() norms=()
|
|
335
|
+
local record path
|
|
336
|
+
while IFS= read -r record; do
|
|
337
|
+
[[ -z $record ]] && continue
|
|
338
|
+
path=${record%%|*}
|
|
339
|
+
paths+=("$path")
|
|
340
|
+
norms+=("$(normalize_wt_path "$path")")
|
|
341
|
+
done <<<"$records"
|
|
342
|
+
((${#paths[@]} < 2)) && return 0
|
|
343
|
+
local main_norm=${norms[0]}
|
|
344
|
+
local -A seen=()
|
|
345
|
+
local -a kept=()
|
|
346
|
+
local i p parent n w inside
|
|
347
|
+
for ((i = 1; i < ${#paths[@]}; i++)); do
|
|
348
|
+
p=${paths[i]//\\//}
|
|
349
|
+
parent=${p%/*}
|
|
350
|
+
[[ -z $parent || $parent == "$p" ]] && continue
|
|
351
|
+
n=$(normalize_wt_path "$parent")
|
|
352
|
+
[[ -z $n || -n ${seen[$n]:-} ]] && continue
|
|
353
|
+
[[ $n == "$main_norm" || $main_norm == "$n"/* ]] && continue
|
|
354
|
+
inside=0
|
|
355
|
+
for w in "${norms[@]}"; do
|
|
356
|
+
[[ -z $w ]] && continue
|
|
357
|
+
if [[ $n == "$w" || $n == "$w"/* ]]; then
|
|
358
|
+
inside=1
|
|
359
|
+
break
|
|
360
|
+
fi
|
|
361
|
+
done
|
|
362
|
+
((inside == 1)) && continue
|
|
363
|
+
seen[$n]=1
|
|
364
|
+
kept+=("$n|$parent")
|
|
365
|
+
done
|
|
366
|
+
((${#kept[@]} == 0)) && return 0
|
|
367
|
+
local line
|
|
368
|
+
while IFS= read -r line; do
|
|
369
|
+
printf '%s\n' "${line#*|}"
|
|
370
|
+
done < <(printf '%s\n' "${kept[@]}" | LC_ALL=C sort -t '|' -k1,1)
|
|
371
|
+
return 0
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
cleanup_wt_scan_roots() {
|
|
375
|
+
# Echo the worktree-tracking roots to scan, one per line.
|
|
376
|
+
#
|
|
377
|
+
# Configured roots come first: the CLEANUP_WT_ORPHAN_ROOTS entries
|
|
378
|
+
# (cleanup_wt_split_roots) when that variable is set, otherwise the default pair
|
|
379
|
+
# derived from the main worktree path (the first `git worktree list --porcelain`
|
|
380
|
+
# stanza, the same derivation consolidation_worktree_path uses in
|
|
381
|
+
# cleanup_worktrees_actions_lib.sh): `<main>/.claude/worktrees` then `<main>-wt`.
|
|
382
|
+
# Registration-derived roots (cleanup_wt_derive_scan_roots) are always appended.
|
|
383
|
+
# The combined list is deduplicated by normalize_wt_path, keeping the first spelling.
|
|
384
|
+
#
|
|
385
|
+
# The listing is read once (`out=$(...) || rc=$?`). On a parse_worktree_list hard
|
|
386
|
+
# failure nothing is derived from it: with no override no root is emitted at all,
|
|
387
|
+
# rather than a bare relative path resolved against the current working directory,
|
|
388
|
+
# and with an override exactly the override roots are emitted.
|
|
389
|
+
# cleanup_wt_scan_records returns 0 with no record for an empty root list, so the
|
|
390
|
+
# advisory records degrade to silence rather than to a misleading scan.
|
|
391
|
+
# Always returns 0.
|
|
392
|
+
local out rc=0 first_record main_wt="" configured="" derived=""
|
|
393
|
+
out=$(parse_worktree_list) || rc=$?
|
|
394
|
+
if ((rc == 0)); then
|
|
395
|
+
first_record=${out%%$'\n'*}
|
|
396
|
+
main_wt=${first_record%%|*}
|
|
397
|
+
derived=$(cleanup_wt_derive_scan_roots "$out")
|
|
398
|
+
fi
|
|
399
|
+
if [[ -n ${CLEANUP_WT_ORPHAN_ROOTS:-} ]]; then
|
|
400
|
+
configured=$(cleanup_wt_split_roots "$CLEANUP_WT_ORPHAN_ROOTS")
|
|
401
|
+
elif [[ -n $main_wt ]]; then
|
|
402
|
+
configured="${main_wt}/.claude/worktrees"$'\n'"${main_wt}-wt"
|
|
403
|
+
fi
|
|
404
|
+
local -A seen=()
|
|
405
|
+
local root n
|
|
406
|
+
while IFS= read -r root; do
|
|
407
|
+
[[ -z $root ]] && continue
|
|
408
|
+
n=$(normalize_wt_path "$root")
|
|
409
|
+
# A root of `/` normalizes to empty, which is not a valid array key.
|
|
410
|
+
[[ -z $n ]] && n=/
|
|
411
|
+
[[ -n ${seen[$n]:-} ]] && continue
|
|
412
|
+
seen[$n]=1
|
|
413
|
+
printf '%s\n' "$root"
|
|
414
|
+
done <<<"${configured}"$'\n'"${derived}"
|
|
415
|
+
return 0
|
|
416
|
+
}
|
|
@@ -158,7 +158,7 @@ preserve_relative_path_reason() {
|
|
|
158
158
|
local name=${1-} val=${2-}
|
|
159
159
|
if [[ -z $val ]]; then
|
|
160
160
|
printf '%s is empty' "$name"
|
|
161
|
-
elif
|
|
161
|
+
elif cleanup_wt_is_absolute_path "$val"; then
|
|
162
162
|
printf '%s is absolute: %s' "$name" "$val"
|
|
163
163
|
elif [[ $val == ".." || $val == "../"* || $val == *"/../"* || $val == *"/.." ]]; then
|
|
164
164
|
printf '%s contains a .. segment: %s' "$name" "$val"
|
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
# whose gitdir pointer no longer resolves, and the ancestor relationship between two
|
|
7
7
|
# branches that both resolve NOT_MERGED. The enumeration/protection seam
|
|
8
8
|
# (cleanup_wt_git, enumerate_branches, parse_worktree_list, normalize_wt_path,
|
|
9
|
-
# compute_protected, check_main_freshness) lives in
|
|
10
|
-
# and the classification ladder (classify_branch)
|
|
11
|
-
# both MUST be sourced before this file. Keeping
|
|
12
|
-
# every file within the 500-line cap.
|
|
9
|
+
# compute_protected, check_main_freshness, cleanup_wt_scan_roots) lives in
|
|
10
|
+
# cleanup_worktrees_enumerate_lib.sh and the classification ladder (classify_branch)
|
|
11
|
+
# lives in cleanup_worktrees_lib.sh; both MUST be sourced before this file. Keeping
|
|
12
|
+
# these records in a sibling file keeps every file within the 500-line cap.
|
|
13
13
|
#
|
|
14
14
|
# Sourcing contract: this library defines functions only; it never runs work at source
|
|
15
15
|
# time, so the wrapper and the bats suites can source it without side effects.
|
|
@@ -111,47 +111,10 @@ scan_stale_refs() {
|
|
|
111
111
|
return 0
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
cleanup_wt_scan_roots() {
|
|
115
|
-
# Echo the worktree-tracking roots to scan, one per line.
|
|
116
|
-
#
|
|
117
|
-
# CLEANUP_WT_ORPHAN_ROOTS overrides the derivation with a colon-separated list.
|
|
118
|
-
# Otherwise BOTH roots derive from the main worktree path — the first
|
|
119
|
-
# `git worktree list --porcelain` stanza, the same derivation
|
|
120
|
-
# consolidation_worktree_path uses in cleanup_worktrees_actions_lib.sh. They are
|
|
121
|
-
# `<main-worktree-path>` joined with `.claude/worktrees` (the agent worktree folder)
|
|
122
|
-
# and `<main-worktree-path>-wt` (the sibling worktree folder), in that order.
|
|
123
|
-
#
|
|
124
|
-
# Deriving both from one read means a parse_worktree_list hard failure emits no root
|
|
125
|
-
# at all, rather than a bare relative path that would be resolved against whatever
|
|
126
|
-
# the process's current working directory happened to be. cleanup_wt_scan_records
|
|
127
|
-
# returns 0 with no record for an empty root list, so the advisory records degrade to
|
|
128
|
-
# silence rather than to a misleading scan. Always returns 0.
|
|
129
|
-
local override=${CLEANUP_WT_ORPHAN_ROOTS:-}
|
|
130
|
-
if [[ -n $override ]]; then
|
|
131
|
-
local IFS=:
|
|
132
|
-
local part
|
|
133
|
-
for part in $override; do
|
|
134
|
-
[[ -n $part ]] && printf '%s\n' "$part"
|
|
135
|
-
done
|
|
136
|
-
return 0
|
|
137
|
-
fi
|
|
138
|
-
local out rc=0 first_record main_wt=""
|
|
139
|
-
out=$(parse_worktree_list) || rc=$?
|
|
140
|
-
if ((rc != 0)); then
|
|
141
|
-
return 0
|
|
142
|
-
fi
|
|
143
|
-
first_record=${out%%$'\n'*}
|
|
144
|
-
main_wt=${first_record%%|*}
|
|
145
|
-
if [[ -n $main_wt ]]; then
|
|
146
|
-
printf '%s\n' "${main_wt}/.claude/worktrees"
|
|
147
|
-
printf '%s\n' "${main_wt}-wt"
|
|
148
|
-
fi
|
|
149
|
-
return 0
|
|
150
|
-
}
|
|
151
|
-
|
|
152
114
|
cleanup_wt_scan_records() {
|
|
153
115
|
# Echo the raw `<path>|<has_gitfile>|<gitdir_target_exists>|<size>` records for every
|
|
154
|
-
# candidate directory under the resolved scan roots, one per line.
|
|
116
|
+
# candidate directory under the resolved scan roots, one per line. The roots come
|
|
117
|
+
# from cleanup_wt_scan_roots in cleanup_worktrees_enumerate_lib.sh.
|
|
155
118
|
#
|
|
156
119
|
# Every filesystem read goes through the CLEANUP_WT_SCAN_BIN seam, so the bats suites
|
|
157
120
|
# replay canned records instead of touching the real filesystem. The scan output is
|
|
@@ -40,6 +40,9 @@
|
|
|
40
40
|
# the SHELL_QC_<TOOL>_BIN seams substitute a checked-in stand-in for a real dependency.
|
|
41
41
|
# An empty value is treated as missing and falls back to `.git`.
|
|
42
42
|
set -euo pipefail
|
|
43
|
+
# shellcheck source=.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_enumerate_lib.sh
|
|
44
|
+
# shellcheck disable=SC1091
|
|
45
|
+
source "$(dirname -- "${BASH_SOURCE[0]}")/cleanup_worktrees_enumerate_lib.sh"
|
|
43
46
|
|
|
44
47
|
scan_helper_dir_size() {
|
|
45
48
|
# Echo the best-effort human-readable size of a directory, or `unknown`.
|
|
@@ -70,20 +73,6 @@ scan_helper_gitfile_name() {
|
|
|
70
73
|
printf '%s\n' ".git"
|
|
71
74
|
}
|
|
72
75
|
|
|
73
|
-
scan_helper_is_absolute_path() {
|
|
74
|
-
# Return 0 when <path> is absolute, else 1. Pure: no filesystem access.
|
|
75
|
-
#
|
|
76
|
-
# Absolute forms are a leading `/` (POSIX paths, MSYS `/c/...` paths, and `//server`
|
|
77
|
-
# UNC paths) and a drive letter followed by `/` or `\` (`C:/...`, `c:/...`,
|
|
78
|
-
# `C:\...`), which is the form Git for Windows writes into a worktree's `.git`
|
|
79
|
-
# pointer file (issue #706). A drive letter with no separator (`C:rel`) is
|
|
80
|
-
# drive-relative and is not absolute; an empty path is not absolute.
|
|
81
|
-
#
|
|
82
|
-
# Args: $1 = path. Returns 0 (absolute) or 1 (not absolute).
|
|
83
|
-
local path=${1:-}
|
|
84
|
-
[[ $path == /* || $path == [A-Za-z]:[/\\]* ]]
|
|
85
|
-
}
|
|
86
|
-
|
|
87
76
|
scan_helper_target_present() {
|
|
88
77
|
# Return 0 when <path> exists, else non-zero. This is the helper's single
|
|
89
78
|
# filesystem existence check, kept in its own function as a test seam: bats tests
|
|
@@ -113,7 +102,7 @@ scan_helper_gitdir_target_exists() {
|
|
|
113
102
|
printf '0\n'
|
|
114
103
|
return 0
|
|
115
104
|
fi
|
|
116
|
-
if !
|
|
105
|
+
if ! cleanup_wt_is_absolute_path "$target"; then
|
|
117
106
|
target="$dir/$target"
|
|
118
107
|
fi
|
|
119
108
|
if scan_helper_target_present "$target"; then
|