@danmoisan/drm-copilot-mcp 1.0.24 → 1.0.27
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 +1045 -214
- package/package.json +1 -1
- package/resources/claude-customizations/.claude/agents/feature-review.md +5 -3
- package/resources/claude-customizations/.claude/agents/parallel-orchestrator.md +11 -4
- package/resources/claude-customizations/.claude/agents/parallel-planner.md +5 -2
- package/resources/claude-customizations/.claude/hooks/enforce-discovery-artifact-gate.ps1 +28 -8
- package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate.ps1 +109 -5
- package/resources/claude-customizations/.claude/hooks/enforce-mermaid-validation.ps1 +390 -0
- package/resources/claude-customizations/.claude/hooks/validate-discovery-artifact-gate.ps1 +28 -8
- package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output.ps1 +117 -46
- package/resources/claude-customizations/.claude/lib/bash/parallel-manifest-validate.sh +115 -3
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadius.psm1 +105 -2
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusConfig.psm1 +32 -52
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusExtraction.psm1 +107 -99
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusNormalization.psm1 +295 -0
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusValidation.psm1 +9 -3
- package/resources/claude-customizations/.claude/lib/codex-routing/CodexDeployment.psm1 +312 -0
- package/resources/claude-customizations/.claude/lib/codex-routing/CodexTopology.psm1 +392 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryValidation.psm1 +500 -0
- package/resources/claude-customizations/.claude/lib/mermaid/MermaidGrammar.psm1 +491 -0
- package/resources/claude-customizations/.claude/lib/mermaid/MermaidLineScanner.psm1 +488 -0
- package/resources/claude-customizations/.claude/lib/mermaid/MermaidMarkdownFences.psm1 +298 -0
- package/resources/claude-customizations/.claude/lib/mermaid/MermaidValidation.psm1 +496 -0
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorState.psm1 +58 -67
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateCheckpointValue.psm1 +383 -0
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateCodexModelReceipts.psm1 +297 -0
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateCodexTopologyReceipts.psm1 +298 -0
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateCompletion.psm1 +232 -43
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateCompletionChecks.psm1 +416 -0
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateModelReceipts.psm1 +366 -0
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateReceipts.psm1 +408 -0
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateRoutingContract.psm1 +428 -0
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateRoutingMatrix.psm1 +377 -0
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateUnconditional.psm1 +166 -0
- package/resources/claude-customizations/.claude/rules/general-unit-test.md +1 -1
- package/resources/claude-customizations/.claude/rules/mermaid.md +142 -0
- package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +88 -3
- package/resources/claude-customizations/.claude/rules/plan-acceptance-gates.md +116 -0
- package/resources/claude-customizations/.claude/rules/powershell.md +1 -1
- package/resources/claude-customizations/.claude/rules/quality-tiers.md +3 -3
- package/resources/claude-customizations/.claude/settings.json +5 -0
- package/resources/claude-customizations/.claude/skills/atomic-plan-contract/SKILL.md +15 -0
- package/resources/claude-customizations/.claude/skills/evidence-and-timestamp-conventions/SKILL.md +13 -0
- package/resources/claude-customizations/.claude/skills/feature-promotion-lifecycle/SKILL.md +6 -0
- package/resources/claude-customizations/.claude/skills/feature-review-workflow/SKILL.md +4 -4
- package/resources/claude-customizations/.claude/skills/mermaid-diagram/SKILL.md +184 -0
- package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/c4.md +50 -0
- package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/class.md +63 -0
- package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/er.md +56 -0
- package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/flowchart.md +68 -0
- package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/gantt.md +51 -0
- package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/other-types.md +82 -0
- package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/pie.md +32 -0
- package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/sequence.md +63 -0
- package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/state.md +49 -0
- package/resources/claude-customizations/.claude/skills/parallel-add/SKILL.md +10 -5
- package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +116 -41
- package/resources/claude-customizations/.claude/skills/parallel-plan/SKILL.md +95 -13
- package/resources/claude-customizations/.claude/skills/parallel-remove/SKILL.md +7 -3
- package/resources/claude-customizations/.claude/skills/powershell-qa-gate/SKILL.md +1 -1
- package/resources/claude-customizations/config/blast-radius.json +9 -3
- package/resources/claude-customizations/pack-manifests/core.json +31 -1
- package/resources/codex-and-agents-customizations/.agents/skills/evidence-and-timestamp-conventions/SKILL.md +13 -0
- package/resources/codex-and-agents-customizations/.agents/skills/general-unit-test/SKILL.md +1 -1
- package/resources/codex-and-agents-customizations/.agents/skills/quality-tiers/SKILL.md +3 -3
- package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
- package/resources/customizations/.github/skills/evidence-and-timestamp-conventions/SKILL.md +13 -0
- package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +37 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# parallel-manifest-validate.sh: sourceable bash port of
|
|
3
3
|
# scripts/dev_tools/parallel_manifest_contract.py. Validates a parallel-run
|
|
4
|
-
# manifest document against invariants M1 through
|
|
4
|
+
# manifest document against invariants M1 through M8 and exposes the two
|
|
5
5
|
# default-resolving accessors that every consumer uses instead of reading
|
|
6
6
|
# `mode` and `max_concurrency` directly.
|
|
7
7
|
#
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
# frontmatter checks short-circuit with a single error; then run identity
|
|
10
10
|
# (M2 parallel, M3 mode, M4 max_concurrency, M5 created_at) in schema field
|
|
11
11
|
# order; then the M7 prohibited-key scan, deep results in document order
|
|
12
|
-
# followed by the top-level results; then the M6 items collection
|
|
12
|
+
# followed by the top-level results; then the M6 items collection; then the
|
|
13
|
+
# key-gated M8 expected_conflict_components assertion.
|
|
13
14
|
#
|
|
14
15
|
# Every error string begins with the literal prefix `Parallel manifest` and
|
|
15
16
|
# ends with a period. The Python module remains the repository authority.
|
|
@@ -43,7 +44,7 @@ PM_DEFAULT_MAX_CONCURRENCY=4
|
|
|
43
44
|
|
|
44
45
|
# Inclusive bounds on a present `max_concurrency` (invariant M4, A7).
|
|
45
46
|
PM_MIN_CONCURRENCY=1
|
|
46
|
-
PM_MAX_CONCURRENCY=
|
|
47
|
+
PM_MAX_CONCURRENCY=32
|
|
47
48
|
|
|
48
49
|
# Keys the manifest rejects at any nesting level (invariant M7).
|
|
49
50
|
PM_DEEP_PROHIBITED_KEYS="depends_on"
|
|
@@ -54,6 +55,11 @@ PM_TOP_LEVEL_PROHIBITED_KEYS="integration_branch"
|
|
|
54
55
|
# Detail recorded when the parser refuses an out-of-subset construct.
|
|
55
56
|
PM_SUBSET_DETAIL=""
|
|
56
57
|
|
|
58
|
+
# Issue numbers already claimed by an earlier expected_conflict_components
|
|
59
|
+
# entry (invariant M8). Threaded across components so cross-component duplicate
|
|
60
|
+
# membership is decided in one pass; reset by pm_validate_text.
|
|
61
|
+
PM_CLAIMED=""
|
|
62
|
+
|
|
57
63
|
pm_parse_manifest() {
|
|
58
64
|
# Parse manifest text into the shared node table (invariant M1).
|
|
59
65
|
#
|
|
@@ -128,6 +134,108 @@ pm_validate_identity() {
|
|
|
128
134
|
fi
|
|
129
135
|
}
|
|
130
136
|
|
|
137
|
+
pm_declared_issue_nums() {
|
|
138
|
+
# Echo the space-separated issue_num values the items collection declares.
|
|
139
|
+
#
|
|
140
|
+
# Supplies the resolution target for invariant M8 without re-running the M6
|
|
141
|
+
# item validation: a malformed entry is reported once by M6 and simply
|
|
142
|
+
# contributes no resolvable key here.
|
|
143
|
+
local declared="" count index entry_path issue_type issue_value
|
|
144
|
+
[[ $(yp_type_of "items") == seq ]] || return 0
|
|
145
|
+
count=$(yp_count_of "items")
|
|
146
|
+
# Accept only entries whose primary key is already well formed; admitting a
|
|
147
|
+
# malformed one would produce a second, confusing M8 error for one defect.
|
|
148
|
+
for ((index = 0; index < count; index++)); do
|
|
149
|
+
entry_path="items[${index}]"
|
|
150
|
+
[[ $(yp_type_of "$entry_path") == map ]] || continue
|
|
151
|
+
issue_type=$(yp_type_of "${entry_path}.issue_num")
|
|
152
|
+
issue_value=$(yp_value_of "${entry_path}.issue_num")
|
|
153
|
+
pc_is_positive_integer "$issue_type" "$issue_value" || continue
|
|
154
|
+
declared="$declared $issue_value"
|
|
155
|
+
done
|
|
156
|
+
printf '%s' "$declared"
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
pm_validate_component_members() {
|
|
160
|
+
# Validate one component's members list against invariant M8.
|
|
161
|
+
#
|
|
162
|
+
# Args: $1 = node path of the members list, $2 = component-scoped context
|
|
163
|
+
# prefix, $3 = space-separated declared issue_num values. Appends every
|
|
164
|
+
# accepted key to PM_CLAIMED so the caller's running set enforces the
|
|
165
|
+
# no-duplicate-membership rule across components.
|
|
166
|
+
local path="$1" ctx="$2" declared="$3"
|
|
167
|
+
local members_type count=0 index slot entry_path member_type member_value
|
|
168
|
+
members_type=$(yp_type_of "$path")
|
|
169
|
+
if [[ $members_type == seq ]]; then
|
|
170
|
+
count=$(yp_count_of "$path")
|
|
171
|
+
fi
|
|
172
|
+
# A missing key, a scalar, and an empty list are one violation: the
|
|
173
|
+
# component asserts no membership and therefore carries no information.
|
|
174
|
+
if [[ $members_type != seq ]] || ((count == 0)); then
|
|
175
|
+
pc_error_add "$ctx members must be a non-empty list of positive integers."
|
|
176
|
+
return 0
|
|
177
|
+
fi
|
|
178
|
+
|
|
179
|
+
# Three successive gates per member; the first failure ends that member's
|
|
180
|
+
# checks, because an unusable value cannot be resolved and an unresolved key
|
|
181
|
+
# cannot meaningfully duplicate another.
|
|
182
|
+
for ((index = 0; index < count; index++)); do
|
|
183
|
+
entry_path="${path}[${index}]"
|
|
184
|
+
slot="$ctx members[${index}]"
|
|
185
|
+
member_type=$(yp_type_of "$entry_path")
|
|
186
|
+
member_value=$(yp_value_of "$entry_path")
|
|
187
|
+
if ! pc_is_positive_integer "$member_type" "$member_value"; then
|
|
188
|
+
pc_error_add "$slot must be a positive integer; found: $(pi_repr_at "$entry_path")."
|
|
189
|
+
continue
|
|
190
|
+
fi
|
|
191
|
+
if ! pc_contains_word "$declared" "$member_value"; then
|
|
192
|
+
pc_error_add "$slot does not resolve to an items[] issue_num; found: $member_value."
|
|
193
|
+
continue
|
|
194
|
+
fi
|
|
195
|
+
if pc_contains_word "$PM_CLAIMED" "$member_value"; then
|
|
196
|
+
pc_error_add "$slot repeats issue_num $member_value, already claimed by an earlier component."
|
|
197
|
+
continue
|
|
198
|
+
fi
|
|
199
|
+
PM_CLAIMED="$PM_CLAIMED $member_value"
|
|
200
|
+
done
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
pm_validate_expected_components() {
|
|
204
|
+
# Validate the optional expected_conflict_components key (invariant M8).
|
|
205
|
+
#
|
|
206
|
+
# Key gated: absence is the overwhelmingly common shape and must cost the
|
|
207
|
+
# caller nothing, so the whole invariant is skipped rather than defaulted
|
|
208
|
+
# and a pre-M8 manifest's error list is unchanged.
|
|
209
|
+
local root="expected_conflict_components"
|
|
210
|
+
yp_has "$root" || return 0
|
|
211
|
+
if [[ $(yp_type_of "$root") != seq ]]; then
|
|
212
|
+
pc_error_add "$PM_CONTEXT $root must be a list."
|
|
213
|
+
return 0
|
|
214
|
+
fi
|
|
215
|
+
|
|
216
|
+
local declared total position comp_path ctx name_type name_value
|
|
217
|
+
declared=$(pm_declared_issue_nums)
|
|
218
|
+
total=$(yp_count_of "$root")
|
|
219
|
+
for ((position = 0; position < total; position++)); do
|
|
220
|
+
comp_path="${root}[${position}]"
|
|
221
|
+
ctx="$PM_CONTEXT ${root}[${position}]"
|
|
222
|
+
if [[ $(yp_type_of "$comp_path") != map ]]; then
|
|
223
|
+
pc_error_add "$ctx must be an object."
|
|
224
|
+
continue
|
|
225
|
+
fi
|
|
226
|
+
# Field order follows the documented authoring order: the optional
|
|
227
|
+
# diagnostic label first, then the required membership list.
|
|
228
|
+
if yp_has "${comp_path}.name"; then
|
|
229
|
+
name_type=$(yp_type_of "${comp_path}.name")
|
|
230
|
+
name_value=$(yp_value_of "${comp_path}.name")
|
|
231
|
+
if ! pc_is_non_empty_string "$name_type" "$name_value"; then
|
|
232
|
+
pc_error_add "$ctx name must be a non-empty string."
|
|
233
|
+
fi
|
|
234
|
+
fi
|
|
235
|
+
pm_validate_component_members "${comp_path}.members" "$ctx" "$declared"
|
|
236
|
+
done
|
|
237
|
+
}
|
|
238
|
+
|
|
131
239
|
pm_validate_text() {
|
|
132
240
|
# Validate a manifest document against invariants M1 to M7.
|
|
133
241
|
#
|
|
@@ -139,6 +247,7 @@ pm_validate_text() {
|
|
|
139
247
|
local text="$1" parse_status=0
|
|
140
248
|
pc_errors_reset
|
|
141
249
|
PM_SUBSET_DETAIL=""
|
|
250
|
+
PM_CLAIMED=""
|
|
142
251
|
pm_parse_manifest "$text" || parse_status=$?
|
|
143
252
|
# An out-of-subset refusal propagates so the caller can distinguish it from
|
|
144
253
|
# a validation verdict; an M1 failure returns the single-element error list
|
|
@@ -156,6 +265,9 @@ pm_validate_text() {
|
|
|
156
265
|
# The manifest carries `kind` on every item, unlike the orchestrator
|
|
157
266
|
# checkpoint, so the shared item validator is asked to require it (S1).
|
|
158
267
|
pi_validate_items "items" "$PM_CONTEXT" 1
|
|
268
|
+
# M8 runs last because its membership check resolves against the same items
|
|
269
|
+
# collection the previous call validated.
|
|
270
|
+
pm_validate_expected_components
|
|
159
271
|
return 0
|
|
160
272
|
}
|
|
161
273
|
|
|
@@ -54,6 +54,7 @@ Set-StrictMode -Version Latest
|
|
|
54
54
|
Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'BlastRadiusExtraction.psm1') -Force
|
|
55
55
|
Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'BlastRadiusGlob.psm1') -Force
|
|
56
56
|
Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'BlastRadiusConfig.psm1') -Force
|
|
57
|
+
Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'BlastRadiusNormalization.psm1') -Force
|
|
57
58
|
Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'BlastRadiusValidation.psm1') -Force
|
|
58
59
|
|
|
59
60
|
# Feature-folder handling. Every radius contains its own feature folder, and a
|
|
@@ -66,6 +67,11 @@ $script:FeatureFolderPrefix = 'docs/features/'
|
|
|
66
67
|
$script:SourceDerived = 'derived'
|
|
67
68
|
$script:SourceObserved = 'observed'
|
|
68
69
|
|
|
70
|
+
# A contract identifier names something callable or referenceable, so it must
|
|
71
|
+
# carry at least one ASCII letter (issue #489). Get-NormalizedDeclaredRadius
|
|
72
|
+
# re-applies that rule to identifiers a pre-#489 extractor already recorded.
|
|
73
|
+
$script:ContractLetterPattern = [regex]::new('[A-Za-z]')
|
|
74
|
+
|
|
69
75
|
# Contention reason kinds, in the fixed order every result reports them. These
|
|
70
76
|
# strings are contract literals consumed by the downstream parallel schema.
|
|
71
77
|
$script:ConflictPathOverlap = 'path_overlap'
|
|
@@ -167,10 +173,20 @@ function Get-BlastRadius {
|
|
|
167
173
|
$entry = [System.Collections.Generic.List[string]]::new()
|
|
168
174
|
$entry.AddRange([string[]]@(Get-PlanPaths -PlanText $PlanText -RootSurface $rootSurface))
|
|
169
175
|
$entry.AddRange([string[]]@(Get-PathFromLine -Line $specLine -RootSurface $rootSurface))
|
|
170
|
-
|
|
176
|
+
|
|
177
|
+
# Read-by-mandate citations are dropped before the feature folder is added: a
|
|
178
|
+
# plan cites the policy rules because its author was told to read them, not
|
|
179
|
+
# because the change will write them (issue #489). Test-BlastRadius applies
|
|
180
|
+
# the same filter, which is what keeps the derived radius passing V1 and V2
|
|
181
|
+
# against its own plan. The feature-folder glob is added afterwards so it can
|
|
182
|
+
# never be excluded.
|
|
183
|
+
$surviving = [System.Collections.Generic.List[string]]::new()
|
|
184
|
+
$surviving.AddRange([string[]]@(Get-NonMandateReadEntry -Entry $entry.ToArray() `
|
|
185
|
+
-MandateRead ([string[]]@(Get-ConfigMandateRead -Config $Config))))
|
|
186
|
+
$surviving.Add((Get-FeatureFolderGlob -FeatureFolder (
|
|
171
187
|
Get-RequiredText -Value $FeatureFolder -FieldName 'feature_folder')))
|
|
172
188
|
|
|
173
|
-
$paths = [string[]]@(Get-OrdinalSortedEntry -Entry $
|
|
189
|
+
$paths = [string[]]@(Get-OrdinalSortedEntry -Entry $surviving.ToArray())
|
|
174
190
|
$concrete = [string[]]@(Get-ConcreteEntry -Entry $paths)
|
|
175
191
|
|
|
176
192
|
return ConvertTo-NormalizedBlastRadius -Radius @{
|
|
@@ -183,6 +199,92 @@ function Get-BlastRadius {
|
|
|
183
199
|
}
|
|
184
200
|
}
|
|
185
201
|
|
|
202
|
+
function Get-NormalizedDeclaredRadius {
|
|
203
|
+
<#
|
|
204
|
+
.SYNOPSIS
|
|
205
|
+
Re-apply the current extraction rules to an already-recorded radius.
|
|
206
|
+
|
|
207
|
+
.DESCRIPTION
|
|
208
|
+
Port of normalize_declared_radius. A radius recorded by an older
|
|
209
|
+
extractor can carry entries the current rules reject: directory-shaped
|
|
210
|
+
tokens, cross-corpus documentation globs, read-by-mandate citations, and
|
|
211
|
+
letterless contract tokens. Re-deriving from the plan text is not always
|
|
212
|
+
possible, so this function re-filters the recorded radius in place of a
|
|
213
|
+
fresh derivation and re-resolves the levels that depend on the surviving
|
|
214
|
+
paths (issue #489). source and computed_at are preserved and the input is
|
|
215
|
+
never mutated.
|
|
216
|
+
|
|
217
|
+
.PARAMETER Radius
|
|
218
|
+
A derived or declared radius hashtable to re-filter.
|
|
219
|
+
|
|
220
|
+
.PARAMETER Config
|
|
221
|
+
Parsed config/blast-radius.json. The root-surface set, the mandate-read
|
|
222
|
+
list, the module map, and the shared-surface list are all read from this
|
|
223
|
+
one value.
|
|
224
|
+
|
|
225
|
+
.OUTPUTS
|
|
226
|
+
System.Collections.Hashtable. A new radius carrying the surviving paths,
|
|
227
|
+
contracts re-filtered by the ASCII-letter rule, and modules and shared
|
|
228
|
+
surfaces re-resolved from the surviving paths. Throws when the radius
|
|
229
|
+
source is observed: an observed radius records a diff listing rather than
|
|
230
|
+
a plan-text harvest, so the plan-text acceptance rules do not apply to it
|
|
231
|
+
and re-filtering one would silently discard genuine evidence.
|
|
232
|
+
#>
|
|
233
|
+
[CmdletBinding()]
|
|
234
|
+
[OutputType([hashtable])]
|
|
235
|
+
param(
|
|
236
|
+
[Parameter(Mandatory = $true)]
|
|
237
|
+
[AllowNull()]
|
|
238
|
+
[object] $Radius,
|
|
239
|
+
[Parameter(Mandatory = $true)]
|
|
240
|
+
[AllowNull()]
|
|
241
|
+
[object] $Config
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
$normalized = ConvertTo-NormalizedBlastRadius -Radius $Radius
|
|
245
|
+
|
|
246
|
+
# Fail fast, before any other work, so the prohibition is unambiguous and no
|
|
247
|
+
# partially-filtered value can escape.
|
|
248
|
+
if ($normalized['source'] -eq $script:SourceObserved) {
|
|
249
|
+
throw ("Get-NormalizedDeclaredRadius rejects a radius whose source is " +
|
|
250
|
+
"'$($script:SourceObserved)': an observed radius records a diff listing, " +
|
|
251
|
+
'not a plan-text harvest, so the plan-text acceptance rules must not ' +
|
|
252
|
+
'be applied to it.')
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
$rootSurface = [string[]]@(Get-ConfigRootSurface -Config $Config)
|
|
256
|
+
|
|
257
|
+
# Re-run the classifier over each recorded entry. An entry the current rules
|
|
258
|
+
# reject is dropped; the mandate-read filter then removes the citations that
|
|
259
|
+
# are evidence of a read rather than of a write.
|
|
260
|
+
$accepted = [System.Collections.Generic.List[string]]::new()
|
|
261
|
+
foreach ($entry in @($normalized['paths'])) {
|
|
262
|
+
if ($null -ne (Get-PathTokenKind -Token $entry -RootSurface $rootSurface)) {
|
|
263
|
+
$accepted.Add([string]$entry)
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
$paths = [string[]]@(Get-NonMandateReadEntry -Entry $accepted.ToArray() `
|
|
268
|
+
-MandateRead ([string[]]@(Get-ConfigMandateRead -Config $Config)))
|
|
269
|
+
$concrete = [string[]]@(Get-ConcreteEntry -Entry $paths)
|
|
270
|
+
|
|
271
|
+
$contract = [System.Collections.Generic.List[string]]::new()
|
|
272
|
+
foreach ($identifier in @($normalized['contracts'])) {
|
|
273
|
+
if ($script:ContractLetterPattern.IsMatch([string]$identifier)) {
|
|
274
|
+
$contract.Add([string]$identifier)
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return ConvertTo-NormalizedBlastRadius -Radius @{
|
|
279
|
+
paths = $paths
|
|
280
|
+
modules = @(Resolve-BlastRadiusModule -PathEntry $paths -Config $Config)
|
|
281
|
+
shared_surfaces = @(Resolve-BlastRadiusSharedSurface -ConcretePath $concrete -Config $Config)
|
|
282
|
+
contracts = @($contract.ToArray())
|
|
283
|
+
source = $normalized['source']
|
|
284
|
+
computed_at = $normalized['computed_at']
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
186
288
|
function Get-BlastRadiusFromObservedPaths {
|
|
187
289
|
<#
|
|
188
290
|
.SYNOPSIS
|
|
@@ -374,6 +476,7 @@ function Test-BlastRadiusConflict {
|
|
|
374
476
|
Export-ModuleMember -Function `
|
|
375
477
|
Get-PlanPaths, `
|
|
376
478
|
Get-BlastRadius, `
|
|
479
|
+
Get-NormalizedDeclaredRadius, `
|
|
377
480
|
Get-BlastRadiusFromObservedPaths, `
|
|
378
481
|
Test-BlastRadius, `
|
|
379
482
|
Test-BlastRadiusConflict
|
|
@@ -39,6 +39,7 @@ Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'BlastRadiusGlob.psm1')
|
|
|
39
39
|
$script:ConfigSharedSurfaceKey = 'shared_surfaces'
|
|
40
40
|
$script:ConfigSharedSurfaceGlobKey = 'shared_surface_globs'
|
|
41
41
|
$script:ConfigModuleKey = 'modules'
|
|
42
|
+
$script:ConfigMandateReadKey = 'mandate_reads'
|
|
42
43
|
$script:ConfigOverBreadthKey = 'over_breadth_fraction'
|
|
43
44
|
|
|
44
45
|
# Numeric types a JSON or literal truth table may carry for the V3 threshold.
|
|
@@ -279,6 +280,36 @@ function Get-ConfigRootSurface {
|
|
|
279
280
|
return @($rootSurface.ToArray())
|
|
280
281
|
}
|
|
281
282
|
|
|
283
|
+
function Get-ConfigMandateRead {
|
|
284
|
+
<#
|
|
285
|
+
.SYNOPSIS
|
|
286
|
+
Read the read-by-mandate exclusion list from the truth table.
|
|
287
|
+
|
|
288
|
+
.DESCRIPTION
|
|
289
|
+
Port of config_mandate_reads. Mandate reads are the paths every agent is
|
|
290
|
+
instructed to read before doing any work, so a citation of one of them is
|
|
291
|
+
evidence that the author obeyed the reading order rather than evidence
|
|
292
|
+
that the change will write the file (issue #489).
|
|
293
|
+
|
|
294
|
+
.PARAMETER Config
|
|
295
|
+
Parsed config/blast-radius.json. Only the mandate_reads key is read.
|
|
296
|
+
|
|
297
|
+
.OUTPUTS
|
|
298
|
+
System.Object[]. Entries sorted and deduplicated by the underlying
|
|
299
|
+
reader. A config with no mandate_reads key yields an empty array, which
|
|
300
|
+
excludes nothing and reproduces pre-change behavior.
|
|
301
|
+
#>
|
|
302
|
+
[CmdletBinding()]
|
|
303
|
+
[OutputType([System.Object[]])]
|
|
304
|
+
param(
|
|
305
|
+
[Parameter(Mandatory = $true)]
|
|
306
|
+
[AllowNull()]
|
|
307
|
+
[object] $Config
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
return @(Get-ConfigStringList -Config $Config -Key $script:ConfigMandateReadKey)
|
|
311
|
+
}
|
|
312
|
+
|
|
282
313
|
function Get-ConfigModuleEntry {
|
|
283
314
|
<#
|
|
284
315
|
.SYNOPSIS
|
|
@@ -374,57 +405,6 @@ function Get-ConfigOverBreadthFraction {
|
|
|
374
405
|
return $fraction
|
|
375
406
|
}
|
|
376
407
|
|
|
377
|
-
function Resolve-BlastRadiusModule {
|
|
378
|
-
<#
|
|
379
|
-
.SYNOPSIS
|
|
380
|
-
Resolve path entries to the module names of the truth-table map.
|
|
381
|
-
|
|
382
|
-
.DESCRIPTION
|
|
383
|
-
Port of resolve_modules. A module joins the radius as soon as one of its
|
|
384
|
-
globs covers one entry, so the search stops at the first hit per module.
|
|
385
|
-
A path matching no glob resolves to no module.
|
|
386
|
-
|
|
387
|
-
.PARAMETER PathEntry
|
|
388
|
-
Concrete paths and globs of a radius. An empty collection is accepted.
|
|
389
|
-
|
|
390
|
-
.PARAMETER Config
|
|
391
|
-
Parsed config/blast-radius.json.
|
|
392
|
-
|
|
393
|
-
.OUTPUTS
|
|
394
|
-
System.Object[]. Matched module names, deduplicated and ordinally sorted.
|
|
395
|
-
#>
|
|
396
|
-
[CmdletBinding()]
|
|
397
|
-
[OutputType([System.Object[]])]
|
|
398
|
-
param(
|
|
399
|
-
[Parameter(Mandatory = $true)]
|
|
400
|
-
[AllowEmptyCollection()]
|
|
401
|
-
[AllowEmptyString()]
|
|
402
|
-
[string[]] $PathEntry,
|
|
403
|
-
[Parameter(Mandatory = $true)]
|
|
404
|
-
[AllowNull()]
|
|
405
|
-
[object] $Config
|
|
406
|
-
)
|
|
407
|
-
|
|
408
|
-
$matched = [System.Collections.Generic.List[string]]::new()
|
|
409
|
-
foreach ($pair in @(Get-ConfigModuleEntry -Config $Config)) {
|
|
410
|
-
foreach ($pattern in $pair['globs']) {
|
|
411
|
-
$hit = $false
|
|
412
|
-
foreach ($entry in $PathEntry) {
|
|
413
|
-
if (Test-GlobMatch -Pattern $pattern -Candidate $entry) {
|
|
414
|
-
$matched.Add([string]$pair['name'])
|
|
415
|
-
$hit = $true
|
|
416
|
-
break
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
if ($hit) {
|
|
420
|
-
break
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
return @(Get-OrdinalSortedEntry -Entry $matched.ToArray())
|
|
426
|
-
}
|
|
427
|
-
|
|
428
408
|
function Resolve-BlastRadiusSharedSurface {
|
|
429
409
|
<#
|
|
430
410
|
.SYNOPSIS
|
|
@@ -485,7 +465,7 @@ Export-ModuleMember -Function `
|
|
|
485
465
|
Get-RequiredMapping, `
|
|
486
466
|
Get-ConfigStringList, `
|
|
487
467
|
Get-ConfigRootSurface, `
|
|
468
|
+
Get-ConfigMandateRead, `
|
|
488
469
|
Get-ConfigModuleEntry, `
|
|
489
470
|
Get-ConfigOverBreadthFraction, `
|
|
490
|
-
Resolve-BlastRadiusModule, `
|
|
491
471
|
Resolve-BlastRadiusSharedSurface
|