@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.
Files changed (68) hide show
  1. package/out/mcp-server.js +1045 -214
  2. package/package.json +1 -1
  3. package/resources/claude-customizations/.claude/agents/feature-review.md +5 -3
  4. package/resources/claude-customizations/.claude/agents/parallel-orchestrator.md +11 -4
  5. package/resources/claude-customizations/.claude/agents/parallel-planner.md +5 -2
  6. package/resources/claude-customizations/.claude/hooks/enforce-discovery-artifact-gate.ps1 +28 -8
  7. package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate.ps1 +109 -5
  8. package/resources/claude-customizations/.claude/hooks/enforce-mermaid-validation.ps1 +390 -0
  9. package/resources/claude-customizations/.claude/hooks/validate-discovery-artifact-gate.ps1 +28 -8
  10. package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output.ps1 +117 -46
  11. package/resources/claude-customizations/.claude/lib/bash/parallel-manifest-validate.sh +115 -3
  12. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadius.psm1 +105 -2
  13. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusConfig.psm1 +32 -52
  14. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusExtraction.psm1 +107 -99
  15. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusNormalization.psm1 +295 -0
  16. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusValidation.psm1 +9 -3
  17. package/resources/claude-customizations/.claude/lib/codex-routing/CodexDeployment.psm1 +312 -0
  18. package/resources/claude-customizations/.claude/lib/codex-routing/CodexTopology.psm1 +392 -0
  19. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryValidation.psm1 +500 -0
  20. package/resources/claude-customizations/.claude/lib/mermaid/MermaidGrammar.psm1 +491 -0
  21. package/resources/claude-customizations/.claude/lib/mermaid/MermaidLineScanner.psm1 +488 -0
  22. package/resources/claude-customizations/.claude/lib/mermaid/MermaidMarkdownFences.psm1 +298 -0
  23. package/resources/claude-customizations/.claude/lib/mermaid/MermaidValidation.psm1 +496 -0
  24. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorState.psm1 +58 -67
  25. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateCheckpointValue.psm1 +383 -0
  26. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateCodexModelReceipts.psm1 +297 -0
  27. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateCodexTopologyReceipts.psm1 +298 -0
  28. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateCompletion.psm1 +232 -43
  29. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateCompletionChecks.psm1 +416 -0
  30. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateModelReceipts.psm1 +366 -0
  31. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateReceipts.psm1 +408 -0
  32. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateRoutingContract.psm1 +428 -0
  33. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateRoutingMatrix.psm1 +377 -0
  34. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateUnconditional.psm1 +166 -0
  35. package/resources/claude-customizations/.claude/rules/general-unit-test.md +1 -1
  36. package/resources/claude-customizations/.claude/rules/mermaid.md +142 -0
  37. package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +88 -3
  38. package/resources/claude-customizations/.claude/rules/plan-acceptance-gates.md +116 -0
  39. package/resources/claude-customizations/.claude/rules/powershell.md +1 -1
  40. package/resources/claude-customizations/.claude/rules/quality-tiers.md +3 -3
  41. package/resources/claude-customizations/.claude/settings.json +5 -0
  42. package/resources/claude-customizations/.claude/skills/atomic-plan-contract/SKILL.md +15 -0
  43. package/resources/claude-customizations/.claude/skills/evidence-and-timestamp-conventions/SKILL.md +13 -0
  44. package/resources/claude-customizations/.claude/skills/feature-promotion-lifecycle/SKILL.md +6 -0
  45. package/resources/claude-customizations/.claude/skills/feature-review-workflow/SKILL.md +4 -4
  46. package/resources/claude-customizations/.claude/skills/mermaid-diagram/SKILL.md +184 -0
  47. package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/c4.md +50 -0
  48. package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/class.md +63 -0
  49. package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/er.md +56 -0
  50. package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/flowchart.md +68 -0
  51. package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/gantt.md +51 -0
  52. package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/other-types.md +82 -0
  53. package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/pie.md +32 -0
  54. package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/sequence.md +63 -0
  55. package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/state.md +49 -0
  56. package/resources/claude-customizations/.claude/skills/parallel-add/SKILL.md +10 -5
  57. package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +116 -41
  58. package/resources/claude-customizations/.claude/skills/parallel-plan/SKILL.md +95 -13
  59. package/resources/claude-customizations/.claude/skills/parallel-remove/SKILL.md +7 -3
  60. package/resources/claude-customizations/.claude/skills/powershell-qa-gate/SKILL.md +1 -1
  61. package/resources/claude-customizations/config/blast-radius.json +9 -3
  62. package/resources/claude-customizations/pack-manifests/core.json +31 -1
  63. package/resources/codex-and-agents-customizations/.agents/skills/evidence-and-timestamp-conventions/SKILL.md +13 -0
  64. package/resources/codex-and-agents-customizations/.agents/skills/general-unit-test/SKILL.md +1 -1
  65. package/resources/codex-and-agents-customizations/.agents/skills/quality-tiers/SKILL.md +3 -3
  66. package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
  67. package/resources/customizations/.github/skills/evidence-and-timestamp-conventions/SKILL.md +13 -0
  68. 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 M7 and exposes the two
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=8
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
- $entry.Add((Get-FeatureFolderGlob -FeatureFolder (
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 $entry.ToArray())
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