@danmoisan/drm-copilot-mcp 1.1.7 → 1.1.9
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 +73 -1
- package/package.json +1 -1
- package/resources/claude-customizations/.claude/agents/parallel-orchestrator.md +6 -5
- package/resources/claude-customizations/.claude/agents/parallel-planner.md +24 -7
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-modes.ps1 +8 -5
- package/resources/claude-customizations/.claude/hooks/enforce-powershell-batch-budget.ps1 +186 -13
- package/resources/claude-customizations/.claude/hooks/enforce-python-batch-budget.ps1 +186 -13
- package/resources/claude-customizations/.claude/hooks/persist-session-id.ps1 +16 -5
- package/resources/claude-customizations/.claude/lib/bash/parallel-lane-assertion.sh +495 -0
- package/resources/claude-customizations/.claude/lib/bash/report-lane-assertion.sh +169 -0
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadius.psm1 +7 -5
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusConfig.psm1 +4 -2
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusExtraction.psm1 +4 -2
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusGlob.psm1 +2 -0
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusNormalization.psm1 +5 -3
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusTokenShape.psm1 +2 -0
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusValidation.psm1 +6 -4
- package/resources/claude-customizations/.claude/lib/codex-routing/CodexDeployment.psm1 +2 -0
- package/resources/claude-customizations/.claude/lib/codex-routing/CodexTopology.psm1 +2 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryValidation.psm1 +16 -16
- package/resources/claude-customizations/.claude/lib/hook-payload/HookPayload.psm1 +2 -0
- package/resources/claude-customizations/.claude/lib/mermaid/MermaidGrammar.psm1 +2 -0
- package/resources/claude-customizations/.claude/lib/mermaid/MermaidLineScanner.psm1 +3 -1
- package/resources/claude-customizations/.claude/lib/mermaid/MermaidMarkdownFences.psm1 +2 -0
- package/resources/claude-customizations/.claude/lib/mermaid/MermaidValidation.psm1 +5 -3
- package/resources/claude-customizations/.claude/lib/model-routing/ModelRouting.psm1 +2 -0
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorState.psm1 +11 -0
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateCheckpointValue.psm1 +2 -0
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateCodexModelReceipts.psm1 +4 -2
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateCodexTopologyReceipts.psm1 +4 -2
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateCompletion.psm1 +9 -7
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateCompletionChecks.psm1 +4 -2
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateModelReceipts.psm1 +4 -2
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateReceipts.psm1 +3 -1
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateRoutingContract.psm1 +4 -2
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateRoutingMatrix.psm1 +3 -1
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateUnconditional.psm1 +7 -5
- package/resources/claude-customizations/.claude/lib/requirements/GeneratedDocumentCounters.psm1 +12 -0
- package/resources/claude-customizations/.claude/skills/epic-orchestrate/SKILL.md +4 -4
- package/resources/claude-customizations/.claude/skills/parallel-add/SKILL.md +12 -9
- package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +3 -3
- package/resources/claude-customizations/.claude/skills/parallel-plan/SKILL.md +8 -3
- package/resources/claude-customizations/config/blast-radius.json +2 -1
- package/resources/claude-customizations/pack-manifests/core.json +2 -0
- package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# parallel-lane-assertion.sh: sourceable bash port of
|
|
3
|
+
# scripts/dev_tools/parallel_lane_assertion.py. Answers one question for a
|
|
4
|
+
# parallel-run planner: did the hand-authored lane grouping in the manifest's
|
|
5
|
+
# optional `expected_conflict_components` key (invariant M8) survive
|
|
6
|
+
# blast-radius derivation? The module derives the connected components of the
|
|
7
|
+
# DERIVED conflict graph and reports every disagreement with the asserted
|
|
8
|
+
# grouping, in four classes. The Python module remains the repository authority
|
|
9
|
+
# and this file must reproduce its output byte for byte.
|
|
10
|
+
#
|
|
11
|
+
# Responsibilities and boundaries. This module is a DIAGNOSTIC. It never
|
|
12
|
+
# overrides a derived conflict edge, never feeds the cohort computation, never
|
|
13
|
+
# influences scheduling, and writes no artifact and no checkpoint field. Every
|
|
14
|
+
# finding is advisory: the entry point reports and exits 0 whether or not
|
|
15
|
+
# findings were produced, so a disagreement can never block a planning run. The
|
|
16
|
+
# asserted grouping is an ASSERTION, not a declaration.
|
|
17
|
+
#
|
|
18
|
+
# Scope. Every function here is pure in the sense the Python reference declares
|
|
19
|
+
# for itself at scripts/dev_tools/parallel_lane_assertion.py:34-38: it reads the
|
|
20
|
+
# node table pm_parse_manifest already populated plus its own arguments, writes
|
|
21
|
+
# only this module's own PLA_ globals, reads no file and no clock, and starts no
|
|
22
|
+
# process other than `sort`. The single I/O boundary is the entry point
|
|
23
|
+
# .claude/lib/bash/report-lane-assertion.sh, which calls pc_enforce_c_locale
|
|
24
|
+
# before any work, so every sort and character class below is byte ordered.
|
|
25
|
+
#
|
|
26
|
+
# shellcheck disable=SC2034
|
|
27
|
+
# SC2034 is disabled file-wide because the class-token constants and the result
|
|
28
|
+
# globals declared here are written in this file and read by the entry point
|
|
29
|
+
# .claude/lib/bash/report-lane-assertion.sh, which shellcheck analyses as a
|
|
30
|
+
# separate file and therefore cannot see the use.
|
|
31
|
+
|
|
32
|
+
# Resolve this file's own directory so its dependency sources regardless of the
|
|
33
|
+
# caller's working directory.
|
|
34
|
+
PLA_LIB_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
|
|
35
|
+
# shellcheck source=.claude/lib/bash/parallel-manifest-validate.sh
|
|
36
|
+
# shellcheck disable=SC1091
|
|
37
|
+
source "$PLA_LIB_DIR/parallel-manifest-validate.sh"
|
|
38
|
+
|
|
39
|
+
# The four report classes. Each is a stable token so a consumer can group
|
|
40
|
+
# findings without parsing the human-readable detail text.
|
|
41
|
+
PLA_EXPECTED_TOGETHER_DERIVED_APART="expected_together_derived_apart"
|
|
42
|
+
PLA_EXPECTED_APART_DERIVED_TOGETHER="expected_apart_derived_together"
|
|
43
|
+
PLA_MEMBER_NAMES_NO_ITEM="member_names_no_item"
|
|
44
|
+
PLA_ITEM_COVERED_BY_NO_COMPONENT="item_covered_by_no_component"
|
|
45
|
+
|
|
46
|
+
# The single informational class, held as a space-separated set so
|
|
47
|
+
# pc_contains_word decides membership. The other three classes indicate a real
|
|
48
|
+
# disagreement with the derived graph; this one only reports that the assertion
|
|
49
|
+
# was silent about an item.
|
|
50
|
+
PLA_INFORMATIONAL_KINDS="$PLA_ITEM_COVERED_BY_NO_COMPONENT"
|
|
51
|
+
|
|
52
|
+
# Endpoint separator on the command line, matching the `--edges "<a>:<b> ..."`
|
|
53
|
+
# convention of the cohort-computation entry points.
|
|
54
|
+
PLA_EDGE_SEPARATOR=":"
|
|
55
|
+
|
|
56
|
+
# Parsed conflict edges in input order, one `<first> <second>` pair per element.
|
|
57
|
+
# Populated by pla_parse_edges and read by pla_derive_components.
|
|
58
|
+
PLA_EDGES=()
|
|
59
|
+
|
|
60
|
+
pla_is_lexical_integer() {
|
|
61
|
+
# Return 0 when a token is a decimal integer in the strict accepted lexis.
|
|
62
|
+
#
|
|
63
|
+
# Args: $1 = the raw token. The lexis is the one
|
|
64
|
+
# .claude/lib/bash/compute-cohorts.sh:59 already applies, so the entry points
|
|
65
|
+
# agree on what an endpoint is. It is deliberately stricter than Python's
|
|
66
|
+
# int(), which also accepts a leading `+`, a leading zero, an underscore
|
|
67
|
+
# separator, and a non-ASCII decimal digit; those forms are a declared
|
|
68
|
+
# divergence class, excluded rather than reproduced, because reproducing them
|
|
69
|
+
# would need a second lexis no other entry point shares.
|
|
70
|
+
[[ $1 =~ ^-?(0|[1-9][0-9]*)$ ]]
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
pla_parse_edges() {
|
|
74
|
+
# Populate PLA_EDGES from a whitespace-separated `<a>:<b>` edge list.
|
|
75
|
+
#
|
|
76
|
+
# Args: $1 = the raw --edges value; empty or whitespace-only yields no
|
|
77
|
+
# edges. A token that is not two accepted integers separated by a colon is
|
|
78
|
+
# dropped rather than aborting the diagnostic, which would deny the operator
|
|
79
|
+
# the findings that the remaining edges still support. Input order is
|
|
80
|
+
# preserved because it is observable through the derived-component order.
|
|
81
|
+
local text="${1-}" token first second
|
|
82
|
+
PLA_EDGES=()
|
|
83
|
+
local -a tokens=()
|
|
84
|
+
# read -ra rather than an unquoted expansion, so a token carrying a glob
|
|
85
|
+
# character is never subjected to pathname expansion.
|
|
86
|
+
read -ra tokens <<<"$text"
|
|
87
|
+
for token in "${tokens[@]}"; do
|
|
88
|
+
# Partition on the FIRST colon, matching str.partition: a token with two
|
|
89
|
+
# colons yields a second endpoint that still carries one, which the lexis
|
|
90
|
+
# then rejects.
|
|
91
|
+
[[ $token == *"$PLA_EDGE_SEPARATOR"* ]] || continue
|
|
92
|
+
first="${token%%"$PLA_EDGE_SEPARATOR"*}"
|
|
93
|
+
second="${token#*"$PLA_EDGE_SEPARATOR"}"
|
|
94
|
+
pla_is_lexical_integer "$first" || continue
|
|
95
|
+
pla_is_lexical_integer "$second" || continue
|
|
96
|
+
PLA_EDGES+=("$first $second")
|
|
97
|
+
done
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
pla_sort_numeric() {
|
|
101
|
+
# Echo a space-separated integer list sorted ascending.
|
|
102
|
+
#
|
|
103
|
+
# Args: $1 = the space-separated list, $2 = `unique` to collapse repeats.
|
|
104
|
+
# Duplicates are retained by default because a finding's member list
|
|
105
|
+
# reproduces the asserted membership, repeats included, whereas the declared
|
|
106
|
+
# item keys are a set. `sort -n` is used rather than a shell comparison so
|
|
107
|
+
# ordering is numeric under the C locale pc_enforce_c_locale pins.
|
|
108
|
+
local input="${1-}" mode="${2-}" out="" line
|
|
109
|
+
local -a words=()
|
|
110
|
+
read -ra words <<<"$input"
|
|
111
|
+
((${#words[@]} > 0)) || return 0
|
|
112
|
+
# Negated test so the guard's own status is 0 on both branches under set -e.
|
|
113
|
+
local -a flags=(-n)
|
|
114
|
+
[[ $mode != unique ]] || flags=(-n -u)
|
|
115
|
+
while IFS= read -r line; do
|
|
116
|
+
[[ -n $line ]] || continue
|
|
117
|
+
out="$out $line"
|
|
118
|
+
done < <(printf '%s\n' "${words[@]}" | sort "${flags[@]}")
|
|
119
|
+
printf '%s' "${out# }"
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
# Asserted lanes read from the manifest, as parallel arrays indexed by the
|
|
123
|
+
# position of the entry within the READABLE entries, which is the index the
|
|
124
|
+
# Python reference's enumerate() produces and therefore the index that reaches
|
|
125
|
+
# the `component[{position}]` label.
|
|
126
|
+
PLA_EXPECTED_COUNT=0
|
|
127
|
+
PLA_EXPECTED_HAS_NAME=()
|
|
128
|
+
PLA_EXPECTED_NAMES=()
|
|
129
|
+
PLA_EXPECTED_MEMBERS=()
|
|
130
|
+
|
|
131
|
+
# Declared item keys, space separated, de-duplicated and ascending.
|
|
132
|
+
PLA_ITEM_KEYS=""
|
|
133
|
+
|
|
134
|
+
pla_read_expected_components() {
|
|
135
|
+
# Populate the PLA_EXPECTED_* arrays from expected_conflict_components.
|
|
136
|
+
#
|
|
137
|
+
# Reads defensively: every shape rule is already enforced by invariant M8,
|
|
138
|
+
# so a malformed entry is skipped rather than reported -- a diagnostic must
|
|
139
|
+
# not fail on input a validator already rejects, and must not emit a second
|
|
140
|
+
# message for a defect M8 reports once.
|
|
141
|
+
local root="expected_conflict_components" total position comp_path
|
|
142
|
+
local members_path member_path member_count index members
|
|
143
|
+
local member_type member_value
|
|
144
|
+
PLA_EXPECTED_COUNT=0
|
|
145
|
+
PLA_EXPECTED_HAS_NAME=()
|
|
146
|
+
PLA_EXPECTED_NAMES=()
|
|
147
|
+
PLA_EXPECTED_MEMBERS=()
|
|
148
|
+
[[ $(yp_type_of "$root") == seq ]] || return 0
|
|
149
|
+
total=$(yp_count_of "$root")
|
|
150
|
+
for ((position = 0; position < total; position++)); do
|
|
151
|
+
comp_path="${root}[${position}]"
|
|
152
|
+
[[ $(yp_type_of "$comp_path") == map ]] || continue
|
|
153
|
+
members_path="${comp_path}.members"
|
|
154
|
+
[[ $(yp_type_of "$members_path") == seq ]] || continue
|
|
155
|
+
# A non-string name reads as absent, so the entry is labelled by
|
|
156
|
+
# position; the name is diagnostic only and never carries identity.
|
|
157
|
+
if [[ $(yp_type_of "${comp_path}.name") == str ]]; then
|
|
158
|
+
PLA_EXPECTED_HAS_NAME+=(1)
|
|
159
|
+
PLA_EXPECTED_NAMES+=("$(yp_value_of "${comp_path}.name")")
|
|
160
|
+
else
|
|
161
|
+
PLA_EXPECTED_HAS_NAME+=(0)
|
|
162
|
+
PLA_EXPECTED_NAMES+=("")
|
|
163
|
+
fi
|
|
164
|
+
members=""
|
|
165
|
+
member_count=$(yp_count_of "$members_path")
|
|
166
|
+
# Manifest order, no de-duplication: the reference keeps the authored
|
|
167
|
+
# tuple as written and sorts only when it renders a finding.
|
|
168
|
+
for ((index = 0; index < member_count; index++)); do
|
|
169
|
+
member_path="${members_path}[${index}]"
|
|
170
|
+
member_type=$(yp_type_of "$member_path")
|
|
171
|
+
member_value=$(yp_value_of "$member_path")
|
|
172
|
+
pc_is_positive_integer "$member_type" "$member_value" || continue
|
|
173
|
+
members="$members $member_value"
|
|
174
|
+
done
|
|
175
|
+
PLA_EXPECTED_MEMBERS+=("${members# }")
|
|
176
|
+
PLA_EXPECTED_COUNT=$((PLA_EXPECTED_COUNT + 1))
|
|
177
|
+
done
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
pla_read_manifest_inputs() {
|
|
181
|
+
# Read the asserted lanes and the declared item keys from the node table.
|
|
182
|
+
#
|
|
183
|
+
# The node table must already be populated by pm_parse_manifest. Populates
|
|
184
|
+
# PLA_EXPECTED_COUNT, the three PLA_EXPECTED_* arrays, and PLA_ITEM_KEYS.
|
|
185
|
+
# Either side may be empty, which is the manifest that asserts nothing and
|
|
186
|
+
# the manifest that declares no item.
|
|
187
|
+
pla_read_expected_components
|
|
188
|
+
# pm_declared_issue_nums already applies the positive-integer test the
|
|
189
|
+
# reference's guard applies, so the two agree on what resolves to an item.
|
|
190
|
+
PLA_ITEM_KEYS=$(pla_sort_numeric "$(pm_declared_issue_nums)" unique)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
# The derived partition: one element per component, each a space-separated
|
|
194
|
+
# ascending member list, ordered by lowest member.
|
|
195
|
+
PLA_COMPONENTS=()
|
|
196
|
+
|
|
197
|
+
pla_derive_components() {
|
|
198
|
+
# Populate PLA_COMPONENTS with the connected components of the conflict
|
|
199
|
+
# graph.
|
|
200
|
+
#
|
|
201
|
+
# Args: $1 = space-separated declared item keys. Reads the parsed edge list
|
|
202
|
+
# from PLA_EDGES. A lane whose items mutually conflict is one connected
|
|
203
|
+
# component; two lanes sharing no edge are two components.
|
|
204
|
+
local keys="$1" root neighbour edge first second current head
|
|
205
|
+
local -a key_tokens=() roots=() neighbours=() queue=()
|
|
206
|
+
local -A adjacency=() seen=()
|
|
207
|
+
PLA_COMPONENTS=()
|
|
208
|
+
|
|
209
|
+
# Seed adjacency from every declared key so an isolated vertex survives as
|
|
210
|
+
# its own single-member component.
|
|
211
|
+
read -ra key_tokens <<<"$keys"
|
|
212
|
+
for root in "${key_tokens[@]}"; do
|
|
213
|
+
adjacency["$root"]=""
|
|
214
|
+
done
|
|
215
|
+
|
|
216
|
+
# Record each conflict on both endpoints; that symmetry, plus the
|
|
217
|
+
# membership test before the append, makes edge direction and repetition
|
|
218
|
+
# irrelevant. A self-loop and an edge naming an undeclared vertex are
|
|
219
|
+
# skipped rather than reported: malformed-edge reporting belongs to the
|
|
220
|
+
# checkpoint validators, and a diagnostic must degrade gracefully.
|
|
221
|
+
for edge in "${PLA_EDGES[@]}"; do
|
|
222
|
+
first="${edge%% *}"
|
|
223
|
+
second="${edge##* }"
|
|
224
|
+
[[ $first != "$second" ]] || continue
|
|
225
|
+
[[ -n ${adjacency["$first"]+set} ]] || continue
|
|
226
|
+
[[ -n ${adjacency["$second"]+set} ]] || continue
|
|
227
|
+
pc_contains_word "${adjacency["$first"]}" "$second" ||
|
|
228
|
+
adjacency["$first"]="${adjacency["$first"]} $second"
|
|
229
|
+
pc_contains_word "${adjacency["$second"]}" "$first" ||
|
|
230
|
+
adjacency["$second"]="${adjacency["$second"]} $first"
|
|
231
|
+
done
|
|
232
|
+
|
|
233
|
+
# Seed a breadth-first walk from each unvisited vertex in ascending key
|
|
234
|
+
# order, making the component sequence a function of the graph alone. A
|
|
235
|
+
# component's first root is necessarily its smallest member -- a smaller one
|
|
236
|
+
# would have been reached from an earlier root and marked seen -- so the
|
|
237
|
+
# components come out ordered by lowest member and need no second sort.
|
|
238
|
+
read -ra roots <<<"$(pla_sort_numeric "$keys" unique)"
|
|
239
|
+
for root in "${roots[@]}"; do
|
|
240
|
+
[[ -z ${seen["$root"]+set} ]] || continue
|
|
241
|
+
seen["$root"]=1
|
|
242
|
+
queue=("$root")
|
|
243
|
+
head=0
|
|
244
|
+
# The visited set is marked at enqueue time, so a vertex reachable by
|
|
245
|
+
# two paths is enqueued once.
|
|
246
|
+
while ((head < ${#queue[@]})); do
|
|
247
|
+
current="${queue[head]}"
|
|
248
|
+
head=$((head + 1))
|
|
249
|
+
read -ra neighbours <<<"${adjacency["$current"]}"
|
|
250
|
+
for neighbour in "${neighbours[@]}"; do
|
|
251
|
+
[[ -z ${seen["$neighbour"]+set} ]] || continue
|
|
252
|
+
seen["$neighbour"]=1
|
|
253
|
+
queue+=("$neighbour")
|
|
254
|
+
done
|
|
255
|
+
done
|
|
256
|
+
PLA_COMPONENTS+=("$(pla_sort_numeric "${queue[*]}" unique)")
|
|
257
|
+
done
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
# Findings in emission order, as parallel arrays. PLA_FINDING_MEMBERS holds the
|
|
261
|
+
# ascending item keys the finding concerns, so a consumer can act on a finding
|
|
262
|
+
# without re-parsing its detail text.
|
|
263
|
+
PLA_FINDING_KINDS=()
|
|
264
|
+
PLA_FINDING_DETAILS=()
|
|
265
|
+
PLA_FINDING_MEMBERS=()
|
|
266
|
+
|
|
267
|
+
# Flat key -> component-index lookups on both sides, which turn every membership
|
|
268
|
+
# question in the comparison into a constant-time test. Neither is ever
|
|
269
|
+
# iterated, so no hash order reaches output.
|
|
270
|
+
declare -gA PLA_DERIVED_INDEX=()
|
|
271
|
+
declare -gA PLA_EXPECTED_INDEX=()
|
|
272
|
+
|
|
273
|
+
pla_add_finding() {
|
|
274
|
+
# Append one finding: $1 = class token, $2 = detail, $3 = member list.
|
|
275
|
+
PLA_FINDING_KINDS+=("$1")
|
|
276
|
+
PLA_FINDING_DETAILS+=("$2")
|
|
277
|
+
PLA_FINDING_MEMBERS+=("$3")
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
pla_count_distinct() {
|
|
281
|
+
# Echo the number of distinct words in a space-separated list.
|
|
282
|
+
# The accumulator is named distinctly from the associative `seen` set in
|
|
283
|
+
# pla_derive_components: shellcheck resolves a variable name file-wide, so
|
|
284
|
+
# reusing the name there makes it read this string as that array.
|
|
285
|
+
local input="${1-}" counted="" word count=0
|
|
286
|
+
local -a words=()
|
|
287
|
+
read -ra words <<<"$input"
|
|
288
|
+
for word in "${words[@]}"; do
|
|
289
|
+
if ! pc_contains_word "$counted" "$word"; then
|
|
290
|
+
counted="$counted $word"
|
|
291
|
+
count=$((count + 1))
|
|
292
|
+
fi
|
|
293
|
+
done
|
|
294
|
+
printf '%s' "$count"
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
pla_component_label() {
|
|
298
|
+
# Echo the label for one asserted lane.
|
|
299
|
+
#
|
|
300
|
+
# Args: $1 = the lane's position. A lane carrying a string name renders as
|
|
301
|
+
# that name in single quotes, including when the name is the empty string,
|
|
302
|
+
# which is still a string; a lane with no usable name renders by position
|
|
303
|
+
# instead. The name is never used for identity.
|
|
304
|
+
local position="$1"
|
|
305
|
+
if ((PLA_EXPECTED_HAS_NAME[position] == 1)); then
|
|
306
|
+
printf "'%s'" "${PLA_EXPECTED_NAMES[position]}"
|
|
307
|
+
else
|
|
308
|
+
printf 'component[%s]' "$position"
|
|
309
|
+
fi
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
pla_render_member_list() {
|
|
313
|
+
# Echo a space-separated integer list in the Python list form `[101, 102]`:
|
|
314
|
+
# square brackets, comma-and-space separator, no trailing comma. An empty
|
|
315
|
+
# list renders as `[]`. Args: $1 = the list.
|
|
316
|
+
local input="${1-}" out="" word
|
|
317
|
+
local -a words=()
|
|
318
|
+
read -ra words <<<"$input"
|
|
319
|
+
for word in "${words[@]}"; do
|
|
320
|
+
if [[ -n $out ]]; then
|
|
321
|
+
out="$out, $word"
|
|
322
|
+
else
|
|
323
|
+
out="$word"
|
|
324
|
+
fi
|
|
325
|
+
done
|
|
326
|
+
printf '[%s]' "$out"
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
pla_find_split_lanes() {
|
|
330
|
+
# Append one finding per asserted lane whose members landed apart.
|
|
331
|
+
#
|
|
332
|
+
# Reads PLA_EXPECTED_* and PLA_DERIVED_INDEX. One finding per lane, not per
|
|
333
|
+
# pair: an operator whose lane was split wants one message naming the lane,
|
|
334
|
+
# not a quadratic list of member pairs. Lanes are visited in manifest order,
|
|
335
|
+
# and a member absent from the derived index is left to the unknown-member
|
|
336
|
+
# class rather than counted here.
|
|
337
|
+
local position key resolved landed distinct
|
|
338
|
+
local -a members=()
|
|
339
|
+
for ((position = 0; position < PLA_EXPECTED_COUNT; position++)); do
|
|
340
|
+
resolved=""
|
|
341
|
+
landed=""
|
|
342
|
+
read -ra members <<<"${PLA_EXPECTED_MEMBERS[position]}"
|
|
343
|
+
for key in "${members[@]}"; do
|
|
344
|
+
[[ -n ${PLA_DERIVED_INDEX["$key"]+set} ]] || continue
|
|
345
|
+
resolved="$resolved $key"
|
|
346
|
+
landed="$landed ${PLA_DERIVED_INDEX["$key"]}"
|
|
347
|
+
done
|
|
348
|
+
distinct=$(pla_count_distinct "$landed")
|
|
349
|
+
((distinct > 1)) || continue
|
|
350
|
+
pla_add_finding "$PLA_EXPECTED_TOGETHER_DERIVED_APART" \
|
|
351
|
+
"expected component $(pla_component_label "$position") was derived apart: its members occupy $distinct distinct conflict components" \
|
|
352
|
+
"$(pla_sort_numeric "$resolved")"
|
|
353
|
+
done
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
pla_find_merged_lanes() {
|
|
357
|
+
# Append one finding per derived component spanning two asserted lanes.
|
|
358
|
+
#
|
|
359
|
+
# Reads PLA_COMPONENTS and PLA_EXPECTED_INDEX. A derived component touching
|
|
360
|
+
# two asserted lanes means derivation found contention between lanes asserted
|
|
361
|
+
# to be independent. Components are visited in derived order, and a key
|
|
362
|
+
# covered by no asserted lane is left to the uncovered-item class.
|
|
363
|
+
local index key covered lanes distinct component
|
|
364
|
+
local -a members=()
|
|
365
|
+
for ((index = 0; index < ${#PLA_COMPONENTS[@]}; index++)); do
|
|
366
|
+
component="${PLA_COMPONENTS[index]}"
|
|
367
|
+
covered=""
|
|
368
|
+
lanes=""
|
|
369
|
+
read -ra members <<<"$component"
|
|
370
|
+
for key in "${members[@]}"; do
|
|
371
|
+
[[ -n ${PLA_EXPECTED_INDEX["$key"]+set} ]] || continue
|
|
372
|
+
covered="$covered $key"
|
|
373
|
+
lanes="$lanes ${PLA_EXPECTED_INDEX["$key"]}"
|
|
374
|
+
done
|
|
375
|
+
distinct=$(pla_count_distinct "$lanes")
|
|
376
|
+
((distinct > 1)) || continue
|
|
377
|
+
pla_add_finding "$PLA_EXPECTED_APART_DERIVED_TOGETHER" \
|
|
378
|
+
"derived conflict component $(pla_render_member_list "$component") spans $distinct expected components that were asserted apart" \
|
|
379
|
+
"$(pla_sort_numeric "$covered")"
|
|
380
|
+
done
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
pla_build_indexes() {
|
|
384
|
+
# Populate the two flat key -> component-index lookups.
|
|
385
|
+
#
|
|
386
|
+
# The expected index is built in manifest order, so when one key appears in
|
|
387
|
+
# two asserted lanes the LAST occurrence wins. That is reproduced from the
|
|
388
|
+
# reference deliberately and no error is reported for it: duplicate membership
|
|
389
|
+
# across components is invariant M8's concern, and reporting it twice for one
|
|
390
|
+
# defect would misdescribe an advisory diagnostic.
|
|
391
|
+
local index position key
|
|
392
|
+
local -a members=()
|
|
393
|
+
PLA_DERIVED_INDEX=()
|
|
394
|
+
PLA_EXPECTED_INDEX=()
|
|
395
|
+
for ((index = 0; index < ${#PLA_COMPONENTS[@]}; index++)); do
|
|
396
|
+
read -ra members <<<"${PLA_COMPONENTS[index]}"
|
|
397
|
+
for key in "${members[@]}"; do
|
|
398
|
+
PLA_DERIVED_INDEX["$key"]="$index"
|
|
399
|
+
done
|
|
400
|
+
done
|
|
401
|
+
for ((position = 0; position < PLA_EXPECTED_COUNT; position++)); do
|
|
402
|
+
read -ra members <<<"${PLA_EXPECTED_MEMBERS[position]}"
|
|
403
|
+
for key in "${members[@]}"; do
|
|
404
|
+
PLA_EXPECTED_INDEX["$key"]="$position"
|
|
405
|
+
done
|
|
406
|
+
done
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
pla_compare() {
|
|
410
|
+
# Compare the asserted lane grouping against the derived components.
|
|
411
|
+
#
|
|
412
|
+
# Args: $1 = space-separated declared item keys. Reads PLA_EXPECTED_* and
|
|
413
|
+
# PLA_EDGES; populates PLA_COMPONENTS and the three PLA_FINDING_* arrays.
|
|
414
|
+
# Findings are grouped by class in the fixed order split, merged, unknown
|
|
415
|
+
# member, uncovered item, so a reader sees the grouping disagreements before
|
|
416
|
+
# the authoring errors and the informational class last.
|
|
417
|
+
local keys="$1" key unknown="" uncovered="" index position
|
|
418
|
+
local -a members=() sorted=()
|
|
419
|
+
pla_derive_components "$keys"
|
|
420
|
+
pla_build_indexes
|
|
421
|
+
PLA_FINDING_KINDS=()
|
|
422
|
+
PLA_FINDING_DETAILS=()
|
|
423
|
+
PLA_FINDING_MEMBERS=()
|
|
424
|
+
|
|
425
|
+
pla_find_split_lanes
|
|
426
|
+
pla_find_merged_lanes
|
|
427
|
+
|
|
428
|
+
# An asserted member naming no manifest item is an authoring error in the
|
|
429
|
+
# assertion itself, so it is reported apart from a grouping disagreement.
|
|
430
|
+
# The candidate list is collected from the authored membership rather than
|
|
431
|
+
# by iterating the index, so no hash order reaches output.
|
|
432
|
+
for ((position = 0; position < PLA_EXPECTED_COUNT; position++)); do
|
|
433
|
+
read -ra members <<<"${PLA_EXPECTED_MEMBERS[position]}"
|
|
434
|
+
for key in "${members[@]}"; do
|
|
435
|
+
[[ -z ${PLA_DERIVED_INDEX["$key"]+set} ]] || continue
|
|
436
|
+
unknown="$unknown $key"
|
|
437
|
+
done
|
|
438
|
+
done
|
|
439
|
+
read -ra sorted <<<"$(pla_sort_numeric "$unknown" unique)"
|
|
440
|
+
for key in "${sorted[@]}"; do
|
|
441
|
+
pla_add_finding "$PLA_MEMBER_NAMES_NO_ITEM" \
|
|
442
|
+
"expected member $key names no manifest item" "$key"
|
|
443
|
+
done
|
|
444
|
+
|
|
445
|
+
# Informational only: an item the assertion did not mention, which is
|
|
446
|
+
# legitimate when the operator asserts a subset of the run.
|
|
447
|
+
for ((index = 0; index < ${#PLA_COMPONENTS[@]}; index++)); do
|
|
448
|
+
read -ra members <<<"${PLA_COMPONENTS[index]}"
|
|
449
|
+
for key in "${members[@]}"; do
|
|
450
|
+
[[ -z ${PLA_EXPECTED_INDEX["$key"]+set} ]] || continue
|
|
451
|
+
uncovered="$uncovered $key"
|
|
452
|
+
done
|
|
453
|
+
done
|
|
454
|
+
read -ra sorted <<<"$(pla_sort_numeric "$uncovered" unique)"
|
|
455
|
+
for key in "${sorted[@]}"; do
|
|
456
|
+
pla_add_finding "$PLA_ITEM_COVERED_BY_NO_COMPONENT" \
|
|
457
|
+
"manifest item $key is covered by no expected component" "$key"
|
|
458
|
+
done
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
# The report's closing line, stating that the diagnostic blocks nothing. Held
|
|
462
|
+
# as a constant so the entry point and the report share one spelling.
|
|
463
|
+
PLA_CLOSING_LINE="Advisory only: this diagnostic never blocks, never modifies a derived edge, never feeds compute_cohorts, and never influences scheduling."
|
|
464
|
+
|
|
465
|
+
# The rendered report, newline separated and with no trailing newline. The
|
|
466
|
+
# single trailing newline is added by the entry point's printf.
|
|
467
|
+
PLA_REPORT=""
|
|
468
|
+
|
|
469
|
+
pla_disagreement_count() {
|
|
470
|
+
# Echo the number of findings outside the informational class.
|
|
471
|
+
local index count=0
|
|
472
|
+
for ((index = 0; index < ${#PLA_FINDING_KINDS[@]}; index++)); do
|
|
473
|
+
if ! pc_contains_word "$PLA_INFORMATIONAL_KINDS" "${PLA_FINDING_KINDS[index]}"; then
|
|
474
|
+
count=$((count + 1))
|
|
475
|
+
fi
|
|
476
|
+
done
|
|
477
|
+
printf '%s' "$count"
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
pla_format_report() {
|
|
481
|
+
# Render the comparison outcome into PLA_REPORT as advisory-only text.
|
|
482
|
+
#
|
|
483
|
+
# A header line naming the derived-component and disagreement counts, one
|
|
484
|
+
# ADVISORY-prefixed line per finding in emission order, and a closing line
|
|
485
|
+
# stating that the diagnostic blocks nothing. The disagreement count covers
|
|
486
|
+
# the first three classes only, because the informational class reports that
|
|
487
|
+
# the assertion was silent rather than that it disagreed.
|
|
488
|
+
local index disagreements
|
|
489
|
+
disagreements=$(pla_disagreement_count)
|
|
490
|
+
PLA_REPORT="Lane assertion: ${#PLA_COMPONENTS[@]} derived conflict component(s); $disagreements disagreement(s)."
|
|
491
|
+
for ((index = 0; index < ${#PLA_FINDING_KINDS[@]}; index++)); do
|
|
492
|
+
PLA_REPORT="$PLA_REPORT"$'\n'"ADVISORY [${PLA_FINDING_KINDS[index]}] ${PLA_FINDING_DETAILS[index]}."
|
|
493
|
+
done
|
|
494
|
+
PLA_REPORT="$PLA_REPORT"$'\n'"$PLA_CLOSING_LINE"
|
|
495
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# report-lane-assertion.sh: destination-portable command-line entry point for
|
|
3
|
+
# the parallel surface's lane-assertion diagnostic. It exists so a workspace
|
|
4
|
+
# that received the Claude customization payload can compare a manifest's
|
|
5
|
+
# hand-authored `expected_conflict_components` assertion against the derived
|
|
6
|
+
# conflict components with nothing but bash -- no Python, no Poetry, no
|
|
7
|
+
# repository checkout.
|
|
8
|
+
#
|
|
9
|
+
# Usage:
|
|
10
|
+
# bash .claude/lib/bash/report-lane-assertion.sh --manifest <path> \
|
|
11
|
+
# [--edges "<a>:<b> <a>:<b> ..."]
|
|
12
|
+
#
|
|
13
|
+
# `--edges` is optional; omitting it, or passing an empty string, means the
|
|
14
|
+
# derived conflict graph has no edges and every declared item is its own
|
|
15
|
+
# component.
|
|
16
|
+
#
|
|
17
|
+
# Output contract:
|
|
18
|
+
# stdout the advisory report: a header line, one ADVISORY line per finding,
|
|
19
|
+
# and the closing line, byte identical to the Python authority
|
|
20
|
+
# scripts/dev_tools/parallel_lane_assertion.py
|
|
21
|
+
# stderr usage text on a usage error
|
|
22
|
+
# exit 0 every non-usage path, including a disagreement, an unreadable
|
|
23
|
+
# manifest, an unparseable manifest, and an out-of-subset manifest
|
|
24
|
+
# exit 2 usage error only
|
|
25
|
+
#
|
|
26
|
+
# The diagnostic is ADVISORY. A disagreement is never expressible as a non-zero
|
|
27
|
+
# exit status, because a planner must not be blocked by a hand-authored
|
|
28
|
+
# assertion that derivation contradicts.
|
|
29
|
+
set -euo pipefail
|
|
30
|
+
|
|
31
|
+
# Resolve this script's own directory so the library sources regardless of cwd.
|
|
32
|
+
RLA_SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
|
|
33
|
+
# shellcheck source=.claude/lib/bash/parallel-lane-assertion.sh
|
|
34
|
+
# shellcheck disable=SC1091
|
|
35
|
+
source "$RLA_SCRIPT_DIR/parallel-lane-assertion.sh"
|
|
36
|
+
|
|
37
|
+
pc_enforce_c_locale
|
|
38
|
+
|
|
39
|
+
rla_usage() {
|
|
40
|
+
# Print the entry point's usage text.
|
|
41
|
+
cat <<'EOF'
|
|
42
|
+
Usage: report-lane-assertion.sh --manifest <path> [--edges "<a>:<b> ..."]
|
|
43
|
+
|
|
44
|
+
Compares a parallel manifest's expected_conflict_components assertion against
|
|
45
|
+
the derived conflict components and prints an advisory report on stdout.
|
|
46
|
+
|
|
47
|
+
Options:
|
|
48
|
+
--manifest Path to docs/features/parallel/<slug>/parallel.md (required).
|
|
49
|
+
--edges Derived conflict edges as "<a>:<b> <c>:<d>" (optional).
|
|
50
|
+
|
|
51
|
+
Advisory only: the report never blocks. Every non-usage path exits 0.
|
|
52
|
+
EOF
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
rla_manifest_unreadable_detail() {
|
|
56
|
+
# Echo the reason the manifest could not be read, or nothing when it can.
|
|
57
|
+
#
|
|
58
|
+
# Args: $1 = the manifest path. The Python authority prints str(OSError)
|
|
59
|
+
# here, which names an errno string bash cannot reproduce; that is declared
|
|
60
|
+
# divergence class 4, so only the line's prefix is parity scoped and this
|
|
61
|
+
# detail is deliberately a bash-native phrasing rather than an imitation.
|
|
62
|
+
local path="$1"
|
|
63
|
+
if [[ ! -e $path ]]; then
|
|
64
|
+
printf 'no such file: %s' "$path"
|
|
65
|
+
elif [[ -d $path ]]; then
|
|
66
|
+
printf 'path is a directory: %s' "$path"
|
|
67
|
+
elif [[ ! -r $path ]]; then
|
|
68
|
+
printf 'file is not readable: %s' "$path"
|
|
69
|
+
fi
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
rla_report() {
|
|
73
|
+
# Run the comparison over an already-parsed node table and print the report.
|
|
74
|
+
#
|
|
75
|
+
# The node table must already be populated by pm_parse_manifest. Args:
|
|
76
|
+
# $1 = the raw --edges value.
|
|
77
|
+
pla_read_manifest_inputs
|
|
78
|
+
pla_parse_edges "$1"
|
|
79
|
+
pla_compare "$PLA_ITEM_KEYS"
|
|
80
|
+
pla_format_report
|
|
81
|
+
# One printf with one trailing newline, matching the single print() the
|
|
82
|
+
# Python authority issues, so the two lanes agree byte for byte.
|
|
83
|
+
printf '%s\n' "$PLA_REPORT"
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
rla_main() {
|
|
87
|
+
# Parse arguments, read the manifest, and print the advisory report.
|
|
88
|
+
#
|
|
89
|
+
# Exit 2 is reserved for a usage error -- an unknown flag, a flag missing its
|
|
90
|
+
# value, or an absent --manifest -- and its usage text goes to stderr so it
|
|
91
|
+
# cannot be mistaken for a report. --help is a successful request for the
|
|
92
|
+
# same text, so it goes to stdout and exits 0. Every other path exits 0,
|
|
93
|
+
# because the diagnostic is advisory and a verdict must not be expressible
|
|
94
|
+
# as a non-zero status.
|
|
95
|
+
local manifest="" edges="" manifest_seen=0 text
|
|
96
|
+
while (($# > 0)); do
|
|
97
|
+
case "$1" in
|
|
98
|
+
--manifest)
|
|
99
|
+
(($# >= 2)) || {
|
|
100
|
+
rla_usage >&2
|
|
101
|
+
return 2
|
|
102
|
+
}
|
|
103
|
+
manifest="$2"
|
|
104
|
+
manifest_seen=1
|
|
105
|
+
shift 2
|
|
106
|
+
;;
|
|
107
|
+
--edges)
|
|
108
|
+
(($# >= 2)) || {
|
|
109
|
+
rla_usage >&2
|
|
110
|
+
return 2
|
|
111
|
+
}
|
|
112
|
+
edges="$2"
|
|
113
|
+
shift 2
|
|
114
|
+
;;
|
|
115
|
+
--help | -h)
|
|
116
|
+
rla_usage
|
|
117
|
+
return 0
|
|
118
|
+
;;
|
|
119
|
+
*)
|
|
120
|
+
rla_usage >&2
|
|
121
|
+
return 2
|
|
122
|
+
;;
|
|
123
|
+
esac
|
|
124
|
+
done
|
|
125
|
+
((manifest_seen == 1)) || {
|
|
126
|
+
rla_usage >&2
|
|
127
|
+
return 2
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
# An unreadable manifest is reported and exits 0, not 2: the operator asked
|
|
131
|
+
# for a diagnostic and the diagnostic's answer is that it could not look.
|
|
132
|
+
local unreadable
|
|
133
|
+
unreadable=$(rla_manifest_unreadable_detail "$manifest")
|
|
134
|
+
if [[ -n $unreadable ]]; then
|
|
135
|
+
printf 'Lane assertion: manifest unreadable (%s); no comparison made.\n' "$unreadable"
|
|
136
|
+
return 0
|
|
137
|
+
fi
|
|
138
|
+
|
|
139
|
+
text=$(cat -- "$manifest")
|
|
140
|
+
pc_errors_reset
|
|
141
|
+
local parse_status=0
|
|
142
|
+
pm_parse_manifest "$text" || parse_status=$?
|
|
143
|
+
if ((parse_status == 2)); then
|
|
144
|
+
# Status 2 is the scanner's refusal to model a construct. It is reported
|
|
145
|
+
# on its own line, distinct from the unparseable line, because a refusal
|
|
146
|
+
# is not a verdict about the manifest: the Python authority would parse
|
|
147
|
+
# this document, and saying so is more useful than a guessed answer.
|
|
148
|
+
printf 'Lane assertion: manifest outside the supported YAML subset (%s); no comparison made.\n' \
|
|
149
|
+
"$PM_SUBSET_DETAIL"
|
|
150
|
+
return 0
|
|
151
|
+
fi
|
|
152
|
+
if ((parse_status == 1)); then
|
|
153
|
+
# The M1 message is reused byte for byte from pm_parse_manifest rather
|
|
154
|
+
# than restated here, so the two lanes cannot drift on its wording.
|
|
155
|
+
printf 'Lane assertion: manifest unparseable (%s).\n' "${PC_ERRORS[0]}"
|
|
156
|
+
return 0
|
|
157
|
+
fi
|
|
158
|
+
|
|
159
|
+
rla_report "$edges"
|
|
160
|
+
return 0
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
# Guard so the file can be sourced without executing main. main's return code
|
|
164
|
+
# is captured and re-exited explicitly as the final statement.
|
|
165
|
+
if [[ ${BASH_SOURCE[0]} == "${0}" ]]; then
|
|
166
|
+
rla_rc=0
|
|
167
|
+
rla_main "$@" || rla_rc=$?
|
|
168
|
+
exit "$rla_rc"
|
|
169
|
+
fi
|
|
@@ -47,15 +47,17 @@
|
|
|
47
47
|
- Two empty radii, and an empty radius against a non-empty one, do not
|
|
48
48
|
conflict. Under-reporting via emptiness is V1's problem at plan time, not
|
|
49
49
|
the relation's.
|
|
50
|
+
CONVENTION: this module fails fast at module scope and imports its siblings with -ErrorAction Stop.
|
|
50
51
|
#>
|
|
51
52
|
|
|
52
53
|
Set-StrictMode -Version Latest
|
|
54
|
+
$ErrorActionPreference = 'Stop'
|
|
53
55
|
|
|
54
|
-
Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'BlastRadiusExtraction.psm1') -Force
|
|
55
|
-
Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'BlastRadiusGlob.psm1') -Force
|
|
56
|
-
Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'BlastRadiusConfig.psm1') -Force
|
|
57
|
-
Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'BlastRadiusNormalization.psm1') -Force
|
|
58
|
-
Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'BlastRadiusValidation.psm1') -Force
|
|
56
|
+
Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'BlastRadiusExtraction.psm1') -Force -ErrorAction Stop
|
|
57
|
+
Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'BlastRadiusGlob.psm1') -Force -ErrorAction Stop
|
|
58
|
+
Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'BlastRadiusConfig.psm1') -Force -ErrorAction Stop
|
|
59
|
+
Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'BlastRadiusNormalization.psm1') -Force -ErrorAction Stop
|
|
60
|
+
Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'BlastRadiusValidation.psm1') -Force -ErrorAction Stop
|
|
59
61
|
|
|
60
62
|
# Feature-folder handling. Every radius contains its own feature folder, and a
|
|
61
63
|
# caller may pass either a bare folder name or an already-qualified path.
|