@danmoisan/drm-copilot-mcp 1.1.23 → 1.1.24
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 +13347 -7926
- package/package.json +1 -1
- package/resources/claude-customizations/.claude/agents/discovery-capability-sentinel.md +34 -0
- package/resources/claude-customizations/.claude/agents/legacy-parity-analyst.md +101 -43
- package/resources/claude-customizations/.claude/agents/migration-coverage-reviewer.md +107 -43
- package/resources/claude-customizations/.claude/agents/parallel-orchestrator.md +14 -0
- package/resources/claude-customizations/.claude/agents/parallel-planner.md +21 -1
- package/resources/claude-customizations/.claude/agents/pr-author.md +17 -1
- package/resources/claude-customizations/.claude/agents/requirements-reconciler.md +117 -42
- package/resources/claude-customizations/.claude/agents/runtime-characterization-analyst.md +103 -43
- package/resources/claude-customizations/.claude/hooks/discovery-capability-probe.ps1 +102 -0
- package/resources/claude-customizations/.claude/hooks/enforce-discovery-artifact-gate.ps1 +69 -194
- package/resources/claude-customizations/.claude/hooks/enforce-discovery-planning-gate.ps1 +87 -0
- package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate.ps1 +7 -1
- package/resources/claude-customizations/.claude/hooks/enforce-model-routing-receipt.ps1 +6 -3
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-epic-scope.ps1 +168 -48
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +1 -1
- package/resources/claude-customizations/.claude/hooks/enforce-powershell-batch-budget.ps1 +59 -20
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-command-allowlist.ps1 +44 -3
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill-helpers.ps1 +60 -4
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.epic-base-branch.ps1 +4 -2
- package/resources/claude-customizations/.claude/hooks/enforce-python-batch-budget.ps1 +59 -20
- package/resources/claude-customizations/.claude/hooks/record-discovery-agent-start.ps1 +132 -0
- package/resources/claude-customizations/.claude/hooks/validate-discovery-artifact-gate.ps1 +56 -212
- package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output-rejection.ps1 +172 -0
- package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output.ps1 +9 -1
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadius.psm1 +9 -3
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusAggregate.psm1 +154 -0
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusCombination.psm1 +228 -0
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusExtraction.psm1 +1 -0
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusNormalization.psm1 +4 -2
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusScheduling.psm1 +63 -16
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusTokenShape.psm1 +56 -1
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusWriteIntent.psm1 +4 -1
- package/resources/claude-customizations/.claude/lib/codex-routing/CodexDeployment.psm1 +5 -1
- package/resources/claude-customizations/.claude/lib/codex-routing/CodexDiscoveryRouting.psm1 +161 -0
- package/resources/claude-customizations/.claude/lib/codex-routing/CodexTopology.psm1 +25 -2
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryAuthority.psd1 +90 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryAuthority.psm1 +493 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryAuthorization.psm1 +479 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryCapability.psm1 +468 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryEncoding.psm1 +156 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryManifest.psm1 +384 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryObservation.psm1 +217 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryPayload.psm1 +276 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryPlanning.psm1 +336 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryRootGrant.psm1 +164 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryStart.psm1 +440 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryStartRecord.psm1 +296 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryStop.psm1 +336 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryToolRouting.psm1 +228 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryValidation.psm1 +188 -341
- package/resources/claude-customizations/.claude/lib/worktree-resolution/HookTargetWorktree.psm1 +391 -0
- package/resources/claude-customizations/.claude/rules/orchestrator-state.md +1 -0
- package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +150 -18
- package/resources/claude-customizations/.claude/settings.json +32 -3
- package/resources/claude-customizations/.claude/skills/adjudicate-unspecified-behavior/SKILL.md +135 -0
- package/resources/claude-customizations/.claude/skills/characterize-runtime-behavior/SKILL.md +134 -0
- package/resources/claude-customizations/.claude/skills/discovery-behavior-reconciliation/SKILL.md +28 -47
- package/resources/claude-customizations/.claude/skills/discovery-coverage-ledger/SKILL.md +32 -48
- package/resources/claude-customizations/.claude/skills/discovery-parity-matrix/SKILL.md +28 -45
- package/resources/claude-customizations/.claude/skills/discovery-repo-inventory/SKILL.md +28 -62
- package/resources/claude-customizations/.claude/skills/discovery-runtime-characterization/SKILL.md +28 -45
- package/resources/claude-customizations/.claude/skills/discovery-validate-artifacts/SKILL.md +28 -61
- package/resources/claude-customizations/.claude/skills/discovery-workflow/SKILL.md +55 -139
- package/resources/claude-customizations/.claude/skills/execution-environment/SKILL.md +24 -9
- package/resources/claude-customizations/.claude/skills/extract-feature-contract/SKILL.md +133 -0
- package/resources/claude-customizations/.claude/skills/generate-acceptance-scenarios/SKILL.md +132 -0
- package/resources/claude-customizations/.claude/skills/initialize-discovery-workspace/SKILL.md +131 -0
- package/resources/claude-customizations/.claude/skills/inventory-legacy-system/SKILL.md +131 -0
- package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +5 -4
- package/resources/claude-customizations/.claude/skills/parallel-add/SKILL.md +21 -1
- package/resources/claude-customizations/.claude/skills/parallel-close/SKILL.md +11 -1
- package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +34 -0
- package/resources/claude-customizations/.claude/skills/parallel-plan/SKILL.md +69 -6
- package/resources/claude-customizations/.claude/skills/parallel-remove/SKILL.md +15 -0
- package/resources/claude-customizations/.claude/skills/parallel-run/SKILL.md +6 -0
- package/resources/claude-customizations/.claude/skills/pr-author/SKILL.md +21 -4
- package/resources/claude-customizations/.claude/skills/reconcile-parity-matrix/SKILL.md +131 -0
- package/resources/claude-customizations/.claude/skills/review-discovery-coverage/SKILL.md +129 -0
- package/resources/claude-customizations/.claude/skills/validate-discovery-artifacts/SKILL.md +127 -0
- package/resources/claude-customizations/config/blast-radius.json +6 -5
- package/resources/claude-customizations/config/orchestration-routing.json +61 -1
- package/resources/claude-customizations/pack-manifests/core.json +33 -1
- package/resources/codex-and-agents-customizations/.agents/skills/adjudicate-unspecified-behavior/SKILL.md +135 -0
- package/resources/codex-and-agents-customizations/.agents/skills/atomic-planner/SKILL.md +7 -0
- package/resources/codex-and-agents-customizations/.agents/skills/characterize-runtime-behavior/SKILL.md +134 -0
- package/resources/codex-and-agents-customizations/.agents/skills/discovery-behavior-reconciliation/SKILL.md +46 -0
- package/resources/codex-and-agents-customizations/.agents/skills/discovery-coverage-ledger/SKILL.md +50 -0
- package/resources/codex-and-agents-customizations/.agents/skills/discovery-parity-matrix/SKILL.md +46 -0
- package/resources/codex-and-agents-customizations/.agents/skills/discovery-repo-inventory/SKILL.md +46 -0
- package/resources/codex-and-agents-customizations/.agents/skills/discovery-runtime-characterization/SKILL.md +46 -0
- package/resources/codex-and-agents-customizations/.agents/skills/discovery-validate-artifacts/SKILL.md +46 -0
- package/resources/codex-and-agents-customizations/.agents/skills/discovery-workflow/SKILL.md +62 -0
- package/resources/codex-and-agents-customizations/.agents/skills/execution-environment/SKILL.md +24 -9
- package/resources/codex-and-agents-customizations/.agents/skills/extract-feature-contract/SKILL.md +133 -0
- package/resources/codex-and-agents-customizations/.agents/skills/generate-acceptance-scenarios/SKILL.md +132 -0
- package/resources/codex-and-agents-customizations/.agents/skills/initialize-discovery-workspace/SKILL.md +131 -0
- package/resources/codex-and-agents-customizations/.agents/skills/inventory-legacy-system/SKILL.md +131 -0
- package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-workflow/SKILL.md +8 -0
- package/resources/codex-and-agents-customizations/.agents/skills/reconcile-parity-matrix/SKILL.md +131 -0
- package/resources/codex-and-agents-customizations/.agents/skills/review-discovery-coverage/SKILL.md +129 -0
- package/resources/codex-and-agents-customizations/.agents/skills/validate-discovery-artifacts/SKILL.md +127 -0
- package/resources/codex-and-agents-customizations/.codex/agents/discovery-capability-sentinel.toml +51 -0
- package/resources/codex-and-agents-customizations/.codex/agents/legacy-parity-analyst-c1.toml +148 -0
- package/resources/codex-and-agents-customizations/.codex/agents/legacy-parity-analyst-c2.toml +148 -0
- package/resources/codex-and-agents-customizations/.codex/agents/legacy-parity-analyst-c3-elevated.toml +148 -0
- package/resources/codex-and-agents-customizations/.codex/agents/legacy-parity-analyst-c3.toml +148 -0
- package/resources/codex-and-agents-customizations/.codex/agents/legacy-parity-analyst-c4.toml +148 -0
- package/resources/codex-and-agents-customizations/.codex/agents/legacy-parity-analyst.toml +148 -0
- package/resources/codex-and-agents-customizations/.codex/agents/migration-coverage-reviewer-c1.toml +154 -0
- package/resources/codex-and-agents-customizations/.codex/agents/migration-coverage-reviewer-c2.toml +154 -0
- package/resources/codex-and-agents-customizations/.codex/agents/migration-coverage-reviewer-c3-elevated.toml +154 -0
- package/resources/codex-and-agents-customizations/.codex/agents/migration-coverage-reviewer-c3.toml +154 -0
- package/resources/codex-and-agents-customizations/.codex/agents/migration-coverage-reviewer-c4.toml +154 -0
- package/resources/codex-and-agents-customizations/.codex/agents/migration-coverage-reviewer.toml +154 -0
- package/resources/codex-and-agents-customizations/.codex/agents/requirements-reconciler-c1.toml +164 -0
- package/resources/codex-and-agents-customizations/.codex/agents/requirements-reconciler-c2.toml +164 -0
- package/resources/codex-and-agents-customizations/.codex/agents/requirements-reconciler-c3-elevated.toml +164 -0
- package/resources/codex-and-agents-customizations/.codex/agents/requirements-reconciler-c3.toml +164 -0
- package/resources/codex-and-agents-customizations/.codex/agents/requirements-reconciler-c4.toml +164 -0
- package/resources/codex-and-agents-customizations/.codex/agents/requirements-reconciler.toml +164 -0
- package/resources/codex-and-agents-customizations/.codex/agents/runtime-characterization-analyst-c1.toml +150 -0
- package/resources/codex-and-agents-customizations/.codex/agents/runtime-characterization-analyst-c2.toml +150 -0
- package/resources/codex-and-agents-customizations/.codex/agents/runtime-characterization-analyst-c3-elevated.toml +150 -0
- package/resources/codex-and-agents-customizations/.codex/agents/runtime-characterization-analyst-c3.toml +150 -0
- package/resources/codex-and-agents-customizations/.codex/agents/runtime-characterization-analyst-c4.toml +150 -0
- package/resources/codex-and-agents-customizations/.codex/agents/runtime-characterization-analyst.toml +150 -0
- package/resources/codex-and-agents-customizations/.codex/config.toml +33 -3
- package/resources/codex-and-agents-customizations/.codex/hooks/codex-agent-profile-attestation.ps1 +22 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/discovery-capability-probe.ps1 +97 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/discovery-start-attestation.ps1 +171 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-codex-model-routing.ps1 +4 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-discovery-artifact-gate.ps1 +187 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-discovery-planning-gate.ps1 +149 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-epic-scope.ps1 +239 -2
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate.ps1 +6 -15
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-powershell-batch-budget.ps1 +54 -15
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-python-batch-budget.ps1 +54 -15
- package/resources/codex-and-agents-customizations/.codex/hooks/hook-target-worktree.ps1 +369 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/record-subagent-routing-attestation.ps1 +8 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/validate-codex-subagent-routing.ps1 +22 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/validate-discovery-agent-output.ps1 +178 -0
- package/resources/codex-and-agents-customizations/.codex/scripts/Resolve-CodexTopology.ps1 +11 -1
- package/resources/codex-and-agents-customizations/pack-manifests/core.json +63 -1
- package/resources/config/orchestration-routing.json +61 -1
- package/resources/customizations/.github/agents/discovery-capability-sentinel.agent.md +34 -0
- package/resources/customizations/.github/agents/legacy-parity-analyst.agent.md +122 -0
- package/resources/customizations/.github/agents/migration-coverage-reviewer.agent.md +128 -0
- package/resources/customizations/.github/agents/requirements-reconciler.agent.md +138 -0
- package/resources/customizations/.github/agents/runtime-characterization-analyst.agent.md +124 -0
- package/resources/customizations/.github/hooks/discovery-agent-start.ps1 +139 -0
- package/resources/customizations/.github/hooks/discovery-agent-stop.ps1 +123 -0
- package/resources/customizations/.github/hooks/discovery-capability-probe.ps1 +98 -0
- package/resources/customizations/.github/hooks/discovery-planning.ps1 +91 -0
- package/resources/customizations/.github/hooks/discovery-pre-tool.ps1 +123 -0
- package/resources/customizations/.github/hooks/discovery.json +35 -0
- package/resources/customizations/.github/instructions/mermaid.instructions.md +85 -0
- package/resources/customizations/.github/prompts/execute-hard-lock.prompt.md +105 -0
- package/resources/customizations/.github/skills/adjudicate-unspecified-behavior/SKILL.md +135 -0
- package/resources/customizations/.github/skills/characterize-runtime-behavior/SKILL.md +134 -0
- package/resources/customizations/.github/skills/discovery-behavior-reconciliation/SKILL.md +46 -0
- package/resources/customizations/.github/skills/discovery-coverage-ledger/SKILL.md +50 -0
- package/resources/customizations/.github/skills/discovery-parity-matrix/SKILL.md +46 -0
- package/resources/customizations/.github/skills/discovery-repo-inventory/SKILL.md +46 -0
- package/resources/customizations/.github/skills/discovery-runtime-characterization/SKILL.md +46 -0
- package/resources/customizations/.github/skills/discovery-validate-artifacts/SKILL.md +46 -0
- package/resources/customizations/.github/skills/discovery-workflow/SKILL.md +62 -0
- package/resources/customizations/.github/skills/execution-environment/SKILL.md +24 -9
- package/resources/customizations/.github/skills/extract-feature-contract/SKILL.md +133 -0
- package/resources/customizations/.github/skills/generate-acceptance-scenarios/SKILL.md +132 -0
- package/resources/customizations/.github/skills/initialize-discovery-workspace/SKILL.md +131 -0
- package/resources/customizations/.github/skills/inventory-legacy-system/SKILL.md +131 -0
- package/resources/customizations/.github/skills/reconcile-parity-matrix/SKILL.md +131 -0
- package/resources/customizations/.github/skills/review-discovery-coverage/SKILL.md +129 -0
- package/resources/customizations/.github/skills/validate-discovery-artifacts/SKILL.md +127 -0
- package/resources/lib/codex-routing/CodexDeployment.psm1 +5 -1
- package/resources/lib/codex-routing/CodexDiscoveryRouting.psm1 +161 -0
- package/resources/lib/codex-routing/CodexTopology.psm1 +25 -2
- package/resources/lib/discovery-validation/DiscoveryAuthority.psd1 +90 -0
- package/resources/lib/discovery-validation/DiscoveryAuthority.psm1 +493 -0
- package/resources/lib/discovery-validation/DiscoveryAuthorization.psm1 +479 -0
- package/resources/lib/discovery-validation/DiscoveryCapability.psm1 +468 -0
- package/resources/lib/discovery-validation/DiscoveryEncoding.psm1 +156 -0
- package/resources/lib/discovery-validation/DiscoveryManifest.psm1 +384 -0
- package/resources/lib/discovery-validation/DiscoveryObservation.psm1 +217 -0
- package/resources/lib/discovery-validation/DiscoveryPayload.psm1 +276 -0
- package/resources/lib/discovery-validation/DiscoveryPlanning.psm1 +336 -0
- package/resources/lib/discovery-validation/DiscoveryRootGrant.psm1 +164 -0
- package/resources/lib/discovery-validation/DiscoveryStart.psm1 +440 -0
- package/resources/lib/discovery-validation/DiscoveryStartRecord.psm1 +296 -0
- package/resources/lib/discovery-validation/DiscoveryStop.psm1 +336 -0
- package/resources/lib/discovery-validation/DiscoveryToolRouting.psm1 +228 -0
- package/resources/powershell/PoshQC/PoshQC.Parallel.psm1 +452 -0
- package/resources/powershell/PoshQC/PoshQC.Shard.psm1 +106 -8
- package/resources/powershell/PoshQC/PoshQC.Testing.psm1 +12 -0
- package/resources/powershell/PoshQC/PoshQC.psd1 +6 -1
- package/resources/powershell/PoshQC/PoshQC.psm1 +7 -1
- package/resources/powershell/PoshQC/README.md +20 -1
package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output-rejection.ps1
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Parallel mutation rejection helpers for validate-orchestrator-output.ps1 (issue #1012).
|
|
4
|
+
|
|
5
|
+
.DESCRIPTION
|
|
6
|
+
Dot-sourced by validate-orchestrator-output.ps1. A parallel skill that rejects a mutation before
|
|
7
|
+
any run checkpoint exists stops without writing state and ends its final output with the line
|
|
8
|
+
PARALLEL_MUTATION_REJECTED: <code>. These helpers parse that signal, scan the live worktrees for
|
|
9
|
+
parallel checkpoint text that would contradict the claim that no state was written, decide
|
|
10
|
+
whether the unresolved parallel stop may be allowed, and compose the hint appended to the block
|
|
11
|
+
message when it may not.
|
|
12
|
+
|
|
13
|
+
The signal grammar, the closed code set, and the hook behavior are defined once in the section
|
|
14
|
+
`## Mutation Rejection Signal (issue #1012)` of .claude/rules/parallel-orchestration.md.
|
|
15
|
+
|
|
16
|
+
Imports no module, invokes no Python, and starts no process. Relies on
|
|
17
|
+
Find-WorktreeRunIdentitySignal, Get-WorktreeItemLiveRoot, Get-WorktreeRunCheckpointText, and
|
|
18
|
+
Get-WorktreeRunCheckpointPath, which the hook imports before it dot-sources this file. Reads no
|
|
19
|
+
file except through Get-WorktreeRunCheckpointText.
|
|
20
|
+
#>
|
|
21
|
+
|
|
22
|
+
# The closed rejection code set, in rule order. It mirrors the table of
|
|
23
|
+
# `## Mutation Rejection Signal (issue #1012)` in .claude/rules/parallel-orchestration.md, which owns
|
|
24
|
+
# the set; a Pester parity row pins this array to that table.
|
|
25
|
+
$script:ParallelMutationRejectionCodes = @('kickoff_artifact_missing', 'run_not_started', 'items_in_flight', 'unknown_item')
|
|
26
|
+
$script:ParallelMutationRejectionPrefix = 'PARALLEL_MUTATION_REJECTED:'
|
|
27
|
+
$script:ParallelMutationRejectionPattern = '^PARALLEL_MUTATION_REJECTED: (?<code>[a-z_]+)$'
|
|
28
|
+
$script:ParallelMutationRejectionHeading = '## Mutation Rejection Signal (issue #1012)'
|
|
29
|
+
$script:ParallelMutationRejectionArtifactType = 'parallel-orchestrator-state'
|
|
30
|
+
|
|
31
|
+
function Get-ParallelMutationRejectionCode {
|
|
32
|
+
<#
|
|
33
|
+
.SYNOPSIS
|
|
34
|
+
Return the code of a valid final-line rejection signal, or $null.
|
|
35
|
+
.DESCRIPTION
|
|
36
|
+
Pure. Only the last non-blank line of the output is considered, after trailing whitespace
|
|
37
|
+
(including a carriage return) is trimmed. The line must match
|
|
38
|
+
^PARALLEL_MUTATION_REJECTED: (?<code>[a-z_]+)$ case-sensitively, and the code must be an
|
|
39
|
+
ordinal member of the closed code set. Any other form, including formatting or trailing
|
|
40
|
+
text, returns $null.
|
|
41
|
+
.PARAMETER AgentOutput
|
|
42
|
+
The agent's final output text.
|
|
43
|
+
#>
|
|
44
|
+
[CmdletBinding()]
|
|
45
|
+
[OutputType([string])]
|
|
46
|
+
param(
|
|
47
|
+
[Parameter(Mandatory = $true)] [AllowNull()] [AllowEmptyString()] [string] $AgentOutput
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
$trimmed = ([string]$AgentOutput).TrimEnd()
|
|
51
|
+
if ($trimmed.Length -eq 0) { return $null }
|
|
52
|
+
$lastLine = $trimmed.Substring($trimmed.LastIndexOf("`n") + 1)
|
|
53
|
+
$match = [regex]::Match($lastLine, $script:ParallelMutationRejectionPattern)
|
|
54
|
+
if (-not $match.Success) { return $null }
|
|
55
|
+
$code = $match.Groups['code'].Value
|
|
56
|
+
foreach ($member in $script:ParallelMutationRejectionCodes) {
|
|
57
|
+
if ([string]::Equals($member, $code, [System.StringComparison]::Ordinal)) { return $member }
|
|
58
|
+
}
|
|
59
|
+
return $null
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function Test-ParallelRunStateAbsent {
|
|
63
|
+
<#
|
|
64
|
+
.SYNOPSIS
|
|
65
|
+
Return $true when no live worktree holds parallel checkpoint text that contradicts a no-state stop.
|
|
66
|
+
.DESCRIPTION
|
|
67
|
+
For every live root, the text at the canonical parallel checkpoint path must be null or blank,
|
|
68
|
+
or parse as a JSON object with route_id 'parallel' and a non-blank string parallel_slug that
|
|
69
|
+
differs (ordinally) from the parallel_slug: value the output names, when it names one. Any
|
|
70
|
+
other non-blank text (unparseable, a non-object root, off-route, or without a slug) and a
|
|
71
|
+
checkpoint recording the named slug return $false.
|
|
72
|
+
.PARAMETER AgentOutput
|
|
73
|
+
The agent's final output text, scanned for a parallel_slug: literal.
|
|
74
|
+
.PARAMETER SessionRoot
|
|
75
|
+
The calling process's path, used only to find the repository.
|
|
76
|
+
#>
|
|
77
|
+
[CmdletBinding()]
|
|
78
|
+
[OutputType([bool])]
|
|
79
|
+
param(
|
|
80
|
+
[Parameter(Mandatory = $true)] [AllowNull()] [AllowEmptyString()] [string] $AgentOutput,
|
|
81
|
+
[Parameter(Mandatory = $true)] [string] $SessionRoot
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
$namedSlug = (Find-WorktreeRunIdentitySignal -Text ([string]$AgentOutput)).ParallelSlug
|
|
85
|
+
# The seam returns its array as one object, so it is assigned before it is iterated.
|
|
86
|
+
$liveRoots = Get-WorktreeItemLiveRoot -SessionRoot $SessionRoot
|
|
87
|
+
foreach ($root in $liveRoots) {
|
|
88
|
+
if ([string]::IsNullOrWhiteSpace($root)) { continue }
|
|
89
|
+
$text = Get-WorktreeRunCheckpointText -Path (Get-WorktreeRunCheckpointPath -Kind 'parallel' -WorktreeRoot $root)
|
|
90
|
+
if ([string]::IsNullOrWhiteSpace($text)) { continue }
|
|
91
|
+
try { $parsed = ConvertFrom-Json -InputObject $text -NoEnumerate -ErrorAction Stop } catch { return $false }
|
|
92
|
+
if ($parsed -isnot [System.Management.Automation.PSCustomObject]) { return $false }
|
|
93
|
+
$names = @($parsed.PSObject.Properties.Name)
|
|
94
|
+
if (-not ($names -ccontains 'route_id') -or ([string]$parsed.route_id -cne 'parallel')) { return $false }
|
|
95
|
+
if (-not ($names -ccontains 'parallel_slug')) { return $false }
|
|
96
|
+
$slug = $parsed.parallel_slug
|
|
97
|
+
if ($slug -isnot [string] -or [string]::IsNullOrWhiteSpace($slug)) { return $false }
|
|
98
|
+
if (-not [string]::IsNullOrWhiteSpace($namedSlug) -and ($slug -ceq $namedSlug)) { return $false }
|
|
99
|
+
}
|
|
100
|
+
return $true
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function Get-OrchestratorOutputRejectionDecision {
|
|
104
|
+
<#
|
|
105
|
+
.SYNOPSIS
|
|
106
|
+
Return the allow decision for an unresolved parallel stop that carries a valid rejection signal, or $null.
|
|
107
|
+
.DESCRIPTION
|
|
108
|
+
Returns @{ Ok = $true; Message = $null } only when every gate holds: the artifact type is
|
|
109
|
+
parallel-orchestrator-state, the resolution status is NoTarget, the final line is a valid
|
|
110
|
+
rejection signal, and the state-absence scan passes. Otherwise returns $null, and the caller
|
|
111
|
+
applies its existing block.
|
|
112
|
+
.PARAMETER ArtifactType
|
|
113
|
+
The artifact type the hook validates.
|
|
114
|
+
.PARAMETER Resolution
|
|
115
|
+
The unresolved result of Resolve-OrchestratorOutputCheckpointPath.
|
|
116
|
+
.PARAMETER AgentOutput
|
|
117
|
+
The agent's final output text.
|
|
118
|
+
.PARAMETER SessionRoot
|
|
119
|
+
The calling process's path, used only to find the repository.
|
|
120
|
+
#>
|
|
121
|
+
[CmdletBinding()]
|
|
122
|
+
[OutputType([hashtable])]
|
|
123
|
+
param(
|
|
124
|
+
[Parameter(Mandatory = $true)] [AllowEmptyString()] [string] $ArtifactType,
|
|
125
|
+
[Parameter(Mandatory = $true)] [AllowNull()] [object] $Resolution,
|
|
126
|
+
[Parameter(Mandatory = $true)] [AllowNull()] [AllowEmptyString()] [string] $AgentOutput,
|
|
127
|
+
[Parameter(Mandatory = $true)] [string] $SessionRoot
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
if ($ArtifactType -cne $script:ParallelMutationRejectionArtifactType) { return $null }
|
|
131
|
+
if ($null -eq $Resolution -or [string]$Resolution.Status -cne 'NoTarget') { return $null }
|
|
132
|
+
if ($null -eq (Get-ParallelMutationRejectionCode -AgentOutput $AgentOutput)) { return $null }
|
|
133
|
+
if (-not (Test-ParallelRunStateAbsent -AgentOutput $AgentOutput -SessionRoot $SessionRoot)) { return $null }
|
|
134
|
+
return @{ Ok = $true; Message = $null }
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function Get-ParallelMutationRejectionHint {
|
|
138
|
+
<#
|
|
139
|
+
.SYNOPSIS
|
|
140
|
+
Return the text appended to an unresolved block message.
|
|
141
|
+
.DESCRIPTION
|
|
142
|
+
Pure. Returns an empty string unless the artifact type is parallel-orchestrator-state and the
|
|
143
|
+
status is NoTarget. Otherwise returns one newline-prefixed hint line naming the signal form
|
|
144
|
+
and the rule heading and, when the last non-blank line begins with the signal prefix but
|
|
145
|
+
carries no valid code, a second line listing the allowed codes.
|
|
146
|
+
.PARAMETER ArtifactType
|
|
147
|
+
The artifact type the hook validates.
|
|
148
|
+
.PARAMETER Status
|
|
149
|
+
The resolution status of the block.
|
|
150
|
+
.PARAMETER AgentOutput
|
|
151
|
+
The agent's final output text.
|
|
152
|
+
#>
|
|
153
|
+
[CmdletBinding()]
|
|
154
|
+
[OutputType([string])]
|
|
155
|
+
param(
|
|
156
|
+
[Parameter(Mandatory = $true)] [AllowEmptyString()] [string] $ArtifactType,
|
|
157
|
+
[Parameter(Mandatory = $true)] [AllowNull()] [AllowEmptyString()] [string] $Status,
|
|
158
|
+
[Parameter(Mandatory = $true)] [AllowNull()] [AllowEmptyString()] [string] $AgentOutput
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
if ($ArtifactType -cne $script:ParallelMutationRejectionArtifactType -or $Status -cne 'NoTarget') { return '' }
|
|
162
|
+
$hint = ("`norchestrator hook: a parallel skill that stopped without writing state ends its final output with the line 'PARALLEL_MUTATION_REJECTED: <code>' as the last line, with no formatting; see '{0}' in .claude/rules/parallel-orchestration.md." -f
|
|
163
|
+
$script:ParallelMutationRejectionHeading)
|
|
164
|
+
$trimmed = ([string]$AgentOutput).TrimEnd()
|
|
165
|
+
$lastLine = $trimmed.Substring($trimmed.LastIndexOf("`n") + 1)
|
|
166
|
+
if ($lastLine.StartsWith($script:ParallelMutationRejectionPrefix, [System.StringComparison]::Ordinal) -and
|
|
167
|
+
$null -eq (Get-ParallelMutationRejectionCode -AgentOutput $AgentOutput)) {
|
|
168
|
+
$hint += ("`norchestrator hook: the final line carries the {0} prefix with an invalid code; allowed codes: {1}." -f
|
|
169
|
+
$script:ParallelMutationRejectionPrefix, ($script:ParallelMutationRejectionCodes -join ', '))
|
|
170
|
+
}
|
|
171
|
+
return $hint
|
|
172
|
+
}
|
|
@@ -57,6 +57,7 @@ Set-StrictMode -Version Latest
|
|
|
57
57
|
|
|
58
58
|
try { . (Join-Path $PSScriptRoot 'validate-orchestrator-output-resolution.ps1') } catch { Add-HookDependencyFailure -Name 'validate-orchestrator-output-resolution.ps1' -ErrorRecord $_ }
|
|
59
59
|
try { . (Join-Path $PSScriptRoot 'validate-orchestrator-output-routing.ps1') } catch { Add-HookDependencyFailure -Name 'validate-orchestrator-output-routing.ps1' -ErrorRecord $_ }
|
|
60
|
+
try { . (Join-Path $PSScriptRoot 'validate-orchestrator-output-rejection.ps1') } catch { Add-HookDependencyFailure -Name 'validate-orchestrator-output-rejection.ps1' -ErrorRecord $_ }
|
|
60
61
|
try { Import-Module (Join-Path $PSScriptRoot '../lib/worktree-resolution/WorktreeItemResolution.psm1') -Force -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'WorktreeItemResolution.psm1' -ErrorRecord $_ }
|
|
61
62
|
try { Import-Module (Join-Path $PSScriptRoot '../lib/worktree-resolution/WorktreeRunResolution.psm1') -Force -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'WorktreeRunResolution.psm1' -ErrorRecord $_ }
|
|
62
63
|
try { Import-Module (Join-Path $PSScriptRoot '../lib/orchestrator-state/OrchestratorStateEpicWaveBarrier.psm1') -Force -ErrorAction Stop } catch { Add-HookDependencyFailure -Name 'OrchestratorStateEpicWaveBarrier.psm1' -ErrorRecord $_ }
|
|
@@ -235,6 +236,10 @@ function Get-OrchestratorOutputBlockDecision {
|
|
|
235
236
|
the condition (message content, its checkpoint, or its runbook) and $false when it cannot
|
|
236
237
|
(a registration argument, an internal evaluation failure, or a state it may not clear).
|
|
237
238
|
The caller applies the re-entry policy to that flag.
|
|
239
|
+
An unresolved parallel-orchestrator-state checkpoint (status NoTarget) is allowed when the
|
|
240
|
+
output ends with a valid PARALLEL_MUTATION_REJECTED: <code> line and no live worktree holds
|
|
241
|
+
contradicting parallel checkpoint text (Get-OrchestratorOutputRejectionDecision, issue #1012);
|
|
242
|
+
otherwise the unresolved block carries the hint from Get-ParallelMutationRejectionHint.
|
|
238
243
|
#>
|
|
239
244
|
[CmdletBinding()]
|
|
240
245
|
[OutputType([hashtable])]
|
|
@@ -264,9 +269,12 @@ function Get-OrchestratorOutputBlockDecision {
|
|
|
264
269
|
return @{ Ok = $false; Repairable = $false; Message = ('ORCHESTRATOR_CHECKPOINT_UNRESOLVED: {0}: NoTarget (RESOLVER_FAILED): {1}' -f $ArtifactType, $_.Exception.Message) }
|
|
265
270
|
}
|
|
266
271
|
if (-not $resolution.Resolved) {
|
|
272
|
+
# Issue #1012: a parallel stop that wrote no state and ends with a valid rejection signal is allowed.
|
|
273
|
+
$rejectionDecision = Get-OrchestratorOutputRejectionDecision -ArtifactType $ArtifactType -Resolution $resolution -AgentOutput ([string]$agentOutput) -SessionRoot $SessionRoot
|
|
274
|
+
if ($null -ne $rejectionDecision) { return $rejectionDecision }
|
|
267
275
|
$resolutionRepairable = ($resolution.Status -in @('NoTarget', 'Ambiguous')) -and ($ArtifactType -in @('epic-orchestrator-state', 'parallel-orchestrator-state'))
|
|
268
276
|
return @{ Ok = $false; Repairable = $resolutionRepairable; Message = ('ORCHESTRATOR_CHECKPOINT_UNRESOLVED: {0}: {1} ({2}): {3}' -f
|
|
269
|
-
$ArtifactType, $resolution.Status, $resolution.ReasonCode, $resolution.Detail) }
|
|
277
|
+
$ArtifactType, $resolution.Status, $resolution.ReasonCode, $resolution.Detail) + (Get-ParallelMutationRejectionHint -ArtifactType $ArtifactType -Status ([string]$resolution.Status) -AgentOutput ([string]$agentOutput)) }
|
|
270
278
|
}
|
|
271
279
|
|
|
272
280
|
$file = Get-CheckpointFileContent -Path $resolution.CheckpointPath
|
|
@@ -73,6 +73,7 @@ Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'BlastRadiusValidation.p
|
|
|
73
73
|
Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'BlastRadiusConflict.psm1') -Force -ErrorAction Stop
|
|
74
74
|
Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'BlastRadiusScheduling.psm1') -Force -ErrorAction Stop
|
|
75
75
|
Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'BlastRadiusWriteIntent.psm1') -Force -ErrorAction Stop
|
|
76
|
+
Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'BlastRadiusCombination.psm1') -Force -ErrorAction Stop
|
|
76
77
|
|
|
77
78
|
# Feature-folder handling. Every radius contains its own feature folder, and a
|
|
78
79
|
# caller may pass either a bare folder name or an already-qualified path.
|
|
@@ -246,7 +247,8 @@ function Get-NormalizedDeclaredRadius {
|
|
|
246
247
|
|
|
247
248
|
.OUTPUTS
|
|
248
249
|
System.Collections.Hashtable. A new radius carrying the surviving paths,
|
|
249
|
-
contracts re-filtered by the ASCII-letter rule
|
|
250
|
+
contracts re-filtered by the ASCII-letter rule and the generic sheet-tab
|
|
251
|
+
rule of Test-GenericContractToken (issue #1006), and modules and shared
|
|
250
252
|
surfaces re-resolved from the surviving paths. Throws when the radius
|
|
251
253
|
source is observed: an observed radius records a diff listing rather than
|
|
252
254
|
a plan-text harvest, so the plan-text acceptance rules do not apply to it
|
|
@@ -297,9 +299,12 @@ function Get-NormalizedDeclaredRadius {
|
|
|
297
299
|
-MandateRead ([string[]]@(Get-ConfigMandateRead -Config $Config)))
|
|
298
300
|
$concrete = [string[]]@(Get-ConcreteEntry -Entry $paths)
|
|
299
301
|
|
|
302
|
+
# Keep a recorded contract only when it carries an ASCII letter and is not a
|
|
303
|
+
# default spreadsheet sheet-tab name (issue #1006).
|
|
300
304
|
$contract = [System.Collections.Generic.List[string]]::new()
|
|
301
305
|
foreach ($identifier in @($normalized['contracts'])) {
|
|
302
|
-
if ($script:ContractLetterPattern.IsMatch([string]$identifier)
|
|
306
|
+
if ($script:ContractLetterPattern.IsMatch([string]$identifier) -and
|
|
307
|
+
-not (Test-GenericContractToken -Token ([string]$identifier))) {
|
|
303
308
|
$contract.Add([string]$identifier)
|
|
304
309
|
}
|
|
305
310
|
}
|
|
@@ -472,4 +477,5 @@ Export-ModuleMember -Function `
|
|
|
472
477
|
Get-WriteIntentPlanPath, `
|
|
473
478
|
Get-WriteIntentSpecContract, `
|
|
474
479
|
Select-WriteIntentPathEntry, `
|
|
475
|
-
Get-PlanPathForConfig
|
|
480
|
+
Get-PlanPathForConfig, `
|
|
481
|
+
Get-BlastRadiusCombination
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Aggregate integration-cost cap over tolerated overlaps (issue #1013, D-2).
|
|
4
|
+
|
|
5
|
+
.DESCRIPTION
|
|
6
|
+
Destination-runtime PowerShell mirror of
|
|
7
|
+
scripts/dev_tools/_blast_radius_aggregate.py. The pairwise edge rule judges
|
|
8
|
+
each pair alone, so one item can accumulate many tolerated overlaps whose
|
|
9
|
+
summed cost exceeds what running it serially would cost. Get-AggregateCappedPair
|
|
10
|
+
caps each item's summed tolerated cost and returns the pairs to promote to
|
|
11
|
+
scheduling edges; Get-BlastRadiusConflictEdge applies the result.
|
|
12
|
+
|
|
13
|
+
Cap rule: total(i) is the summed cost of the remaining tolerated overlaps
|
|
14
|
+
that include item i. Item i is over its cap when
|
|
15
|
+
total(i) * 100 > band_duration(i) * tolerance_percent, evaluated in
|
|
16
|
+
integers; a missing band reads as default_band. While an item is over its
|
|
17
|
+
cap, the smallest-key such item promotes its overlap with the minimum key
|
|
18
|
+
(benefit ascending, cost descending, a ascending, b ascending).
|
|
19
|
+
|
|
20
|
+
Parity notes for maintainers:
|
|
21
|
+
- The Python module is the reference implementation; the committed
|
|
22
|
+
aggregate fixtures bind the two runtimes together.
|
|
23
|
+
- Every function is pure: no filesystem, subprocess, network, or clock
|
|
24
|
+
access, and no input is mutated. Integers only.
|
|
25
|
+
CONVENTION: this module fails fast at module scope and imports its siblings with -ErrorAction Stop.
|
|
26
|
+
#>
|
|
27
|
+
|
|
28
|
+
Set-StrictMode -Version Latest
|
|
29
|
+
$ErrorActionPreference = 'Stop'
|
|
30
|
+
|
|
31
|
+
# tolerance_percent is a percentage of the band duration.
|
|
32
|
+
$script:Percent = 100
|
|
33
|
+
|
|
34
|
+
# Resolve an item's band duration, reading a missing band as default_band.
|
|
35
|
+
function Get-AggregateBandDuration {
|
|
36
|
+
[CmdletBinding()]
|
|
37
|
+
[OutputType([long])]
|
|
38
|
+
param(
|
|
39
|
+
[AllowNull()]
|
|
40
|
+
[object] $Band,
|
|
41
|
+
[Parameter(Mandatory = $true)]
|
|
42
|
+
[hashtable] $Tolerance
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
$resolved = if ($null -eq $Band) { $Tolerance['default_band'] } else { $Band }
|
|
46
|
+
$duration = $Tolerance['band_durations']
|
|
47
|
+
if ($resolved -isnot [string] -or -not $duration.ContainsKey($resolved)) {
|
|
48
|
+
throw "band '$resolved' must be one of $(@($duration.Keys | Sort-Object) -join ', ')."
|
|
49
|
+
}
|
|
50
|
+
return [long]$duration[$resolved]
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
# Return the smallest item key whose tolerated total exceeds its cap, or $null.
|
|
54
|
+
function Get-FirstOverCapItem {
|
|
55
|
+
[CmdletBinding()]
|
|
56
|
+
[OutputType([long])]
|
|
57
|
+
param(
|
|
58
|
+
[Parameter(Mandatory = $true)]
|
|
59
|
+
[AllowEmptyCollection()]
|
|
60
|
+
[object[]] $Remaining,
|
|
61
|
+
[Parameter(Mandatory = $true)]
|
|
62
|
+
[hashtable] $BandByKey,
|
|
63
|
+
[Parameter(Mandatory = $true)]
|
|
64
|
+
[hashtable] $Tolerance
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# Each overlap's cost counts toward both of its items.
|
|
68
|
+
$total = @{}
|
|
69
|
+
foreach ($overlap in $Remaining) {
|
|
70
|
+
foreach ($key in @($overlap['a'], $overlap['b'])) {
|
|
71
|
+
$current = if ($total.ContainsKey($key)) { $total[$key] } else { [long]0 }
|
|
72
|
+
$total[$key] = $current + $overlap['cost']
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
$percent = [long]$Tolerance['tolerance_percent']
|
|
76
|
+
foreach ($key in @($total.Keys | Sort-Object)) {
|
|
77
|
+
$band = if ($BandByKey.ContainsKey($key)) { $BandByKey[$key] } else { $null }
|
|
78
|
+
$cap = (Get-AggregateBandDuration -Band $band -Tolerance $Tolerance) * $percent
|
|
79
|
+
if ($total[$key] * $script:Percent -gt $cap) {
|
|
80
|
+
return $key
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return $null
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function Get-AggregateCappedPair {
|
|
87
|
+
<#
|
|
88
|
+
.SYNOPSIS
|
|
89
|
+
Return the tolerated pairs to promote so every item is within its cap.
|
|
90
|
+
|
|
91
|
+
.DESCRIPTION
|
|
92
|
+
Port of select_capped_pairs. Repeatedly takes the smallest-key item that
|
|
93
|
+
is over its cap, promotes its cheapest-to-serialize overlap, and
|
|
94
|
+
recomputes the totals, until no item is over its cap.
|
|
95
|
+
|
|
96
|
+
.PARAMETER ToleratedOverlap
|
|
97
|
+
The pairwise tolerated overlaps, in any order: records carrying a, b,
|
|
98
|
+
cost, and benefit.
|
|
99
|
+
|
|
100
|
+
.PARAMETER Band
|
|
101
|
+
Item key to complexity band; a key that is absent or maps to null reads
|
|
102
|
+
as default_band.
|
|
103
|
+
|
|
104
|
+
.PARAMETER Tolerance
|
|
105
|
+
A value returned by Get-ConfigConflictTolerance.
|
|
106
|
+
|
|
107
|
+
.OUTPUTS
|
|
108
|
+
System.Collections.Specialized.OrderedDictionary. The promoted pairs in
|
|
109
|
+
promotion order, each an ordered record with keys a and b, written to
|
|
110
|
+
the pipeline; nothing when no item is over its cap. Wrap the call in
|
|
111
|
+
@() to read the result as an array. Throws when a band is not a
|
|
112
|
+
configured band.
|
|
113
|
+
#>
|
|
114
|
+
[CmdletBinding()]
|
|
115
|
+
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
116
|
+
param(
|
|
117
|
+
[Parameter(Mandatory = $true)]
|
|
118
|
+
[AllowEmptyCollection()]
|
|
119
|
+
[object[]] $ToleratedOverlap,
|
|
120
|
+
[Parameter(Mandatory = $true)]
|
|
121
|
+
[hashtable] $Band,
|
|
122
|
+
[Parameter(Mandatory = $true)]
|
|
123
|
+
[hashtable] $Tolerance
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
# Copy every overlap and band key into Int64 records, so integer keys of any
|
|
127
|
+
# width compare equal and no input is mutated.
|
|
128
|
+
$bandByKey = @{}
|
|
129
|
+
foreach ($key in $Band.Keys) { $bandByKey[[long]$key] = $Band[$key] }
|
|
130
|
+
$copied = foreach ($overlap in $ToleratedOverlap) {
|
|
131
|
+
[ordered]@{ a = [long]$overlap['a']; b = [long]$overlap['b']; cost = [long]$overlap['cost']; benefit = [long]$overlap['benefit'] }
|
|
132
|
+
}
|
|
133
|
+
$remaining = [System.Collections.Generic.List[object]]::new()
|
|
134
|
+
foreach ($overlap in @($copied | Sort-Object -Property { $_['a'] }, { $_['b'] })) {
|
|
135
|
+
if ($null -ne $overlap) { $remaining.Add($overlap) }
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
$promoted = [System.Collections.Generic.List[object]]::new()
|
|
139
|
+
while ($true) {
|
|
140
|
+
$item = Get-FirstOverCapItem -Remaining @($remaining.ToArray()) -BandByKey $bandByKey -Tolerance $Tolerance
|
|
141
|
+
if ($null -eq $item) {
|
|
142
|
+
# Callers wrap the enumerated records with @() so an empty result is
|
|
143
|
+
# an empty array.
|
|
144
|
+
return $promoted.ToArray()
|
|
145
|
+
}
|
|
146
|
+
# Cheapest to serialize: least benefit, then most cost, then pair keys.
|
|
147
|
+
$cheapest = @($remaining | Where-Object { $_['a'] -eq $item -or $_['b'] -eq $item } |
|
|
148
|
+
Sort-Object -Property { $_['benefit'] }, { - $_['cost'] }, { $_['a'] }, { $_['b'] })[0]
|
|
149
|
+
[void]$remaining.Remove($cheapest)
|
|
150
|
+
$promoted.Add([ordered]@{ a = $cheapest['a']; b = $cheapest['b'] })
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
Export-ModuleMember -Function Get-AggregateCappedPair
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Planning-time issue combination over the scheduling pair data (issue #1013).
|
|
4
|
+
|
|
5
|
+
.DESCRIPTION
|
|
6
|
+
Destination-runtime PowerShell mirror of
|
|
7
|
+
scripts/dev_tools/_blast_radius_combination.py (spec D-5). Finds the pairs
|
|
8
|
+
of work items whose integration cost is high enough that delivering them as
|
|
9
|
+
one item costs less than merging them, and groups them into combinations.
|
|
10
|
+
|
|
11
|
+
Rules:
|
|
12
|
+
1. Disabled when conflict_tolerance is absent or tolerance_percent is 0.
|
|
13
|
+
2. Get-BlastRadiusPairDecision supplies each conflicting pair's reasons,
|
|
14
|
+
cost (including the D-7 contract term), and benefit; edges and
|
|
15
|
+
tolerated pairs are both evaluated.
|
|
16
|
+
3. A pair is a candidate when its full reasons contain
|
|
17
|
+
contract_dependency or it has a same-file overlap: an overlapping entry
|
|
18
|
+
pair, after the mergeable exclusion, of two equal concrete entries that
|
|
19
|
+
do not match append_only_paths.
|
|
20
|
+
4. A candidate qualifies when
|
|
21
|
+
cost * 100 >= benefit * combination_threshold_percent.
|
|
22
|
+
5. Qualifying pairs are grouped into connected components (union-find);
|
|
23
|
+
each becomes one combination with ascending members, primary equal to
|
|
24
|
+
the smallest member, pairs sorted by (a, b), and estimated_savings equal
|
|
25
|
+
to the sum of its pair costs.
|
|
26
|
+
|
|
27
|
+
Parity notes for maintainers:
|
|
28
|
+
- The Python module is the reference implementation; the committed
|
|
29
|
+
combination fixtures bind the two runtimes together.
|
|
30
|
+
- Every function is pure: no filesystem, subprocess, network, or clock
|
|
31
|
+
access, and no input is mutated.
|
|
32
|
+
CONVENTION: this module fails fast at module scope and imports its siblings with -ErrorAction Stop.
|
|
33
|
+
#>
|
|
34
|
+
|
|
35
|
+
Set-StrictMode -Version Latest
|
|
36
|
+
$ErrorActionPreference = 'Stop'
|
|
37
|
+
|
|
38
|
+
Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'BlastRadiusGlob.psm1') -Force -ErrorAction Stop
|
|
39
|
+
Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'BlastRadiusConflict.psm1') -Force -ErrorAction Stop
|
|
40
|
+
Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'BlastRadiusValidation.psm1') -Force -ErrorAction Stop
|
|
41
|
+
Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'BlastRadiusScheduling.psm1') -Force -ErrorAction Stop
|
|
42
|
+
|
|
43
|
+
# The threshold is a percentage of the pair benefit.
|
|
44
|
+
$script:Percent = 100
|
|
45
|
+
$script:ContractDependency = 'contract_dependency'
|
|
46
|
+
|
|
47
|
+
# Port of has_same_file_overlap: two equal, non-mergeable, non-append-only
|
|
48
|
+
# concrete entries among the overlapping pairs the relation enumerates.
|
|
49
|
+
function Test-SameFileOverlap {
|
|
50
|
+
[CmdletBinding()]
|
|
51
|
+
[OutputType([bool])]
|
|
52
|
+
param(
|
|
53
|
+
[Parameter(Mandatory = $true)]
|
|
54
|
+
[AllowNull()]
|
|
55
|
+
[object] $RadiusA,
|
|
56
|
+
[Parameter(Mandatory = $true)]
|
|
57
|
+
[AllowNull()]
|
|
58
|
+
[object] $RadiusB,
|
|
59
|
+
[Parameter(Mandatory = $true)]
|
|
60
|
+
[AllowNull()]
|
|
61
|
+
[object] $Config,
|
|
62
|
+
[Parameter(Mandatory = $true)]
|
|
63
|
+
[hashtable] $Tolerance
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
$left = ConvertTo-NormalizedBlastRadius -Radius $RadiusA
|
|
67
|
+
$right = ConvertTo-NormalizedBlastRadius -Radius $RadiusB
|
|
68
|
+
$mergeable = [string[]]@(Get-ConfigMergeablePath -Config $Config)
|
|
69
|
+
$pathsA = @(Get-NonMergeablePathEntry -Entry ([string[]]@($left['paths'])) -MergeablePath $mergeable)
|
|
70
|
+
$pathsB = @(Get-NonMergeablePathEntry -Entry ([string[]]@($right['paths'])) -MergeablePath $mergeable)
|
|
71
|
+
$appendOnly = [string[]]@($Tolerance['append_only_paths'])
|
|
72
|
+
foreach ($pair in @(Get-OverlappingPathPair -PathA $pathsA -PathB $pathsB)) {
|
|
73
|
+
$entry = [string]$pair['EntryA']
|
|
74
|
+
$isEqual = [string]::Equals($entry, [string]$pair['EntryB'], [System.StringComparison]::Ordinal)
|
|
75
|
+
if ($isEqual -and -not (Test-GlobEntry -Entry $entry) -and -not (Test-MergeablePath -Entry $entry -MergeablePath $appendOnly)) {
|
|
76
|
+
return $true
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return $false
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
# Validate item keys (distinct integers) and return records sorted by key.
|
|
83
|
+
function Get-CombinationOrderedItem {
|
|
84
|
+
[CmdletBinding()]
|
|
85
|
+
[OutputType([System.Object[]])]
|
|
86
|
+
param(
|
|
87
|
+
[Parameter(Mandatory = $true)]
|
|
88
|
+
[AllowEmptyCollection()]
|
|
89
|
+
[object[]] $Item
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
$seen = [System.Collections.Generic.HashSet[long]]::new()
|
|
93
|
+
$record = foreach ($entry in $Item) {
|
|
94
|
+
$key = [long]$entry['key']
|
|
95
|
+
if (-not $seen.Add($key)) {
|
|
96
|
+
throw "combination item keys must be distinct: $key."
|
|
97
|
+
}
|
|
98
|
+
$band = if ($entry.Contains('band')) { $entry['band'] } else { $null }
|
|
99
|
+
@{ key = $key; radius = $entry['radius']; band = $band }
|
|
100
|
+
}
|
|
101
|
+
return @(@($record) | Where-Object { $null -ne $_ } | Sort-Object -Property { $_['key'] })
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
# Return the union-find root of Key, registering Key as its own root when new.
|
|
105
|
+
# Parent is the component's working map, owned by Get-CombinationComponent.
|
|
106
|
+
function Get-UnionFindRoot {
|
|
107
|
+
[CmdletBinding()]
|
|
108
|
+
[OutputType([long])]
|
|
109
|
+
param(
|
|
110
|
+
[Parameter(Mandatory = $true)]
|
|
111
|
+
[hashtable] $Parent,
|
|
112
|
+
[Parameter(Mandatory = $true)]
|
|
113
|
+
[long] $Key
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
if (-not $Parent.ContainsKey($Key)) { $Parent[$Key] = $Key }
|
|
117
|
+
$root = $Key
|
|
118
|
+
while ($Parent[$root] -ne $root) { $root = $Parent[$root] }
|
|
119
|
+
return $root
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
# Group qualifying pairs into connected components with union-find.
|
|
123
|
+
function Get-CombinationComponent {
|
|
124
|
+
[CmdletBinding()]
|
|
125
|
+
[OutputType([System.Object[]])]
|
|
126
|
+
param(
|
|
127
|
+
[Parameter(Mandatory = $true)]
|
|
128
|
+
[AllowEmptyCollection()]
|
|
129
|
+
[object[]] $Pair
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
$parent = @{}
|
|
133
|
+
foreach ($entry in $Pair) {
|
|
134
|
+
$rootA = Get-UnionFindRoot -Parent $parent -Key $entry['a']
|
|
135
|
+
$rootB = Get-UnionFindRoot -Parent $parent -Key $entry['b']
|
|
136
|
+
if ($rootA -ne $rootB) {
|
|
137
|
+
$parent[[System.Math]::Max($rootA, $rootB)] = [System.Math]::Min($rootA, $rootB)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
$grouped = @{}
|
|
142
|
+
foreach ($entry in $Pair) {
|
|
143
|
+
$root = Get-UnionFindRoot -Parent $parent -Key $entry['a']
|
|
144
|
+
if (-not $grouped.ContainsKey($root)) { $grouped[$root] = [System.Collections.Generic.List[object]]::new() }
|
|
145
|
+
$grouped[$root].Add($entry)
|
|
146
|
+
}
|
|
147
|
+
$combination = foreach ($root in @($grouped.Keys | Sort-Object)) {
|
|
148
|
+
$component = @($grouped[$root].ToArray() | Sort-Object -Property { $_['a'] }, { $_['b'] })
|
|
149
|
+
[long[]]$member = @($component | ForEach-Object { $_['a']; $_['b'] } | Sort-Object -Unique)
|
|
150
|
+
[long]$savings = 0
|
|
151
|
+
foreach ($entry in $component) { $savings += $entry['cost'] }
|
|
152
|
+
[ordered]@{ members = $member; primary = $member[0]; pairs = $component; estimated_savings = $savings }
|
|
153
|
+
}
|
|
154
|
+
return @($combination)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function Get-BlastRadiusCombination {
|
|
158
|
+
<#
|
|
159
|
+
.SYNOPSIS
|
|
160
|
+
Find the qualifying pairs of a set of items and group them.
|
|
161
|
+
|
|
162
|
+
.DESCRIPTION
|
|
163
|
+
Port of find_combinations. Visits every unordered pair once in ascending
|
|
164
|
+
key order and applies D-5 rules 1 to 5.
|
|
165
|
+
|
|
166
|
+
.PARAMETER Item
|
|
167
|
+
The run's items: records carrying key (an integer issue number), radius,
|
|
168
|
+
and an optional band.
|
|
169
|
+
|
|
170
|
+
.PARAMETER Config
|
|
171
|
+
Parsed truth table.
|
|
172
|
+
|
|
173
|
+
.PARAMETER Relation
|
|
174
|
+
The detection relation passed to every pair decision; pass
|
|
175
|
+
${function:Test-BlastRadiusConflict}.
|
|
176
|
+
|
|
177
|
+
.OUTPUTS
|
|
178
|
+
System.Collections.Hashtable. Keys pairs (ordered records a, b, cost,
|
|
179
|
+
benefit, sorted by (a, b)) and combinations (ordered records members,
|
|
180
|
+
primary, pairs, estimated_savings, sorted by primary). Both are empty
|
|
181
|
+
when combination is disabled.
|
|
182
|
+
#>
|
|
183
|
+
[CmdletBinding()]
|
|
184
|
+
[OutputType([hashtable])]
|
|
185
|
+
param(
|
|
186
|
+
[Parameter(Mandatory = $true)]
|
|
187
|
+
[AllowEmptyCollection()]
|
|
188
|
+
[object[]] $Item,
|
|
189
|
+
[Parameter(Mandatory = $true)]
|
|
190
|
+
[AllowNull()]
|
|
191
|
+
[object] $Config,
|
|
192
|
+
[scriptblock] $Relation
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
if ($null -eq $Relation) {
|
|
196
|
+
throw '-Relation is required: pass ${function:Test-BlastRadiusConflict} from the facade module BlastRadius.psm1.'
|
|
197
|
+
}
|
|
198
|
+
$tolerance = Get-ConfigConflictTolerance -Config $Config
|
|
199
|
+
$ordered = @(Get-CombinationOrderedItem -Item $Item)
|
|
200
|
+
# The absent key reads as the strict tolerance, whose percent is 0.
|
|
201
|
+
if ([long]$tolerance['tolerance_percent'] -eq 0) {
|
|
202
|
+
return @{ pairs = @(); combinations = @() }
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
$threshold = [long]$tolerance['combination_threshold_percent']
|
|
206
|
+
$qualifying = [System.Collections.Generic.List[object]]::new()
|
|
207
|
+
for ($first = 0; $first -lt $ordered.Count; $first++) {
|
|
208
|
+
for ($second = $first + 1; $second -lt $ordered.Count; $second++) {
|
|
209
|
+
$a = $ordered[$first]
|
|
210
|
+
$b = $ordered[$second]
|
|
211
|
+
$decision = Get-BlastRadiusPairDecision -RadiusA $a['radius'] -RadiusB $b['radius'] -Config $Config -BandA $a['band'] -BandB $b['band'] -Relation $Relation
|
|
212
|
+
if (-not $decision['conflict']) { continue }
|
|
213
|
+
# The full reasons are read, because contract_dependency is never first.
|
|
214
|
+
$isCandidate = @($decision['reasons']) -ccontains $script:ContractDependency
|
|
215
|
+
if (-not $isCandidate) {
|
|
216
|
+
$isCandidate = Test-SameFileOverlap -RadiusA $a['radius'] -RadiusB $b['radius'] -Config $Config -Tolerance $tolerance
|
|
217
|
+
}
|
|
218
|
+
if ($isCandidate -and ($decision['cost'] * $script:Percent -ge $decision['benefit'] * $threshold)) {
|
|
219
|
+
$qualifying.Add([ordered]@{ a = $a['key']; b = $b['key']; cost = [long]$decision['cost']; benefit = [long]$decision['benefit'] })
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
$pair = @($qualifying.ToArray())
|
|
225
|
+
return @{ pairs = $pair; combinations = @(Get-CombinationComponent -Pair $pair) }
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
Export-ModuleMember -Function Get-BlastRadiusCombination
|
package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusNormalization.psm1
CHANGED
|
@@ -120,10 +120,12 @@ function Get-ContractIdentifier {
|
|
|
120
120
|
# Inside a qualifying section an inline-code token without a separator is
|
|
121
121
|
# a contract identifier; a token with one is a path reference and is
|
|
122
122
|
# recorded at the paths level instead. A token carrying no ASCII letter
|
|
123
|
-
# is notation, not an identifier, and is dropped (issue #489)
|
|
123
|
+
# is notation, not an identifier, and is dropped (issue #489), as is a
|
|
124
|
+
# default spreadsheet sheet-tab name such as Sheet1 (issue #1006).
|
|
124
125
|
foreach ($token in @(Get-InlineCodeToken -Line $line)) {
|
|
125
126
|
if ($token.IndexOf('/') -lt 0 -and
|
|
126
|
-
$script:ContractLetterPattern.IsMatch($token)
|
|
127
|
+
$script:ContractLetterPattern.IsMatch($token) -and
|
|
128
|
+
-not (Test-GenericContractToken -Token $token)) {
|
|
127
129
|
$identifier.Add($token)
|
|
128
130
|
}
|
|
129
131
|
}
|