@danmoisan/drm-copilot-mcp 1.1.18 → 1.1.19
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 +989 -662
- package/package.json +3 -2
- package/resources/claude-customizations/.claude/agents/atomic-executor.md +11 -0
- package/resources/claude-customizations/.claude/agents/atomic-planner.md +10 -0
- package/resources/claude-customizations/.claude/agents/epic-orchestrator.md +12 -4
- package/resources/claude-customizations/.claude/agents/epic-planner.md +5 -4
- package/resources/claude-customizations/.claude/agents/feature-review.md +6 -4
- package/resources/claude-customizations/.claude/agents/orchestrator.md +5 -4
- package/resources/claude-customizations/.claude/agents/parallel-orchestrator.md +31 -13
- package/resources/claude-customizations/.claude/agents/parallel-planner.md +9 -6
- package/resources/claude-customizations/.claude/agents/pr-author.md +15 -2
- package/resources/claude-customizations/.claude/agents/prd-feature.md +1 -1
- package/resources/claude-customizations/.claude/hooks/check-powershell-test-purity.ps1 +7 -1
- package/resources/claude-customizations/.claude/hooks/check-python-test-purity.ps1 +7 -1
- package/resources/claude-customizations/.claude/hooks/enforce-checkpoint-monotonic.ps1 +7 -1
- package/resources/claude-customizations/.claude/hooks/enforce-completion-consistency.ps1 +111 -46
- package/resources/claude-customizations/.claude/hooks/enforce-completion-helpers.ps1 +105 -0
- package/resources/claude-customizations/.claude/hooks/enforce-discovery-artifact-gate.ps1 +8 -1
- package/resources/claude-customizations/.claude/hooks/enforce-epic-invocation-origin.ps1 +7 -1
- package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate-resolution.ps1 +116 -59
- package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate.ps1 +55 -23
- package/resources/claude-customizations/.claude/hooks/enforce-epic-wave-barrier.ps1 +74 -75
- package/resources/claude-customizations/.claude/hooks/enforce-epic-worktree-removal-gate-resolution.ps1 +127 -35
- package/resources/claude-customizations/.claude/hooks/enforce-epic-worktree-removal-gate.ps1 +61 -57
- package/resources/claude-customizations/.claude/hooks/enforce-evidence-locations.ps1 +7 -1
- package/resources/claude-customizations/.claude/hooks/enforce-feature-folder-order.ps1 +129 -39
- package/resources/claude-customizations/.claude/hooks/enforce-mermaid-validation.ps1 +7 -1
- package/resources/claude-customizations/.claude/hooks/enforce-model-routing-receipt.ps1 +9 -3
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-epic-scope.ps1 +39 -51
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-helpers.ps1 +31 -58
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-modes.ps1 +63 -47
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-targets.ps1 +433 -0
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +17 -12
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-abandon-gate.ps1 +9 -3
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-cohort-barrier.ps1 +65 -50
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-drift-gate.ps1 +49 -42
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-worktree-removal-gate.ps1 +54 -72
- package/resources/claude-customizations/.claude/hooks/enforce-powershell-batch-budget.ps1 +8 -2
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-command-allowlist.ps1 +279 -0
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill-helpers.ps1 +129 -75
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.artifact-root.ps1 +233 -0
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.docs-only.ps1 +224 -0
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.ps1 +69 -24
- package/resources/claude-customizations/.claude/hooks/enforce-prd-feature-before-planner-helpers.ps1 +29 -18
- package/resources/claude-customizations/.claude/hooks/enforce-prd-feature-before-planner.ps1 +10 -4
- package/resources/claude-customizations/.claude/hooks/enforce-promotion-mcp-only.ps1 +14 -5
- package/resources/claude-customizations/.claude/hooks/enforce-python-batch-budget.ps1 +8 -2
- package/resources/claude-customizations/.claude/hooks/feature-folder-resolution.ps1 +373 -0
- package/resources/claude-customizations/.claude/hooks/feature-review-coverage-thresholds.ps1 +85 -0
- package/resources/claude-customizations/.claude/hooks/hook-command-heredoc.ps1 +119 -0
- package/resources/claude-customizations/.claude/hooks/hook-command-invocation-operands.ps1 +198 -0
- package/resources/claude-customizations/.claude/hooks/hook-command-invocation.ps1 +254 -255
- package/resources/claude-customizations/.claude/hooks/hook-command-payload-powershell.ps1 +201 -0
- package/resources/claude-customizations/.claude/hooks/hook-command-payload.ps1 +484 -0
- package/resources/claude-customizations/.claude/hooks/hook-command-scanner.ps1 +27 -110
- package/resources/claude-customizations/.claude/hooks/hook-dependency-guard.ps1 +114 -0
- package/resources/claude-customizations/.claude/hooks/hook-docs-only-diff.ps1 +493 -0
- package/resources/claude-customizations/.claude/hooks/subagentstop-envelope.ps1 +192 -0
- package/resources/claude-customizations/.claude/hooks/validate-bash.ps1 +9 -3
- package/resources/claude-customizations/.claude/hooks/validate-discovery-artifact-gate.ps1 +40 -42
- package/resources/claude-customizations/.claude/hooks/validate-executor-output.ps1 +66 -38
- package/resources/claude-customizations/.claude/hooks/validate-feature-review-coverage-metrics.ps1 +180 -0
- package/resources/claude-customizations/.claude/hooks/validate-feature-review-coverage-rules.ps1 +195 -0
- package/resources/claude-customizations/.claude/hooks/validate-feature-review-coverage.ps1 +141 -395
- package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output-resolution.ps1 +311 -0
- package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output-routing.ps1 +170 -0
- package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output.ps1 +115 -190
- package/resources/claude-customizations/.claude/hooks/validate-planner-output.ps1 +64 -32
- package/resources/claude-customizations/.claude/hooks/validate-pr-author-output.ps1 +39 -44
- package/resources/claude-customizations/.claude/hooks/validate-prd-feature-output.ps1 +43 -14
- package/resources/claude-customizations/.claude/hooks/validate-required-artifact-output.ps1 +66 -36
- package/resources/claude-customizations/.claude/hooks/validate-task-researcher-output.ps1 +31 -34
- package/resources/claude-customizations/.claude/lib/bash/mutate-parallel-run.sh +191 -0
- package/resources/claude-customizations/.claude/lib/bash/parallel-cohorts.sh +13 -3
- package/resources/claude-customizations/.claude/lib/bash/parallel-mutation-cli.sh +152 -0
- package/resources/claude-customizations/.claude/lib/bash/parallel-mutation-ops.sh +193 -0
- package/resources/claude-customizations/.claude/lib/bash/parallel-mutation.sh +322 -0
- package/resources/claude-customizations/.claude/lib/bash/remove-parallel-item.sh +146 -0
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusExtraction.psm1 +23 -33
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusTokenShape.psm1 +86 -6
- package/resources/claude-customizations/.claude/lib/ci-gate/Invoke-CiGateParser.ps1 +91 -19
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateEpicWaveBarrier.psm1 +346 -0
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateIssueAdoption.psm1 +24 -3
- package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeItemResolution.psm1 +79 -2
- package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeRunResolution.psm1 +3 -3
- package/resources/claude-customizations/.claude/rules/architecture-boundaries.md +5 -5
- package/resources/claude-customizations/.claude/rules/csharp.md +2 -2
- package/resources/claude-customizations/.claude/rules/general-code-change.md +1 -1
- package/resources/claude-customizations/.claude/rules/general-unit-test.md +3 -2
- package/resources/claude-customizations/.claude/rules/orchestrator-state.md +37 -4
- package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +50 -5
- package/resources/claude-customizations/.claude/rules/plan-acceptance-gates.md +47 -8
- package/resources/claude-customizations/.claude/rules/quality-tiers.md +11 -7
- package/resources/claude-customizations/.claude/rules/shell.md +10 -8
- package/resources/claude-customizations/.claude/rules/typescript.md +1 -1
- package/resources/claude-customizations/.claude/settings.json +3 -4
- package/resources/claude-customizations/.claude/skills/acceptance-criteria-tracking/SKILL.md +1 -1
- package/resources/claude-customizations/.claude/skills/atomic-plan-contract/SKILL.md +5 -1
- package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_detached_lib.sh +1 -1
- package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_enumerate_lib.sh +2 -0
- package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_scan_helper.sh +2 -0
- package/resources/claude-customizations/.claude/skills/epic-orchestrate/SKILL.md +33 -6
- package/resources/claude-customizations/.claude/skills/epic-plan/SKILL.md +1 -1
- package/resources/claude-customizations/.claude/skills/evidence-and-timestamp-conventions/SKILL.md +10 -1
- package/resources/claude-customizations/.claude/skills/feature-promotion-lifecycle/SKILL.md +1 -1
- package/resources/claude-customizations/.claude/skills/feature-review-workflow/SKILL.md +4 -3
- package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +14 -6
- package/resources/claude-customizations/.claude/skills/parallel-add/SKILL.md +52 -14
- package/resources/claude-customizations/.claude/skills/parallel-close/SKILL.md +22 -9
- package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +79 -16
- package/resources/claude-customizations/.claude/skills/parallel-plan/SKILL.md +63 -7
- package/resources/claude-customizations/.claude/skills/parallel-remove/SKILL.md +35 -12
- package/resources/claude-customizations/.claude/skills/pr-author/SKILL.md +3 -1
- package/resources/claude-customizations/.claude/skills/quota-throttling/SKILL.md +1 -1
- package/resources/claude-customizations/.claude/skills/remediation-handoff-atomic-planner/SKILL.md +2 -2
- package/resources/claude-customizations/pack-manifests/core.json +23 -0
- package/resources/codex-and-agents-customizations/.agents/skills/acceptance-criteria-tracking/SKILL.md +1 -1
- package/resources/codex-and-agents-customizations/.agents/skills/architecture-boundaries/SKILL.md +5 -5
- package/resources/codex-and-agents-customizations/.agents/skills/atomic-executor/SKILL.md +11 -0
- package/resources/codex-and-agents-customizations/.agents/skills/atomic-plan-contract/SKILL.md +4 -0
- package/resources/codex-and-agents-customizations/.agents/skills/atomic-planner/SKILL.md +10 -0
- package/resources/codex-and-agents-customizations/.agents/skills/csharp/SKILL.md +2 -2
- package/resources/codex-and-agents-customizations/.agents/skills/csharp-qa-gate/SKILL.md +2 -2
- package/resources/codex-and-agents-customizations/.agents/skills/epic-orchestrate/SKILL.md +8 -0
- package/resources/codex-and-agents-customizations/.agents/skills/epic-plan/SKILL.md +17 -1
- package/resources/codex-and-agents-customizations/.agents/skills/epic-run/SKILL.md +2 -1
- package/resources/codex-and-agents-customizations/.agents/skills/evidence-and-timestamp-conventions/SKILL.md +10 -1
- package/resources/codex-and-agents-customizations/.agents/skills/feature-review-workflow/SKILL.md +13 -5
- package/resources/codex-and-agents-customizations/.agents/skills/general-code-change/SKILL.md +1 -1
- package/resources/codex-and-agents-customizations/.agents/skills/general-unit-test/SKILL.md +3 -2
- package/resources/codex-and-agents-customizations/.agents/skills/orchestrate/SKILL.md +15 -6
- package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-workflow/SKILL.md +1 -1
- package/resources/codex-and-agents-customizations/.agents/skills/quality-tiers/SKILL.md +11 -7
- package/resources/codex-and-agents-customizations/.agents/skills/translate-claude-to-codex/SKILL.md +1 -1
- package/resources/codex-and-agents-customizations/.agents-variants/csharp-legacy/skills/csharp/SKILL.md +2 -2
- package/resources/codex-and-agents-customizations/.agents-variants/csharp-legacy/skills/csharp-qa-gate/SKILL.md +2 -2
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c1.toml +11 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c2.toml +11 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c3-elevated.toml +11 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c3.toml +11 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c4.toml +11 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor.toml +11 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c1.toml +10 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c2.toml +10 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c3-elevated.toml +10 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c3.toml +10 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c4.toml +10 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner.toml +10 -0
- package/resources/codex-and-agents-customizations/.codex/agents/epic-orchestrator.toml +2 -1
- package/resources/codex-and-agents-customizations/.codex/agents/feature-review.toml +6 -6
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c1.toml +2 -2
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c2.toml +2 -2
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c3-elevated.toml +2 -2
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c3.toml +2 -2
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c4.toml +2 -2
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator.toml +2 -2
- package/resources/codex-and-agents-customizations/.codex/codex-web-setup.sh +318 -273
- package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/check-powershell-test-purity.ps1 +7 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/check-python-test-purity.ps1 +7 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/codex-epic-child-launch-attestation.ps1 +1 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-checkpoint-monotonic.ps1 +7 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-codex-model-routing.ps1 +8 -2
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-completion-consistency.ps1 +97 -44
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-completion-helpers.ps1 +105 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-child-worktree-binding.ps1 +7 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-merge-gate.ps1 +37 -5
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-planning-only.ps1 +6 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-root-invocation.ps1 +7 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-wave-barrier.ps1 +7 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-worktree-removal-gate.ps1 +54 -41
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-evidence-locations.ps1 +7 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-epic-scope.ps1 +35 -14
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-helpers.ps1 +31 -58
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-modes.ps1 +63 -47
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-targets.ps1 +433 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate.ps1 +24 -11
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-powershell-batch-budget.ps1 +8 -2
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-promotion-mcp-only.ps1 +13 -4
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-python-batch-budget.ps1 +8 -2
- package/resources/codex-and-agents-customizations/.codex/hooks/feature-folder-resolution.ps1 +373 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/feature-review-coverage-thresholds.ps1 +85 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-heredoc.ps1 +119 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-invocation-operands.ps1 +198 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-invocation.ps1 +254 -255
- package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-payload-powershell.ps1 +201 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-payload.ps1 +484 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-scanner.ps1 +27 -110
- package/resources/codex-and-agents-customizations/.codex/hooks/hook-dependency-guard.ps1 +114 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/hook-docs-only-diff.ps1 +493 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/validate-bash.ps1 +8 -2
- package/resources/codex-and-agents-customizations/.codex/hooks/validate-codex-subagent-routing.ps1 +7 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/validate-feature-review-coverage.ps1 +24 -8
- package/resources/codex-and-agents-customizations/pack-manifests/core.json +9 -0
- package/resources/customizations/.github/agents/csharp-typed-engineer.agent.md +2 -2
- package/resources/customizations/.github/instructions/csharp-code-change.instructions.md +4 -4
- package/resources/customizations/.github/instructions/csharp-unit-test.instructions.md +2 -2
- package/resources/customizations/.github/skills/acceptance-criteria-tracking/SKILL.md +1 -1
- package/resources/customizations/.github/skills/atomic-plan-contract/SKILL.md +4 -0
- package/resources/customizations/.github/skills/evidence-and-timestamp-conventions/SKILL.md +10 -1
- package/resources/customizations/.github/skills/feature-review-workflow/SKILL.md +1 -1
- package/resources/powershell/PoshQC/PoshQC.Coverage.psm1 +314 -0
- package/resources/powershell/PoshQC/PoshQC.Testing.psm1 +25 -28
- package/resources/powershell/PoshQC/PoshQC.psm1 +1 -0
- package/resources/powershell/PoshQC/README.md +8 -2
- package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +2 -319
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# mutate-parallel-run.sh: destination-portable command-line entry point for the
|
|
3
|
+
# decision and audit-record steps of the parallel-add, parallel-close, and
|
|
4
|
+
# parallel-orchestrate skills (issue #890). It exists so a workspace that
|
|
5
|
+
# received the Claude customization payload can run those steps with nothing
|
|
6
|
+
# but bash -- no interpreter, no repository checkout.
|
|
7
|
+
#
|
|
8
|
+
# Usage:
|
|
9
|
+
# bash .claude/lib/bash/mutate-parallel-run.sh admit --candidate <key> \
|
|
10
|
+
# [--edges "<a>:<b> ..."] --in-flight "<k> ..." \
|
|
11
|
+
# --current-cohort-members "<k> ..."
|
|
12
|
+
# bash .claude/lib/bash/mutate-parallel-run.sh recolor --unstarted "<k> ..." \
|
|
13
|
+
# [--edges "<a>:<b> ..."] --pinned "<k> ..." --generation <g> \
|
|
14
|
+
# --current-cohort <c> --highest-pinned-cohort <h>
|
|
15
|
+
# bash .claude/lib/bash/mutate-parallel-run.sh add-entry --item <key> \
|
|
16
|
+
# --deferred true|false --generation <g> [--at <timestamp>]
|
|
17
|
+
# bash .claude/lib/bash/mutate-parallel-run.sh close --items "<k>:<state> ..."
|
|
18
|
+
# bash .claude/lib/bash/mutate-parallel-run.sh close-entry --generation <g> \
|
|
19
|
+
# [--at <timestamp>]
|
|
20
|
+
# bash .claude/lib/bash/mutate-parallel-run.sh requeue-entry --item <key> \
|
|
21
|
+
# --generation <g> [--at <timestamp>]
|
|
22
|
+
#
|
|
23
|
+
# `--edges` is optional; `--in-flight`, `--current-cohort-members`,
|
|
24
|
+
# `--unstarted`, `--pinned`, and `--items` accept an empty string. Integer
|
|
25
|
+
# tokens match `-?(0|[1-9][0-9]*)`. An `--items` token is an integer, a colon,
|
|
26
|
+
# and a `[A-Za-z_]+` state; a duplicate key is a usage error. `--at` defaults to
|
|
27
|
+
# the current UTC time as yyyy-MM-ddTHH-mm; the clock is read only when `--at`
|
|
28
|
+
# is omitted.
|
|
29
|
+
#
|
|
30
|
+
# Exit-code contract (identical to remove-parallel-item.sh):
|
|
31
|
+
# exit 0 success; one compact JSON object on stdout
|
|
32
|
+
# exit 1 rule rejection; the reference implementation's exact message as
|
|
33
|
+
# one stderr line
|
|
34
|
+
# exit 2 usage error; one stderr line beginning
|
|
35
|
+
# "PARALLEL_MUTATION_ERROR: usage error:"
|
|
36
|
+
# Argument validation runs first, then rule rejection, then output.
|
|
37
|
+
#
|
|
38
|
+
# Parity reference: scripts/dev_tools/parallel_mutation_protocol.py
|
|
39
|
+
# (decide_admission, recolor_unstarted, decide_close) and
|
|
40
|
+
# scripts/dev_tools/_parallel_mutation_entries.py (build_add_entry,
|
|
41
|
+
# build_close_entry, build_requeue_entry).
|
|
42
|
+
#
|
|
43
|
+
# Declared divergences (also listed in both parity lanes):
|
|
44
|
+
# 1. A duplicate key in `close --items` is a usage error (exit 2); the Python
|
|
45
|
+
# mapping would silently keep the last record for that key.
|
|
46
|
+
# 2. `at` is a caller-supplied string instead of a `datetime`.
|
|
47
|
+
# 3. Option abbreviations are rejected.
|
|
48
|
+
# 4. `admit` has no rejection branch, in either implementation.
|
|
49
|
+
set -euo pipefail
|
|
50
|
+
|
|
51
|
+
# Resolve this script's own directory so the libraries source regardless of cwd.
|
|
52
|
+
MPR_SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
|
|
53
|
+
# shellcheck source=.claude/lib/bash/parallel-mutation-ops.sh
|
|
54
|
+
# shellcheck disable=SC1091
|
|
55
|
+
source "$MPR_SCRIPT_DIR/parallel-mutation-ops.sh"
|
|
56
|
+
# shellcheck source=.claude/lib/bash/parallel-mutation-cli.sh
|
|
57
|
+
# shellcheck disable=SC1091
|
|
58
|
+
source "$MPR_SCRIPT_DIR/parallel-mutation-cli.sh"
|
|
59
|
+
|
|
60
|
+
pc_enforce_c_locale
|
|
61
|
+
|
|
62
|
+
mpr_admit() {
|
|
63
|
+
# Run the admit subcommand.
|
|
64
|
+
local candidate edges in_flight members rc=0
|
|
65
|
+
pmcli_parse_options "candidate edges in-flight current-cohort-members" "$@"
|
|
66
|
+
pmcli_require candidate in-flight current-cohort-members
|
|
67
|
+
pmcli_integer "${PMCLI_OPTS[candidate]}" "--candidate"
|
|
68
|
+
candidate="$PMCLI_VALUE"
|
|
69
|
+
pmcli_edge_list "${PMCLI_OPTS[edges]-}"
|
|
70
|
+
edges="$PMCLI_VALUE"
|
|
71
|
+
pmcli_integer_list "${PMCLI_OPTS["in-flight"]}" "--in-flight"
|
|
72
|
+
in_flight="$PMCLI_VALUE"
|
|
73
|
+
pmcli_integer_list "${PMCLI_OPTS["current-cohort-members"]}" "--current-cohort-members"
|
|
74
|
+
members="$PMCLI_VALUE"
|
|
75
|
+
pm_decide_admission "$candidate" "$edges" "$in_flight" "$members" || rc=$?
|
|
76
|
+
pmcli_emit "$rc"
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
mpr_recolor() {
|
|
80
|
+
# Run the recolor subcommand; same options as remove-parallel-item.sh.
|
|
81
|
+
local unstarted edges pinned generation current highest rc=0
|
|
82
|
+
pmcli_parse_options "unstarted edges pinned generation current-cohort highest-pinned-cohort" "$@"
|
|
83
|
+
pmcli_require unstarted pinned generation current-cohort highest-pinned-cohort
|
|
84
|
+
pmcli_integer_list "${PMCLI_OPTS[unstarted]}" "--unstarted"
|
|
85
|
+
unstarted="$PMCLI_VALUE"
|
|
86
|
+
pmcli_edge_list "${PMCLI_OPTS[edges]-}"
|
|
87
|
+
edges="$PMCLI_VALUE"
|
|
88
|
+
pmcli_integer_list "${PMCLI_OPTS[pinned]}" "--pinned"
|
|
89
|
+
pinned="$PMCLI_VALUE"
|
|
90
|
+
pmcli_integer "${PMCLI_OPTS[generation]}" "--generation"
|
|
91
|
+
generation="$PMCLI_VALUE"
|
|
92
|
+
pmcli_integer "${PMCLI_OPTS["current-cohort"]}" "--current-cohort"
|
|
93
|
+
current="$PMCLI_VALUE"
|
|
94
|
+
pmcli_integer "${PMCLI_OPTS["highest-pinned-cohort"]}" "--highest-pinned-cohort"
|
|
95
|
+
highest="$PMCLI_VALUE"
|
|
96
|
+
pm_recolor_unstarted "$unstarted" "$edges" "$pinned" "$generation" "$current" "$highest" || rc=$?
|
|
97
|
+
pmcli_emit "$rc"
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
mpr_add_entry() {
|
|
101
|
+
# Run the add-entry subcommand.
|
|
102
|
+
local item deferred generation at rc=0
|
|
103
|
+
pmcli_parse_options "item deferred generation at" "$@"
|
|
104
|
+
pmcli_require item deferred generation
|
|
105
|
+
pmcli_integer "${PMCLI_OPTS[item]}" "--item"
|
|
106
|
+
item="$PMCLI_VALUE"
|
|
107
|
+
deferred="${PMCLI_OPTS[deferred]}"
|
|
108
|
+
[[ $deferred == true || $deferred == false ]] ||
|
|
109
|
+
pmcli_usage_error "--deferred must be true or false; found: $deferred"
|
|
110
|
+
pmcli_integer "${PMCLI_OPTS[generation]}" "--generation"
|
|
111
|
+
generation="$PMCLI_VALUE"
|
|
112
|
+
pmcli_timestamp
|
|
113
|
+
at="$PMCLI_VALUE"
|
|
114
|
+
pm_build_add_entry "$item" "$deferred" "$generation" "$at" || rc=$?
|
|
115
|
+
pmcli_emit "$rc"
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
mpr_close() {
|
|
119
|
+
# Run the close subcommand over every `<key>:<state>` token.
|
|
120
|
+
local token key state canonical="" seen=" " rc=0
|
|
121
|
+
pmcli_parse_options "items" "$@"
|
|
122
|
+
pmcli_require items
|
|
123
|
+
pcoh_split_words "${PMCLI_OPTS[items]}"
|
|
124
|
+
local -a tokens=("${PCOH_WORDS[@]}")
|
|
125
|
+
for token in "${tokens[@]}"; do
|
|
126
|
+
[[ $token =~ ^(-?(0|[1-9][0-9]*)):([A-Za-z_]+)$ ]] ||
|
|
127
|
+
pmcli_usage_error "--items token must be <key>:<state>; found: $token"
|
|
128
|
+
state="${BASH_REMATCH[3]}"
|
|
129
|
+
key=$((BASH_REMATCH[1]))
|
|
130
|
+
# A duplicate key would let one record silently replace another, so it
|
|
131
|
+
# is rejected before the library runs (declared divergence 1).
|
|
132
|
+
[[ $seen != *" $key "* ]] || pmcli_usage_error "--items key given more than once: $key"
|
|
133
|
+
seen="$seen$key "
|
|
134
|
+
canonical="$canonical $key:$state"
|
|
135
|
+
done
|
|
136
|
+
pm_decide_close "${canonical# }" || rc=$?
|
|
137
|
+
pmcli_emit "$rc"
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
mpr_close_entry() {
|
|
141
|
+
# Run the close-entry subcommand.
|
|
142
|
+
local generation at rc=0
|
|
143
|
+
pmcli_parse_options "generation at" "$@"
|
|
144
|
+
pmcli_require generation
|
|
145
|
+
pmcli_integer "${PMCLI_OPTS[generation]}" "--generation"
|
|
146
|
+
generation="$PMCLI_VALUE"
|
|
147
|
+
pmcli_timestamp
|
|
148
|
+
at="$PMCLI_VALUE"
|
|
149
|
+
pm_build_close_entry "$generation" "$at" || rc=$?
|
|
150
|
+
pmcli_emit "$rc"
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
mpr_requeue_entry() {
|
|
154
|
+
# Run the requeue-entry subcommand.
|
|
155
|
+
local item generation at rc=0
|
|
156
|
+
pmcli_parse_options "item generation at" "$@"
|
|
157
|
+
pmcli_require item generation
|
|
158
|
+
pmcli_integer "${PMCLI_OPTS[item]}" "--item"
|
|
159
|
+
item="$PMCLI_VALUE"
|
|
160
|
+
pmcli_integer "${PMCLI_OPTS[generation]}" "--generation"
|
|
161
|
+
generation="$PMCLI_VALUE"
|
|
162
|
+
pmcli_timestamp
|
|
163
|
+
at="$PMCLI_VALUE"
|
|
164
|
+
pm_build_requeue_entry "$item" "$generation" "$at" || rc=$?
|
|
165
|
+
pmcli_emit "$rc"
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
mpr_main() {
|
|
169
|
+
# Dispatch on the subcommand.
|
|
170
|
+
(($# > 0)) ||
|
|
171
|
+
pmcli_usage_error "missing subcommand; expected one of admit, recolor, add-entry, close, close-entry, requeue-entry"
|
|
172
|
+
local subcommand="$1"
|
|
173
|
+
shift
|
|
174
|
+
case "$subcommand" in
|
|
175
|
+
admit) mpr_admit "$@" ;;
|
|
176
|
+
recolor) mpr_recolor "$@" ;;
|
|
177
|
+
add-entry) mpr_add_entry "$@" ;;
|
|
178
|
+
close) mpr_close "$@" ;;
|
|
179
|
+
close-entry) mpr_close_entry "$@" ;;
|
|
180
|
+
requeue-entry) mpr_requeue_entry "$@" ;;
|
|
181
|
+
*) pmcli_usage_error "unknown subcommand: $subcommand" ;;
|
|
182
|
+
esac
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
# Guard so the file can be sourced without executing main. main's return code
|
|
186
|
+
# is captured and re-exited explicitly as the final statement.
|
|
187
|
+
if [[ ${BASH_SOURCE[0]} == "${0}" ]]; then
|
|
188
|
+
mpr_rc=0
|
|
189
|
+
mpr_main "$@" || mpr_rc=$?
|
|
190
|
+
exit "$mpr_rc"
|
|
191
|
+
fi
|
|
@@ -52,14 +52,19 @@ PCOH_RESULT=""
|
|
|
52
52
|
PCOH_WORDS=()
|
|
53
53
|
|
|
54
54
|
pcoh_split_words() {
|
|
55
|
-
# Populate PCOH_WORDS by splitting a
|
|
55
|
+
# Populate PCOH_WORDS by splitting a whitespace-separated string.
|
|
56
56
|
#
|
|
57
57
|
# Args: $1 = the string to split. An empty or whitespace-only string yields
|
|
58
58
|
# an empty array, which is the empty-graph and empty-cohort case. `read -ra`
|
|
59
59
|
# is used rather than an unquoted expansion so a token is never subjected to
|
|
60
|
-
# pathname expansion.
|
|
60
|
+
# pathname expansion. Newline, CR, VT, and FF are converted to spaces first:
|
|
61
|
+
# `read` consumes one newline-terminated record and splits it on IFS only, so
|
|
62
|
+
# without the conversion every token after the first newline is dropped and
|
|
63
|
+
# CR, VT, and FF are not separators. With it, the separator set is space, tab,
|
|
64
|
+
# LF, CR, VT, and FF, which matches Python str.split() for ASCII.
|
|
65
|
+
local text="${1-}"
|
|
61
66
|
PCOH_WORDS=()
|
|
62
|
-
read -ra PCOH_WORDS <<<"${
|
|
67
|
+
read -ra PCOH_WORDS <<<"${text//[$'\n\r\v\f']/ }"
|
|
63
68
|
}
|
|
64
69
|
|
|
65
70
|
pcoh_fail() {
|
|
@@ -123,9 +128,14 @@ pcoh_build_adjacency() {
|
|
|
123
128
|
PCOH_ADJACENT=()
|
|
124
129
|
pcoh_split_words "$keys"
|
|
125
130
|
local -a key_list=("${PCOH_WORDS[@]}")
|
|
131
|
+
# Rebuild the membership haystack from the split tokens. The endpoint checks
|
|
132
|
+
# below match a space-delimited word, so they would miss a key that the caller
|
|
133
|
+
# separated with a newline, tab, CR, VT, or FF instead of a single space.
|
|
134
|
+
keys=""
|
|
126
135
|
# Seed every declared key so an isolated vertex survives into the coloring
|
|
127
136
|
# step; inferring vertices from the edge list alone would drop it.
|
|
128
137
|
for key in "${key_list[@]}"; do
|
|
138
|
+
keys="$keys $key"
|
|
129
139
|
PCOH_DEGREE["$key"]=0
|
|
130
140
|
PCOH_NEIGHBORS["$key"]=""
|
|
131
141
|
done
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# parallel-mutation-cli.sh: sourceable command-line helpers shared by the two
|
|
3
|
+
# parallel mutation entry points, remove-parallel-item.sh (issue #791) and
|
|
4
|
+
# mutate-parallel-run.sh (issue #890). It holds option parsing, required-option
|
|
5
|
+
# checks, integer, key-list, and edge-list validation, the `--at` timestamp, and
|
|
6
|
+
# the result printer, so both entry points apply one grammar and one exit-code
|
|
7
|
+
# contract.
|
|
8
|
+
#
|
|
9
|
+
# Grammar. Every option takes exactly one value. An unknown name, an
|
|
10
|
+
# abbreviation, a `--name=value` form, a repeated option, or a missing value is
|
|
11
|
+
# a usage error; a value beginning with `--` counts as missing. Integer tokens
|
|
12
|
+
# match `-?(0|[1-9][0-9]*)`. `--at` matches `[0-9A-Za-z:.+-]+` and defaults to
|
|
13
|
+
# the current UTC minute as yyyy-MM-ddTHH-mm.
|
|
14
|
+
#
|
|
15
|
+
# Exit-code contract carried by these helpers:
|
|
16
|
+
# exit 2 usage error; one stderr line beginning
|
|
17
|
+
# "PARALLEL_MUTATION_ERROR: usage error:" (pmcli_usage_error)
|
|
18
|
+
# exit 1 rule rejection; PM_ERROR printed as one stderr line (pmcli_emit)
|
|
19
|
+
# exit 0 success; PM_RESULT printed as one stdout line (pmcli_emit)
|
|
20
|
+
#
|
|
21
|
+
# Dependencies. pmcli_emit reads PM_RESULT and PM_ERROR, which
|
|
22
|
+
# parallel-mutation.sh owns; an entry point sources that library before calling
|
|
23
|
+
# it. The only external command is `date`, run solely when `--at` is omitted.
|
|
24
|
+
#
|
|
25
|
+
# shellcheck disable=SC2034
|
|
26
|
+
# SC2034 is disabled file-wide because PMCLI_OPTS and PMCLI_VALUE are written
|
|
27
|
+
# here and read by the two entry points, which shellcheck analyses separately.
|
|
28
|
+
|
|
29
|
+
# Resolve this file's own directory so its dependencies source regardless of
|
|
30
|
+
# the caller's working directory.
|
|
31
|
+
PMCLI_LIB_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
|
|
32
|
+
# shellcheck source=.claude/lib/bash/parallel-cohorts.sh
|
|
33
|
+
# shellcheck disable=SC1091
|
|
34
|
+
source "$PMCLI_LIB_DIR/parallel-cohorts.sh"
|
|
35
|
+
|
|
36
|
+
# Parsed option values, keyed by option name without the leading dashes.
|
|
37
|
+
declare -gA PMCLI_OPTS=()
|
|
38
|
+
|
|
39
|
+
# The canonical value stored by the most recent validator.
|
|
40
|
+
PMCLI_VALUE=""
|
|
41
|
+
|
|
42
|
+
pmcli_usage_error() {
|
|
43
|
+
# Print one usage-error line on stderr and exit 2.
|
|
44
|
+
#
|
|
45
|
+
# Args: $1 = the specific reason.
|
|
46
|
+
printf 'PARALLEL_MUTATION_ERROR: usage error: %s\n' "$1" >&2
|
|
47
|
+
exit 2
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
pmcli_parse_options() {
|
|
51
|
+
# Parse `--name value` pairs into PMCLI_OPTS against an allowed-name list.
|
|
52
|
+
#
|
|
53
|
+
# Args: $1 = space-separated allowed option names, then the arguments.
|
|
54
|
+
local allowed="$1" name
|
|
55
|
+
shift
|
|
56
|
+
PMCLI_OPTS=()
|
|
57
|
+
while (($# > 0)); do
|
|
58
|
+
name="${1#--}"
|
|
59
|
+
if [[ $1 != --* ]] || ! pc_contains_word "$allowed" "$name"; then
|
|
60
|
+
pmcli_usage_error "unknown option: $1"
|
|
61
|
+
fi
|
|
62
|
+
if [[ -n ${PMCLI_OPTS["$name"]+set} ]]; then
|
|
63
|
+
pmcli_usage_error "option given more than once: $1"
|
|
64
|
+
fi
|
|
65
|
+
if (($# < 2)) || [[ $2 == --* ]]; then
|
|
66
|
+
pmcli_usage_error "option $1 requires a value"
|
|
67
|
+
fi
|
|
68
|
+
PMCLI_OPTS["$name"]="$2"
|
|
69
|
+
shift 2
|
|
70
|
+
done
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
pmcli_require() {
|
|
74
|
+
# Exit with a usage error unless every named option was supplied.
|
|
75
|
+
#
|
|
76
|
+
# Args: the option names that are required.
|
|
77
|
+
local name
|
|
78
|
+
for name in "$@"; do
|
|
79
|
+
[[ -n ${PMCLI_OPTS["$name"]+set} ]] || pmcli_usage_error "missing required option: --$name"
|
|
80
|
+
done
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
pmcli_integer() {
|
|
84
|
+
# Validate one integer token and store it in canonical form in PMCLI_VALUE.
|
|
85
|
+
#
|
|
86
|
+
# Args: $1 = the token, $2 = a label naming where it came from. The
|
|
87
|
+
# validators store their result in a global instead of echoing it, because
|
|
88
|
+
# a usage error raised inside a command substitution would exit only the
|
|
89
|
+
# subshell.
|
|
90
|
+
if [[ ! $1 =~ ^-?(0|[1-9][0-9]*)$ ]]; then
|
|
91
|
+
pmcli_usage_error "$2 must be a decimal integer matching -?(0|[1-9][0-9]*); found: $1"
|
|
92
|
+
fi
|
|
93
|
+
PMCLI_VALUE=$(($1))
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
pmcli_integer_list() {
|
|
97
|
+
# Validate a whitespace-separated key list; store it space separated.
|
|
98
|
+
#
|
|
99
|
+
# Args: $1 = the list, $2 = a label naming the option.
|
|
100
|
+
local token canonical=""
|
|
101
|
+
pcoh_split_words "$1"
|
|
102
|
+
local -a tokens=("${PCOH_WORDS[@]}")
|
|
103
|
+
for token in "${tokens[@]}"; do
|
|
104
|
+
pmcli_integer "$token" "$2 key"
|
|
105
|
+
canonical="$canonical $PMCLI_VALUE"
|
|
106
|
+
done
|
|
107
|
+
PMCLI_VALUE="${canonical# }"
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
pmcli_edge_list() {
|
|
111
|
+
# Validate a whitespace-separated `a:b` edge list; store it canonical.
|
|
112
|
+
#
|
|
113
|
+
# Args: $1 = the edge list.
|
|
114
|
+
local token first canonical=""
|
|
115
|
+
pcoh_split_words "$1"
|
|
116
|
+
local -a tokens=("${PCOH_WORDS[@]}")
|
|
117
|
+
for token in "${tokens[@]}"; do
|
|
118
|
+
if [[ $token != *:* || $token == *:*:* ]]; then
|
|
119
|
+
pmcli_usage_error "edge must be <a>:<b>; found: $token"
|
|
120
|
+
fi
|
|
121
|
+
pmcli_integer "${token%%:*}" "edge endpoint"
|
|
122
|
+
first="$PMCLI_VALUE"
|
|
123
|
+
pmcli_integer "${token#*:}" "edge endpoint"
|
|
124
|
+
canonical="$canonical $first:$PMCLI_VALUE"
|
|
125
|
+
done
|
|
126
|
+
PMCLI_VALUE="${canonical# }"
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
pmcli_timestamp() {
|
|
130
|
+
# Store the validated `--at` value, or the current UTC minute when omitted.
|
|
131
|
+
if [[ -n ${PMCLI_OPTS[at]+set} ]]; then
|
|
132
|
+
PMCLI_VALUE="${PMCLI_OPTS[at]}"
|
|
133
|
+
[[ $PMCLI_VALUE =~ ^[0-9A-Za-z:.+-]+$ ]] ||
|
|
134
|
+
pmcli_usage_error "--at must match [0-9A-Za-z:.+-]+; found: $PMCLI_VALUE"
|
|
135
|
+
else
|
|
136
|
+
# The only clock read of either entry point, taken only when --at is
|
|
137
|
+
# omitted.
|
|
138
|
+
PMCLI_VALUE=$(date -u +%Y-%m-%dT%H-%M)
|
|
139
|
+
fi
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
pmcli_emit() {
|
|
143
|
+
# Print the library outcome and return the matching exit code.
|
|
144
|
+
#
|
|
145
|
+
# Args: $1 = the library function's return code.
|
|
146
|
+
if (($1 != 0)); then
|
|
147
|
+
printf '%s\n' "$PM_ERROR" >&2
|
|
148
|
+
return 1
|
|
149
|
+
fi
|
|
150
|
+
printf '%s\n' "$PM_RESULT"
|
|
151
|
+
return 0
|
|
152
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# parallel-mutation-ops.sh: sourceable bash port of the add, close, and requeue
|
|
3
|
+
# functions of the parallel mutation engine (issue #890), so a workspace that
|
|
4
|
+
# received the Claude customization payload can decide an admission, gate a run
|
|
5
|
+
# close, and build the add, close, and requeue mutations[] records without an
|
|
6
|
+
# interpreter. It sources parallel-mutation.sh, which carries the removal
|
|
7
|
+
# functions and the shared message builders.
|
|
8
|
+
#
|
|
9
|
+
# Parity reference (the repository authority; this file reproduces its
|
|
10
|
+
# observable outputs and rejection messages byte for byte):
|
|
11
|
+
# decide_admission scripts/dev_tools/parallel_mutation_protocol.py
|
|
12
|
+
# decide_close scripts/dev_tools/parallel_mutation_protocol.py
|
|
13
|
+
# build_add_entry scripts/dev_tools/_parallel_mutation_entries.py
|
|
14
|
+
# build_close_entry scripts/dev_tools/_parallel_mutation_entries.py
|
|
15
|
+
# build_requeue_entry scripts/dev_tools/_parallel_mutation_entries.py
|
|
16
|
+
# ItemRecord, MutationEntry scripts/dev_tools/_parallel_mutation_models.py
|
|
17
|
+
# rejection messages scripts/dev_tools/_parallel_mutation_errors.py
|
|
18
|
+
#
|
|
19
|
+
# Declared divergences (also listed in both parity lanes):
|
|
20
|
+
# 1. A duplicate key in `close --items` is a usage error (exit 2) raised by
|
|
21
|
+
# the command line; the Python mapping would keep the last record.
|
|
22
|
+
# 2. `at` is a caller-supplied string instead of a `datetime`.
|
|
23
|
+
# 3. Option abbreviations are rejected.
|
|
24
|
+
# 4. `admit` has no rejection branch, in either implementation.
|
|
25
|
+
#
|
|
26
|
+
# Contract. Every public function is pure: no file I/O, no network access, no
|
|
27
|
+
# clock read, and no external utility beyond `sort`. Each sets PM_RESULT to one
|
|
28
|
+
# compact JSON object and returns 0 on success, or sets PM_ERROR to the exact
|
|
29
|
+
# reference message and returns 1 on a rule rejection. Integer arguments are
|
|
30
|
+
# expected in canonical decimal form; the entry point mutate-parallel-run.sh
|
|
31
|
+
# validates them before calling here. Entry validation order follows
|
|
32
|
+
# MutationEntry: the stamped recolor_generation first, then item_key.
|
|
33
|
+
#
|
|
34
|
+
# shellcheck disable=SC2034
|
|
35
|
+
# SC2034 is disabled file-wide because PM_RESULT and PM_ERROR are written here
|
|
36
|
+
# and read by the entry point mutate-parallel-run.sh and by the bats suites,
|
|
37
|
+
# which shellcheck analyses separately.
|
|
38
|
+
|
|
39
|
+
# Resolve this file's own directory so its dependencies source regardless of
|
|
40
|
+
# the caller's working directory.
|
|
41
|
+
PMOPS_LIB_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
|
|
42
|
+
# shellcheck source=.claude/lib/bash/parallel-mutation.sh
|
|
43
|
+
# shellcheck disable=SC1091
|
|
44
|
+
source "$PMOPS_LIB_DIR/parallel-mutation.sh"
|
|
45
|
+
|
|
46
|
+
pm_decide_admission() {
|
|
47
|
+
# Decide whether a candidate joins the current cohort or is deferred.
|
|
48
|
+
#
|
|
49
|
+
# Args: $1 = candidate key, $2 = space-separated `a:b` conflict edges over
|
|
50
|
+
# all items, $3 = space-separated in-flight keys, $4 = space-separated
|
|
51
|
+
# current-cohort member keys. The candidate is deferred when any edge joins
|
|
52
|
+
# it to a key in the union of $3 and $4, in either endpoint position.
|
|
53
|
+
# Returns 0 with PM_RESULT holding the decision; admission never rejects.
|
|
54
|
+
local candidate="$1" edges="$2" edge first second outcome="admit_current_cohort"
|
|
55
|
+
local recompute=false
|
|
56
|
+
PM_RESULT=""
|
|
57
|
+
PM_ERROR=""
|
|
58
|
+
pcoh_split_words "$3 $4"
|
|
59
|
+
local blocking=" ${PCOH_WORDS[*]} "
|
|
60
|
+
pcoh_split_words "$edges"
|
|
61
|
+
local -a edge_list=("${PCOH_WORDS[@]}")
|
|
62
|
+
# Scan every edge for one joining the candidate to a blocking key; the
|
|
63
|
+
# first such edge settles the decision.
|
|
64
|
+
for edge in "${edge_list[@]}"; do
|
|
65
|
+
first="${edge%%:*}"
|
|
66
|
+
second="${edge#*:}"
|
|
67
|
+
if { [[ $first == "$candidate" ]] && [[ $blocking == *" $second "* ]]; } ||
|
|
68
|
+
{ [[ $second == "$candidate" ]] && [[ $blocking == *" $first "* ]]; }; then
|
|
69
|
+
outcome="defer_and_recolor"
|
|
70
|
+
recompute=true
|
|
71
|
+
break
|
|
72
|
+
fi
|
|
73
|
+
done
|
|
74
|
+
PM_RESULT="{\"candidate\":$candidate,\"outcome\":\"$outcome\",\"triggers_recompute\":$recompute}"
|
|
75
|
+
return 0
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
pm_build_add_entry() {
|
|
79
|
+
# Build the single mutations[] record for an accepted add.
|
|
80
|
+
#
|
|
81
|
+
# Args: $1 = item key, $2 = `true` for a deferred add (stamps g + 1),
|
|
82
|
+
# otherwise `false` (stamps g), $3 = the generation before the add, $4 =
|
|
83
|
+
# the `at` timestamp string. Returns 0 with PM_RESULT holding the record in
|
|
84
|
+
# F3 field order, 1 with PM_ERROR holding the reference contract message.
|
|
85
|
+
local item_key="$1" deferred="$2" generation="$3" at="$4" stamped
|
|
86
|
+
PM_RESULT=""
|
|
87
|
+
PM_ERROR=""
|
|
88
|
+
stamped="$generation"
|
|
89
|
+
if [[ $deferred == true ]]; then
|
|
90
|
+
stamped=$((generation + 1))
|
|
91
|
+
fi
|
|
92
|
+
if ((stamped < 0)); then
|
|
93
|
+
pm_fail "$(pm_entry_contract_message recolor_generation "$stamped" add)"
|
|
94
|
+
return 1
|
|
95
|
+
fi
|
|
96
|
+
if ((item_key <= 0)); then
|
|
97
|
+
pm_fail "$(pm_entry_contract_message item_key "$item_key" add)"
|
|
98
|
+
return 1
|
|
99
|
+
fi
|
|
100
|
+
PM_RESULT="{\"op\":\"add\",\"item_key\":$item_key,\"at\":$(pm_json_string "$at"),\"prior_state\":null,\"new_state\":\"scheduled\",\"disposition\":null,\"recolor_generation\":$stamped}"
|
|
101
|
+
return 0
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
pm_in_flight_list() {
|
|
105
|
+
# Echo a key list as Python renders a list of ints, ascending.
|
|
106
|
+
#
|
|
107
|
+
# Args: $1 = space-separated keys, at least one.
|
|
108
|
+
local ordered key rendered=""
|
|
109
|
+
pcoh_split_words "$1"
|
|
110
|
+
ordered=$(printf '%s\n' "${PCOH_WORDS[@]}" | LC_ALL=C sort -n)
|
|
111
|
+
while IFS= read -r key; do
|
|
112
|
+
[[ -n $key ]] || continue
|
|
113
|
+
[[ -z $rendered ]] || rendered="$rendered, "
|
|
114
|
+
rendered="$rendered$key"
|
|
115
|
+
done <<<"$ordered"
|
|
116
|
+
printf '[%s]' "$rendered"
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
pm_decide_close() {
|
|
120
|
+
# Gate a run close on no item being in flight.
|
|
121
|
+
#
|
|
122
|
+
# Args: $1 = space-separated `<key>:<state>` tokens, one per tracked item.
|
|
123
|
+
# Each token is validated in supplied order as the reference ItemRecord
|
|
124
|
+
# constructor validates it: a non-positive key names nothing the run
|
|
125
|
+
# tracks, and an out-of-enum state is rejected with the item-state members.
|
|
126
|
+
# Returns 0 with PM_RESULT `{"close_permitted":true}`, or 1 with PM_ERROR
|
|
127
|
+
# naming every in-flight key ascending.
|
|
128
|
+
local token key state in_flight=""
|
|
129
|
+
PM_RESULT=""
|
|
130
|
+
PM_ERROR=""
|
|
131
|
+
pcoh_split_words "$1"
|
|
132
|
+
local -a tokens=("${PCOH_WORDS[@]}")
|
|
133
|
+
for token in "${tokens[@]}"; do
|
|
134
|
+
key="${token%%:*}"
|
|
135
|
+
state="${token#*:}"
|
|
136
|
+
if ((key <= 0)); then
|
|
137
|
+
pm_fail "$(pm_unknown_item_message "$key")"
|
|
138
|
+
return 1
|
|
139
|
+
fi
|
|
140
|
+
if ! pc_enum_members_contains "$PC_VALID_ITEM_STATES" "$state"; then
|
|
141
|
+
pm_fail "$PM_REJECTION_PREFIX item $key state $(pc_repr str "$state") is not one of $PC_VALID_ITEM_STATES."
|
|
142
|
+
return 1
|
|
143
|
+
fi
|
|
144
|
+
if [[ $state == "$PM_PINNED_STATE" ]]; then
|
|
145
|
+
in_flight="$in_flight $key"
|
|
146
|
+
fi
|
|
147
|
+
done
|
|
148
|
+
if [[ -n $in_flight ]]; then
|
|
149
|
+
pm_fail "$PM_REJECTION_PREFIX close requires no item in flight; still in flight: $(pm_in_flight_list "$in_flight")."
|
|
150
|
+
return 1
|
|
151
|
+
fi
|
|
152
|
+
PM_RESULT='{"close_permitted":true}'
|
|
153
|
+
return 0
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
pm_build_close_entry() {
|
|
157
|
+
# Build the single run-scoped mutations[] record for an accepted close.
|
|
158
|
+
#
|
|
159
|
+
# Args: $1 = the generation, stamped unchanged, $2 = the `at` timestamp
|
|
160
|
+
# string. Returns 0 with PM_RESULT holding the record, 1 with PM_ERROR when
|
|
161
|
+
# the generation is negative.
|
|
162
|
+
local generation="$1" at="$2"
|
|
163
|
+
PM_RESULT=""
|
|
164
|
+
PM_ERROR=""
|
|
165
|
+
if ((generation < 0)); then
|
|
166
|
+
pm_fail "$(pm_entry_contract_message recolor_generation "$generation" close)"
|
|
167
|
+
return 1
|
|
168
|
+
fi
|
|
169
|
+
PM_RESULT="{\"op\":\"close\",\"item_key\":null,\"at\":$(pm_json_string "$at"),\"prior_state\":null,\"new_state\":null,\"disposition\":null,\"recolor_generation\":$generation}"
|
|
170
|
+
return 0
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
pm_build_requeue_entry() {
|
|
174
|
+
# Build the single mutations[] record for a drift-induced requeue.
|
|
175
|
+
#
|
|
176
|
+
# Args: $1 = item key, $2 = the generation before the requeue (stamps
|
|
177
|
+
# g + 1), $3 = the `at` timestamp string. Returns 0 with PM_RESULT holding
|
|
178
|
+
# the record, 1 with PM_ERROR holding the reference contract message.
|
|
179
|
+
local item_key="$1" generation="$2" at="$3" stamped
|
|
180
|
+
PM_RESULT=""
|
|
181
|
+
PM_ERROR=""
|
|
182
|
+
stamped=$((generation + 1))
|
|
183
|
+
if ((stamped < 0)); then
|
|
184
|
+
pm_fail "$(pm_entry_contract_message recolor_generation "$stamped" requeue)"
|
|
185
|
+
return 1
|
|
186
|
+
fi
|
|
187
|
+
if ((item_key <= 0)); then
|
|
188
|
+
pm_fail "$(pm_entry_contract_message item_key "$item_key" requeue)"
|
|
189
|
+
return 1
|
|
190
|
+
fi
|
|
191
|
+
PM_RESULT="{\"op\":\"requeue\",\"item_key\":$item_key,\"at\":$(pm_json_string "$at"),\"prior_state\":\"$PM_PINNED_STATE\",\"new_state\":\"blocked\",\"disposition\":null,\"recolor_generation\":$stamped}"
|
|
192
|
+
return 0
|
|
193
|
+
}
|