@danmoisan/drm-copilot-mcp 1.1.0 → 1.1.1

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.
@@ -0,0 +1,187 @@
1
+ <#
2
+ .SYNOPSIS
3
+ Blast-radius token-shape predicates, ported from the Python reference.
4
+
5
+ .DESCRIPTION
6
+ Destination-runtime PowerShell port of
7
+ scripts/dev_tools/_blast_radius_token_shapes.py. Holds the pure,
8
+ context-free shape tests that reject an inline-code token before
9
+ Get-PathTokenKind can record it as a repository path. A token can look like
10
+ a path and still name no file: a placeholder or interpolation marker makes it
11
+ a command or artifact shape, and a corpus-wide documentation glob makes it a
12
+ cross-corpus claim. Neither is evidence that a work item will write anything.
13
+
14
+ Test-PlaceholderMarker is new in issue #502.
15
+ Test-MultipleFeatureFolderSpan was relocated here from
16
+ BlastRadiusExtraction.psm1 in the same change, and the two module-scoped
17
+ documentation-corpus variables it reads travelled with it because $script:
18
+ scope is per module. The relocation exists because the extraction module had
19
+ two lines of headroom against the 500-line limit when the marker guard was
20
+ added, so an in-place guard was arithmetically impossible.
21
+
22
+ The Python module remains the authoritative reference implementation. This
23
+ module is a LEAF: it imports no sibling blast-radius module, so the
24
+ extraction module can import it with no possibility of a cycle. That
25
+ constraint is why the predicate does not live in BlastRadiusNormalization.psm1,
26
+ which already imports the extraction module.
27
+
28
+ Every function is pure: no filesystem, subprocess, network, or wall-clock
29
+ access, and no input is mutated.
30
+
31
+ Parity notes for maintainers:
32
+ - The marker array is character-identical to PLACEHOLDER_MARKERS in
33
+ scripts/dev_tools/_blast_radius_token_shapes.py and to the tuple of the
34
+ same name in scripts/dev_tools/plan_gate_coverage.py, whose origin is
35
+ the checkable-literal placeholder guard recorded in
36
+ .claude/rules/plan-acceptance-gates.md. The marker set is a module
37
+ constant, not a truth-table key: it describes what a path can never
38
+ contain rather than a policy a repository could tune.
39
+ - Every marker literal is single-quoted, and the two dollar forms are
40
+ built by character concatenation. A double-quoted PowerShell string
41
+ expands the subexpression form and the delimited-variable form, so a
42
+ double-quoted marker literal would silently define a DIFFERENT
43
+ vocabulary than the Python reference and the parity would fail on
44
+ exactly the two members that matter most.
45
+ - Substring search uses [System.StringComparison]::Ordinal so the result
46
+ is culture-independent and matches Python's byte-wise 'in' operator.
47
+ - Both predicates are total on every string, including the empty string, a
48
+ token consisting only of a marker, and a bare bracket pair. Neither
49
+ throws for any input, because the classifier that calls them runs over
50
+ every inline-code span in a document and a throw would abort an entire
51
+ derivation over one stray span.
52
+ #>
53
+
54
+ Set-StrictMode -Version Latest
55
+
56
+ # Placeholder and interpolation markers. A token carrying any of these was
57
+ # written to document a shape, not to name a file, so it can never be a write
58
+ # claim.
59
+ #
60
+ # The angle brackets are the dominant corpus shape and are also the strongest
61
+ # case: Windows forbids both characters in a filename outright, so an
62
+ # angle-bracketed token cannot name a file on the platform this repository is
63
+ # developed on. The two dollar forms are shell and PowerShell interpolation, and
64
+ # the percent form is the Windows shell's environment-variable syntax; each
65
+ # resolves at run time to text that is not in the token.
66
+ #
67
+ # The two dollar forms are assembled from single characters deliberately. Written
68
+ # as one single-quoted literal they would be correct, but assembling them makes
69
+ # the intent explicit at the definition site and removes any question of what a
70
+ # future edit to the quoting style would do.
71
+ $script:PlaceholderMarker = [string[]]@(
72
+ '<',
73
+ '>',
74
+ '$' + '{',
75
+ '$' + '(',
76
+ '%'
77
+ )
78
+
79
+ # Documentation-corpus root and the index, counted after that prefix, of the
80
+ # segment that names one feature folder. A glob whose wildcard reaches this
81
+ # segment or any earlier one claims every feature folder in the corpus.
82
+ $script:FeatureCorpusPrefix = 'docs/features/'
83
+ $script:FeatureFolderSegmentIndex = 1
84
+
85
+ function Test-PlaceholderMarker {
86
+ <#
87
+ .SYNOPSIS
88
+ Report whether a token carries a placeholder or interpolation marker.
89
+
90
+ .DESCRIPTION
91
+ Port of contains_placeholder_marker. A marker-bearing token documents a
92
+ shape rather than naming a file. Two work items that cite the same
93
+ mandated artifact shape therefore acquired a path-level conflict edge on
94
+ a string that resolves to nothing, which made thematically unrelated
95
+ items contend and serialized runs that had no reason to serialize
96
+ (issue #502).
97
+
98
+ The test is a plain substring scan over a fixed vocabulary, deliberately
99
+ context-free: it needs no repository lookup, no configuration, and no
100
+ knowledge of which segment the marker sits in. A marker anywhere in the
101
+ token is disqualifying, including in the filename position, because an
102
+ interpolated filename is as unresolvable as an interpolated directory.
103
+
104
+ .PARAMETER Token
105
+ A single whitespace-free inline-code token. The empty string is accepted
106
+ and reports false.
107
+
108
+ .OUTPUTS
109
+ System.Boolean. True when any configured marker appears anywhere in the
110
+ token, otherwise false.
111
+ #>
112
+ [CmdletBinding()]
113
+ [OutputType([bool])]
114
+ param(
115
+ [Parameter(Mandatory = $true)]
116
+ [AllowEmptyString()]
117
+ [string] $Token
118
+ )
119
+
120
+ # Ordinal comparison keeps the result culture-independent and byte-wise, so
121
+ # it agrees with Python's 'in' operator on every input.
122
+ foreach ($marker in $script:PlaceholderMarker) {
123
+ if ($Token.IndexOf($marker, [System.StringComparison]::Ordinal) -ge 0) {
124
+ return $true
125
+ }
126
+ }
127
+
128
+ return $false
129
+ }
130
+
131
+ function Test-MultipleFeatureFolderSpan {
132
+ <#
133
+ .SYNOPSIS
134
+ Report whether a glob claims more than one documentation feature folder.
135
+
136
+ .DESCRIPTION
137
+ Port of spans_multiple_feature_folders. The documentation corpus is laid
138
+ out as docs/features/<bucket>/<feature-folder>/..., so a glob whose
139
+ wildcard occupies or truncates the feature-folder segment claims every
140
+ feature folder in the corpus. That made two unrelated work items contend
141
+ purely because both wrote documentation (issue #489). A glob carrying a
142
+ complete, wildcard-free feature-folder segment claims one folder and is
143
+ retained.
144
+
145
+ .PARAMETER Token
146
+ A wildcard-bearing token already accepted by the shape rules of
147
+ Get-PathTokenKind.
148
+
149
+ .OUTPUTS
150
+ System.Boolean. True when the token is rooted in the documentation corpus
151
+ and its wildcard reaches the feature-folder segment or any earlier one.
152
+ #>
153
+ [CmdletBinding()]
154
+ [OutputType([bool])]
155
+ param(
156
+ [Parameter(Mandatory = $true)]
157
+ [AllowEmptyString()]
158
+ [string] $Token
159
+ )
160
+
161
+ if (-not $Token.StartsWith($script:FeatureCorpusPrefix,
162
+ [System.StringComparison]::Ordinal)) {
163
+ return $false
164
+ }
165
+
166
+ $segment = @($Token.Substring($script:FeatureCorpusPrefix.Length) -split '/')
167
+
168
+ # A token that stops at or before the feature-folder segment has had that
169
+ # segment truncated away by the wildcard, so it spans the whole corpus.
170
+ if ($segment.Count -le $script:FeatureFolderSegmentIndex) {
171
+ return $true
172
+ }
173
+
174
+ # Every segment up to and including the feature-folder name must be a literal
175
+ # for the claim to resolve to exactly one folder.
176
+ for ($index = 0; $index -le $script:FeatureFolderSegmentIndex; $index++) {
177
+ if ($segment[$index].IndexOf('*') -ge 0) {
178
+ return $true
179
+ }
180
+ }
181
+
182
+ return $false
183
+ }
184
+
185
+ Export-ModuleMember -Function `
186
+ Test-PlaceholderMarker, `
187
+ Test-MultipleFeatureFolderSpan
@@ -233,11 +233,69 @@ Three constraints bound the mandate-read exclusion:
233
233
  detection compares the declared radius against the paths a diff actually touched, so an item that
234
234
  wrote an excluded path is caught against observed evidence rather than against prose.
235
235
 
236
- The extractor additionally rejects three token shapes that were never write claims: a wildcard-free
236
+ The extractor additionally rejects four token shapes that were never write claims: a wildcard-free
237
237
  token whose final component names a directory rather than a file, a `docs/features/` glob whose
238
- wildcard occupies or truncates the feature-folder segment, and a contract token carrying no ASCII
239
- letter. `artifacts/` is not a known top-level segment, so a bare `artifacts/**` subtree claim no
240
- longer satisfies the shape rules.
238
+ wildcard occupies or truncates the feature-folder segment, a contract token carrying no ASCII
239
+ letter, and a token containing a placeholder or interpolation marker. `artifacts/` is not a known
240
+ top-level segment, so a bare `artifacts/**` subtree claim no longer satisfies the shape rules.
241
+
242
+ ### Placeholder-shape rejection (issue #502)
243
+
244
+ The fourth shape is a token containing any member of the marker set
245
+
246
+ ```text
247
+ < > ${ $( %
248
+ ```
249
+
250
+ The set is not a configuration key and is not read from `config/blast-radius.json`. It originates in
251
+ the checkable-literal placeholder guard defined in `.claude/rules/plan-acceptance-gates.md`, which
252
+ uses the identical five markers to decide that a plan operand documents a command *shape* rather
253
+ than stating a real assertion. Both subsystems answer the same question about the same text, so the
254
+ two vocabularies are pinned equal by test rather than left to convention.
255
+
256
+ **A marker-bearing token never matches a tracked path.** A placeholder or interpolation form
257
+ resolves at run time to text that is not in the token, so the token as written names nothing. For
258
+ the two angle brackets the claim is stronger than a heuristic: Windows forbids both characters in a
259
+ filename outright, so an angle-bracketed token cannot name a file on the platform this repository is
260
+ developed on. Admitting such a token recorded a `paths` entry that no diff could ever touch, and two
261
+ items citing the identical shape then acquired a `path_overlap` edge on a string that resolves to
262
+ nothing.
263
+
264
+ **The dominant token is a mandated artifact shape, which is why the exposure was corpus-wide rather
265
+ than incidental.** Every agent in this repository is instructed to write its evidence to the
266
+ canonical scheme defined in `.claude/skills/evidence-and-timestamp-conventions/SKILL.md`, and that
267
+ scheme is non-overridable, so a well-formed plan quotes the same feature-relative artifact shape
268
+ that every other well-formed plan quotes. The shape is therefore the most widely shared token in the
269
+ corpus, and admitting it made compliance with the evidence-path scheme a source of contention. The
270
+ same reasoning applies to the feature-document shapes and to the session-keyed state-file shape.
271
+
272
+ **The planner remains obliged to enumerate a genuine write explicitly.** This rejection describes a
273
+ plan that documents a shape, not a permanent ban on the paths a shape resembles. When an item's plan
274
+ will actually write a path it expressed as a shape, the planner appends that exact concrete path to
275
+ the declared radius after normalization. This is the same obligation constraint 1 of the
276
+ mandate-read exclusion imposes, and `detect_escaped_paths` is the same backstop: drift detection
277
+ compares the declared radius against the paths a diff actually touched, so an item that wrote a path
278
+ it expressed only as a shape is caught against observed evidence rather than against prose.
279
+
280
+ **Accepted fail-open trade.** The rejection runs inside the classifier, upstream of shared-surface
281
+ resolution, so a marker-bearing token whose shape matches a configured `shared_surface_globs`
282
+ pattern is dropped before it can be reported as a touched shared surface. The trade is inherent to
283
+ that placement: a guard downstream of surface resolution would leave the token in `paths` and
284
+ reintroduce the path-level false edge it exists to remove. Corpus exposure was measured empty — no
285
+ plan in the 58-plan corpus examined for issue #502 cited a marker-bearing token whose shape matched a
286
+ configured shared-surface glob — and the planner obligation above is what keeps a genuine write
287
+ visible. A later change that widens the marker set must re-take that measurement.
288
+
289
+ **Known residual: the whitespace split.** Extraction harvests whitespace-free inline-code tokens, so
290
+ a placeholder form written with internal whitespace is split into fragments before the marker test
291
+ ever runs. Each fragment is then judged on its own shape and is normally rejected for an unrelated
292
+ reason, most often for carrying no separator. The residual is therefore benign in the accepting
293
+ direction but is recorded here because it means the rejection is not a complete guard against every
294
+ way a shape can be written; it is a guard against the whitespace-free forms the extractor actually
295
+ admits.
296
+
297
+ Enforcement of this rejection is prose plus validator logic, exactly as for the three shapes above.
298
+ No JSON Schema is authored, imported, or read for it, and `config/blast-radius.json` gains no key.
241
299
 
242
300
  ### Module-map granularity criterion
243
301
 
@@ -173,6 +173,33 @@ integration-to-main PR.
173
173
  The `## Invocation Prompt` section is the exact text the user replays (from the main session,
174
174
  never from an `orchestrator` agent) to launch execution.
175
175
 
176
+ ### Integration Commit Form (issue #539)
177
+
178
+ The epic planner's own version-control operations — committing the epic manifest, the fanned-in
179
+ child feature folders and approved plans, and the durable kickoff copy — are orchestration
180
+ bookkeeping, not implementation. The preimplementation gate
181
+ (`.claude/hooks/enforce-orchestration-preimplementation-gate.ps1`) exempts them only when the
182
+ invocation is **pathspec-bearing**: every staging or integration invocation must name at least one
183
+ explicit path operand, and every operand must resolve inside one of the five exempt
184
+ orchestration-bookkeeping trees:
185
+
186
+ - `docs/features/epics/`
187
+ - `docs/features/parallel/`
188
+ - `docs/features/active/`
189
+ - `docs/features/potential/`
190
+ - `artifacts/orchestration/`
191
+
192
+ Use one of the two pathspec-bearing spellings:
193
+
194
+ - the message option followed by a double-dash separator and then the exempt path operands, or
195
+ - the exempt path operands named directly before the message option.
196
+
197
+ A pathless integration invocation — a message option with no path operand — is denied (D4 row 4),
198
+ as are whole-tree operands, tree-wide flags, pathspec-from-file options, history-rewriting or
199
+ content-widening options, and any operand that resolves outside the five exempt trees. Mixing one
200
+ exempt operand with one production operand denies the whole invocation. Name each exempt path
201
+ explicitly; the exemption is allow-side only and every parse ambiguity denies.
202
+
176
203
  ## Checkpoint Handling
177
204
 
178
205
  Persist `artifacts/orchestration/epic-planner-state.json` after every completed step with the
@@ -522,6 +522,34 @@ Validate the artifact through `mcp__drm-copilot__validate_orchestration_artifact
522
522
  `artifact_type: "parallel-kickoff"`. That artifact type dispatches to
523
523
  `scripts/dev_tools/parallel_kickoff_contract.py`, which this feature delivers.
524
524
 
525
+ ### Integration Commit Form (issue #539)
526
+
527
+ The parallel planner's own version-control operations — committing the run manifest
528
+ `docs/features/parallel/<slug>/parallel.md`, each item's prepared feature folder and approved
529
+ plan-path, and the durable kickoff copy `docs/features/parallel/<slug>/parallel-kickoff.md` — are
530
+ orchestration bookkeeping, not implementation. The preimplementation gate
531
+ (`.claude/hooks/enforce-orchestration-preimplementation-gate.ps1`) exempts them only when the
532
+ invocation is **pathspec-bearing**: every staging or integration invocation must name at least one
533
+ explicit path operand, and every operand must resolve inside one of the five exempt
534
+ orchestration-bookkeeping trees:
535
+
536
+ - `docs/features/epics/`
537
+ - `docs/features/parallel/`
538
+ - `docs/features/active/`
539
+ - `docs/features/potential/`
540
+ - `artifacts/orchestration/`
541
+
542
+ Use one of the two pathspec-bearing spellings:
543
+
544
+ - the message option followed by a double-dash separator and then the exempt path operands, or
545
+ - the exempt path operands named directly before the message option.
546
+
547
+ A pathless integration invocation — a message option with no path operand — is denied (D4 row 4),
548
+ as are whole-tree operands, tree-wide flags, pathspec-from-file options, history-rewriting or
549
+ content-widening options, and any operand that resolves outside the five exempt trees. Mixing one
550
+ exempt operand with one production operand denies the whole invocation. Name each exempt path
551
+ explicitly; the exemption is allow-side only and every parse ambiguity denies.
552
+
525
553
  ## Completion Report
526
554
 
527
555
  The final report to the operator must include:
@@ -33,6 +33,7 @@
33
33
  ".claude/hooks/enforce-feature-folder-order.ps1",
34
34
  ".claude/hooks/enforce-model-routing-receipt.ps1",
35
35
  ".claude/hooks/enforce-orchestration-preimplementation-gate.ps1",
36
+ ".claude/hooks/enforce-orchestration-preimplementation-gate-helpers.ps1",
36
37
  ".claude/hooks/enforce-parallel-abandon-gate.ps1",
37
38
  ".claude/hooks/enforce-parallel-cohort-barrier.ps1",
38
39
  ".claude/hooks/enforce-parallel-cohort-barrier-helpers.ps1",
@@ -129,6 +130,7 @@
129
130
  ".claude/lib/blast-radius/BlastRadiusValidation.psm1",
130
131
  ".claude/lib/blast-radius/BlastRadius.psm1",
131
132
  ".claude/lib/blast-radius/BlastRadiusNormalization.psm1",
133
+ ".claude/lib/blast-radius/BlastRadiusTokenShape.psm1",
132
134
  ".claude/rules/parallel-orchestration.md",
133
135
  ".claude/rules/shell.md",
134
136
  ".claude/lib/bash/compute-cohorts.sh",
@@ -2,7 +2,7 @@ default_permissions = ":danger-full-access"
2
2
 
3
3
  [mcp_servers.drm-copilot]
4
4
  command = "npx"
5
- args = ["-y", "@danmoisan/drm-copilot-mcp@1.1.0"]
5
+ args = ["-y", "@danmoisan/drm-copilot-mcp@1.1.1"]
6
6
  required = true
7
7
  enabled_tools = [
8
8
  "collect_commit_context",