@danmoisan/drm-copilot-mcp 1.0.19 → 1.0.21

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 (32) hide show
  1. package/out/mcp-server.js +71 -8
  2. package/package.json +1 -1
  3. package/resources/claude-customizations/.claude/agents/atomic-executor.md +2 -2
  4. package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output.ps1 +14 -6
  5. package/resources/claude-customizations/.claude/lib/model-routing/ModelRouting.psm1 +34 -14
  6. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorState.psm1 +30 -18
  7. package/resources/claude-customizations/.claude/rules/general-code-change.md +1 -1
  8. package/resources/claude-customizations/.claude/rules/general-unit-test.md +2 -2
  9. package/resources/claude-customizations/.claude/rules/typescript.md +5 -5
  10. package/resources/codex-and-agents-customizations/.agents/skills/general-code-change/SKILL.md +1 -1
  11. package/resources/codex-and-agents-customizations/.agents/skills/general-unit-test/SKILL.md +2 -2
  12. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c1.toml +4 -0
  13. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c2.toml +4 -0
  14. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c3-elevated.toml +4 -0
  15. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c3.toml +4 -0
  16. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c4.toml +4 -0
  17. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator.toml +4 -0
  18. package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
  19. package/resources/codex-and-agents-customizations/.codex/hooks/check-powershell-test-purity.ps1 +9 -65
  20. package/resources/codex-and-agents-customizations/.codex/hooks/check-python-test-purity.ps1 +9 -65
  21. package/resources/codex-and-agents-customizations/.codex/hooks/codex-pretooluse-file-mapping.ps1 +474 -0
  22. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-checkpoint-monotonic.ps1 +20 -101
  23. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-completion-consistency.ps1 +16 -3
  24. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-child-worktree-binding.ps1 +14 -5
  25. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-planning-only.ps1 +23 -5
  26. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-evidence-locations.ps1 +30 -52
  27. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate.ps1 +28 -27
  28. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-powershell-batch-budget.ps1 +19 -49
  29. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-python-batch-budget.ps1 +19 -49
  30. package/resources/codex-and-agents-customizations/pack-manifests/core.json +1 -0
  31. package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +19 -0
  32. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-pr-author-skill.ps1 +0 -500
@@ -1,500 +0,0 @@
1
- # Converted hook
2
- # Review the generated hook behavior before enabling it.
3
-
4
- <#
5
- .SYNOPSIS
6
- Pre-tool-use hook that enforces the pr-author skill is used before gh pr create or gh pr edit.
7
-
8
- .DESCRIPTION
9
- Invoked by the Claude Code PreToolUse hook before any Bash command runs. Reads
10
- tool input JSON from the CLAUDE_TOOL_INPUT environment variable, inspects the
11
- attempted command, and blocks gh pr create / gh pr edit commands that bypass the
12
- pr-author skill workflow.
13
-
14
- Required sequence:
15
- 1. mcp__drm-copilot__collect_pr_context writes artifacts/pr_context.summary.txt
16
- 2. pr-author skill produces the body text; the pr-author agent writes
17
- artifacts/pr_body_<N>.md and a sibling integrity receipt
18
- artifacts/pr_body_<N>.receipt.json
19
- 3. gh pr create --body-file artifacts/pr_body_<N>.md
20
- (or gh pr edit --body-file ...)
21
-
22
- Block cases:
23
- Case A - gh pr create or gh pr edit with --body (inline, no --body-file): blocked.
24
- Case B - gh pr create with neither --body nor --body-file: blocked.
25
- Case C - gh pr create or gh pr edit with --body-file but context artifact absent: blocked.
26
- Preflight - --body-file/context present: orchestrator-state checkpoint must pass
27
- --require-pr-creation-ready before receipt verification runs, else blocked.
28
- Receipt - preflight passed: the SHA-256 receipt is verified in five ordered checks
29
- (Section below). The first failing check blocks.
30
-
31
- Receipt verification decision order on the --body-file-with-context path:
32
- PR_BODY_PATH_NONCANONICAL -> PR_AUTHOR_RECEIPT_MISSING -> PR_AUTHOR_RECEIPT_NUMBER_MISMATCH
33
- -> PR_AUTHOR_RECEIPT_HASH_MISMATCH -> PR_AUTHOR_RECEIPT_STALE -> allow.
34
-
35
- .NOTES
36
- Compatible with PowerShell 7+. No external module dependencies.
37
-
38
- Enforcement strength: the SHA-256 receipt is a policy-level integrity check that binds the
39
- PR body bytes to the receipt the pr-author agent wrote. It is not a cryptographic or security
40
- boundary: any actor with Write access to artifacts/ can replace both the body file and the
41
- receipt together, because all agents share the same filesystem and the runtime exposes no
42
- native agent-identity signal at Bash PreToolUse time. The mechanism prevents accidental bypass
43
- and requires a deliberate, documented act to circumvent. It MUST NOT be described as
44
- tamper-proof or as a security boundary.
45
- #>
46
- [CmdletBinding()]
47
- param()
48
-
49
- $script:PrContextArtifactPath = 'artifacts/pr_context.summary.txt'
50
- $script:OrchestratorStateCheckpointPath = 'artifacts/orchestration/orchestrator-state.json'
51
-
52
- function Invoke-OrchestratorStatePreflight {
53
- <#
54
- .SYNOPSIS
55
- Runs the orchestrator-state validator against the checkpoint and reports pass/fail.
56
- .DESCRIPTION
57
- Mirrors Invoke-RoutingContractValidation (.codex/hooks/validate-orchestrator-output.ps1):
58
- an injectable subprocess scriptblock seam defaults to ``python -m
59
- scripts.dev_tools.validate_orchestration_artifacts orchestrator-state <CheckpointPath>
60
- --require-pr-creation-ready``. A missing checkpoint or validation failure surfaces via the
61
- validator's exit/stderr text (pre-PR-creation readiness, not full completion).
62
- .OUTPUTS
63
- System.Collections.Hashtable with keys HasErrors (bool) and ErrorText (string).
64
- #>
65
- [CmdletBinding()]
66
- [OutputType([hashtable])]
67
- param(
68
- [Parameter(Mandatory = $false)]
69
- [string] $CheckpointPath = $script:OrchestratorStateCheckpointPath,
70
-
71
- [Parameter(Mandatory = $false)]
72
- [scriptblock] $Invoker = {
73
- param($Path)
74
- $output = & python -m scripts.dev_tools.validate_orchestration_artifacts `
75
- orchestrator-state $Path --require-pr-creation-ready 2>&1
76
- [pscustomobject]@{
77
- ExitCode = $LASTEXITCODE
78
- Output = ($output | Out-String)
79
- }
80
- }
81
- )
82
-
83
- $result = & $Invoker $CheckpointPath
84
- $exitCode = 0
85
- if ($null -ne $result -and ($result.PSObject.Properties.Name -contains 'ExitCode')) { $exitCode = [int]$result.ExitCode }
86
- $outputText = ''
87
- if ($null -ne $result -and ($result.PSObject.Properties.Name -contains 'Output')) { $outputText = ([string]$result.Output).Trim() }
88
-
89
- return @{ HasErrors = ($exitCode -ne 0); ErrorText = $outputText }
90
- }
91
-
92
- function Get-PrContextArtifactExistence {
93
- <#
94
- .SYNOPSIS
95
- Wrapper around Test-Path for the PR context artifact. Tests mock this function.
96
- .OUTPUTS
97
- System.Boolean
98
- #>
99
- [CmdletBinding()]
100
- [OutputType([bool])]
101
- param()
102
-
103
- return [bool](Test-Path -LiteralPath $script:PrContextArtifactPath)
104
- }
105
-
106
- function Get-PrBodyFileBytes {
107
- <#
108
- .SYNOPSIS
109
- Read the raw bytes of the PR body file. Tests mock this function (read seam).
110
- .DESCRIPTION
111
- Returns the byte content of the supplied body-file path, or $null when the file is absent.
112
- This is the injectable boundary for body-file bytes in tests; no test writes the body file
113
- to disk. The bytes are hashed inline by the receipt verification function.
114
- .PARAMETER BodyFilePath
115
- The relative path to the PR body file (for example artifacts/pr_body_5.md).
116
- .OUTPUTS
117
- System.Byte[] or $null
118
- #>
119
- [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '', Justification = 'The plural noun names the byte-array return; the seam name is fixed by the receipt contract.')]
120
- [CmdletBinding()]
121
- [OutputType([byte[]])]
122
- param(
123
- [Parameter(Mandatory)]
124
- [string] $BodyFilePath
125
- )
126
-
127
- if (-not (Test-Path -LiteralPath $BodyFilePath)) {
128
- return $null
129
- }
130
-
131
- return [System.IO.File]::ReadAllBytes($BodyFilePath)
132
- }
133
-
134
- function Get-PrAuthorReceiptContent {
135
- <#
136
- .SYNOPSIS
137
- Read the raw JSON text of the PR body receipt. Tests mock this function (read seam).
138
- .DESCRIPTION
139
- Returns the raw text content of the sibling receipt file artifacts/pr_body_<N>.receipt.json,
140
- or $null when the receipt file is absent. This is the injectable boundary for receipt
141
- content in tests; no test writes the receipt file to disk.
142
- .PARAMETER ReceiptFilePath
143
- The relative path to the receipt file (for example artifacts/pr_body_5.receipt.json).
144
- .OUTPUTS
145
- System.String or $null
146
- #>
147
- [CmdletBinding()]
148
- [OutputType([string])]
149
- param(
150
- [Parameter(Mandatory)]
151
- [string] $ReceiptFilePath
152
- )
153
-
154
- if (-not (Test-Path -LiteralPath $ReceiptFilePath)) {
155
- return $null
156
- }
157
-
158
- return (Get-Content -LiteralPath $ReceiptFilePath -Raw)
159
- }
160
-
161
- function Get-PrContextSummaryLastWriteUtc {
162
- <#
163
- .SYNOPSIS
164
- Return the UTC last-write time of the PR context summary. Tests mock this function (seam).
165
- .DESCRIPTION
166
- Returns the LastWriteTimeUtc of artifacts/pr_context.summary.txt, or $null when the file is
167
- absent. The staleness check compares the receipt's created_at against this value; both are
168
- artifact metadata, so no wall-clock seam is required.
169
- .OUTPUTS
170
- System.DateTime or $null
171
- #>
172
- [CmdletBinding()]
173
- [OutputType([datetime])]
174
- param()
175
-
176
- if (-not (Test-Path -LiteralPath $script:PrContextArtifactPath)) {
177
- return $null
178
- }
179
-
180
- return (Get-Item -LiteralPath $script:PrContextArtifactPath).LastWriteTimeUtc
181
- }
182
-
183
- function Test-PrAuthorReceiptVerification {
184
- <#
185
- .SYNOPSIS
186
- Verify the SHA-256 receipt and return a block-reason string, or $null when verified.
187
- .DESCRIPTION
188
- Runs the five ordered receipt checks on the --body-file-with-context path. Each check is its
189
- own short-circuiting branch; the first failure returns its reason code:
190
- 1. PR_BODY_PATH_NONCANONICAL - --body-file path does not match the canonical
191
- artifacts/pr_body_<N>.md pattern (case-sensitive).
192
- 2. PR_AUTHOR_RECEIPT_MISSING - sibling artifacts/pr_body_<N>.receipt.json absent.
193
- 3. PR_AUTHOR_RECEIPT_NUMBER_MISMATCH- receipt.number (integer) != <N> from the path.
194
- 4. PR_AUTHOR_RECEIPT_HASH_MISMATCH - inline SHA-256 (lowercase hex) of the body bytes
195
- != receipt.sha256.
196
- 5. PR_AUTHOR_RECEIPT_STALE - receipt.created_at (UTC) not strictly newer than the
197
- context summary last-write time.
198
- Returns $null when all five checks pass (allow). All disk access flows through the three
199
- injectable seams (Get-PrBodyFileBytes, Get-PrAuthorReceiptContent,
200
- Get-PrContextSummaryLastWriteUtc); SHA-256 is computed inline. This is a policy-level
201
- integrity check, not a cryptographic control: any actor with Write access to artifacts/ can
202
- replace the body file and the receipt together.
203
- .PARAMETER CommandText
204
- The Bash command text containing the --body-file argument.
205
- .OUTPUTS
206
- System.String or $null
207
- #>
208
- [CmdletBinding()]
209
- [OutputType([string])]
210
- param(
211
- [Parameter(Mandatory)]
212
- [string] $CommandText
213
- )
214
-
215
- # Check 1: the --body-file argument must match the canonical artifacts/pr_body_<N>.md pattern.
216
- # The match is case-sensitive (-cmatch) so a non-canonical path is rejected before any read.
217
- if ($CommandText -cnotmatch '--body-file\s+artifacts/pr_body_(\d+)\.md\b') {
218
- return "PR_BODY_PATH_NONCANONICAL: ``--body-file`` must reference a canonical ``artifacts/pr_body_<N>.md`` file produced by the pr-author skill. The path supplied does not match ``artifacts/pr_body_<N>.md``."
219
- }
220
-
221
- $bodyNumber = [int]$Matches[1]
222
- $bodyFilePath = "artifacts/pr_body_$bodyNumber.md"
223
- $receiptFilePath = "artifacts/pr_body_$bodyNumber.receipt.json"
224
-
225
- # Check 2: the sibling receipt file must exist (read via the injectable seam).
226
- $receiptRaw = Get-PrAuthorReceiptContent -ReceiptFilePath $receiptFilePath
227
- if ([string]::IsNullOrWhiteSpace($receiptRaw)) {
228
- return "PR_AUTHOR_RECEIPT_MISSING: ``$receiptFilePath`` is absent. The pr-author agent must write the SHA-256 receipt alongside ``$bodyFilePath`` before issuing ``gh pr create``/``gh pr edit --body-file``."
229
- }
230
-
231
- # A receipt that is present but not valid JSON cannot be verified; treat it as missing content.
232
- try {
233
- $receipt = $receiptRaw | ConvertFrom-Json -ErrorAction Stop
234
- } catch {
235
- return "PR_AUTHOR_RECEIPT_MISSING: ``$receiptFilePath`` is not valid JSON. The pr-author agent must write a well-formed receipt with ``number``, ``sha256``, and ``created_at``."
236
- }
237
-
238
- # Check 3: the receipt number must equal the <N> extracted from the canonical path.
239
- $receiptNumber = $null
240
- $parsedNumber = 0
241
- if ([int]::TryParse([string]$receipt.number, [ref] $parsedNumber)) {
242
- $receiptNumber = $parsedNumber
243
- }
244
- if ($receiptNumber -ne $bodyNumber) {
245
- return "PR_AUTHOR_RECEIPT_NUMBER_MISMATCH: ``$receiptFilePath`` ``number`` ($($receipt.number)) does not equal the body-file number ($bodyNumber). The receipt must bind to ``$bodyFilePath``."
246
- }
247
-
248
- # Check 4: the inline SHA-256 (lowercase hex) of the body bytes must equal receipt.sha256.
249
- $bodyBytes = Get-PrBodyFileBytes -BodyFilePath $bodyFilePath
250
- if ($null -eq $bodyBytes) {
251
- return "PR_AUTHOR_RECEIPT_HASH_MISMATCH: ``$bodyFilePath`` could not be read to verify its SHA-256 against ``$receiptFilePath``."
252
- }
253
-
254
- $sha256 = [System.Security.Cryptography.SHA256]::Create()
255
- try {
256
- $hashBytes = $sha256.ComputeHash($bodyBytes)
257
- } finally {
258
- $sha256.Dispose()
259
- }
260
- $computedHash = ([System.BitConverter]::ToString($hashBytes) -replace '-', '').ToLowerInvariant()
261
-
262
- if ($computedHash -ne ([string]$receipt.sha256).ToLowerInvariant()) {
263
- return "PR_AUTHOR_RECEIPT_HASH_MISMATCH: the SHA-256 of ``$bodyFilePath`` does not equal ``sha256`` in ``$receiptFilePath``. The body file was modified after the receipt was written."
264
- }
265
-
266
- # Check 5: receipt.created_at (UTC) must be strictly newer than the context summary last-write.
267
- $createdAt = [DateTime]::MinValue
268
- $createdParsed = [DateTime]::TryParse(
269
- [string]$receipt.created_at,
270
- [System.Globalization.CultureInfo]::InvariantCulture,
271
- [System.Globalization.DateTimeStyles]::AdjustToUniversal -bor [System.Globalization.DateTimeStyles]::AssumeUniversal,
272
- [ref] $createdAt)
273
- if (-not $createdParsed) {
274
- return "PR_AUTHOR_RECEIPT_STALE: ``$receiptFilePath`` has a missing or unparseable ``created_at``. The pr-author agent must record a UTC ISO-8601 ``created_at`` strictly newer than ``$script:PrContextArtifactPath``."
275
- }
276
-
277
- $contextLastWrite = Get-PrContextSummaryLastWriteUtc
278
- if (($null -eq $contextLastWrite) -or ($createdAt -le $contextLastWrite)) {
279
- return "PR_AUTHOR_RECEIPT_STALE: ``$receiptFilePath`` ``created_at`` is not strictly newer than the last-write time of ``$script:PrContextArtifactPath``. The pr-author agent must regenerate the body and receipt after refreshing the PR context."
280
- }
281
-
282
- return $null
283
- }
284
-
285
- function Get-PrAuthorBypassReason {
286
- <#
287
- .SYNOPSIS
288
- Inspect the command text and return a block reason string, or $null when the command is allowed.
289
- .DESCRIPTION
290
- Returns PR_AUTHOR_SKILL_BLOCKED when gh pr create or gh pr edit is run with --body (inline,
291
- no --body-file), or when gh pr create is run with no body flag at all. Returns
292
- PR_CONTEXT_MISSING when --body-file is present but the context artifact does not exist on
293
- disk. When --body-file is present and the context artifact exists, verifies the SHA-256
294
- receipt via Test-PrAuthorReceiptVerification and returns its block reason
295
- (PR_BODY_PATH_NONCANONICAL / PR_AUTHOR_RECEIPT_*) when verification fails. Returns $null for
296
- all allowed patterns. Cases A, B, and C are evaluated first and unchanged; receipt
297
- verification only extends the previously-allowed --body-file-with-context path.
298
- .PARAMETER CommandText
299
- The Bash command text extracted from CLAUDE_TOOL_INPUT.
300
- .PARAMETER ContextExists
301
- Whether artifacts/pr_context.summary.txt currently exists on disk.
302
- .OUTPUTS
303
- System.String or $null
304
- #>
305
- [CmdletBinding()]
306
- [OutputType([string])]
307
- param(
308
- [Parameter(Mandatory)]
309
- [string] $CommandText,
310
-
311
- [Parameter(Mandatory)]
312
- [bool] $ContextExists
313
- )
314
-
315
- # Only act on gh pr create or gh pr edit subcommands.
316
- $isPrCreate = $CommandText -match '(?i)\bgh\s+pr\s+create\b'
317
- $isPrEdit = $CommandText -match '(?i)\bgh\s+pr\s+edit\b'
318
-
319
- if (-not $isPrCreate -and -not $isPrEdit) {
320
- return $null
321
- }
322
-
323
- $hasBodyFile = $CommandText -match '(?i)--body-file\b'
324
- $hasInlineBody = $CommandText -match '(?i)--body(?!-file)\b'
325
-
326
- # Case A: gh pr create OR gh pr edit with inline --body (not --body-file). Evaluated before the
327
- # gh pr edit no-body allow short-circuit so inline-body edits are blocked, not allowed.
328
- if (($isPrCreate -or $isPrEdit) -and $hasInlineBody -and -not $hasBodyFile) {
329
- return "PR_AUTHOR_SKILL_BLOCKED: ``gh pr create`` and ``gh pr edit`` must use ``--body-file`` with a file produced by the pr-author skill from ``$script:PrContextArtifactPath``. Run ``mcp__drm-copilot__collect_pr_context`` to generate the context file, apply the pr-author skill to produce ``artifacts/pr_body_<N>.md``, then pass that file via ``--body-file``."
330
- }
331
-
332
- if ($isPrCreate) {
333
- # Case B: gh pr create with no body flag at all.
334
- if (-not $hasInlineBody -and -not $hasBodyFile) {
335
- return "PR_AUTHOR_SKILL_BLOCKED: New PRs require ``--body-file``. Run ``mcp__drm-copilot__collect_pr_context`` to generate ``$script:PrContextArtifactPath``, apply the pr-author skill to produce ``artifacts/pr_body_<N>.md``, then pass that file via ``--body-file``."
336
- }
337
- }
338
-
339
- if ($isPrEdit) {
340
- # gh pr edit with no --body or --body-file (e.g., --title, --add-label, --reviewer) is allowed.
341
- if (-not $hasInlineBody -and -not $hasBodyFile) {
342
- return $null
343
- }
344
- }
345
-
346
- # Case C: --body-file present but context artifact is absent.
347
- if ($hasBodyFile -and -not $ContextExists) {
348
- return "PR_CONTEXT_MISSING: ``$script:PrContextArtifactPath`` is absent. Run ``mcp__drm-copilot__collect_pr_context`` before creating or editing the PR body."
349
- }
350
-
351
- # Orchestrator-state preflight: runs inside this same PreToolUse hook (so it cannot be
352
- # bypassed by invoking gh pr create/edit directly) before receipt verification.
353
- if ($hasBodyFile -and $ContextExists) {
354
- $preflightResult = Invoke-OrchestratorStatePreflight
355
- if ($preflightResult.HasErrors) {
356
- $preflightSummary = if ([string]::IsNullOrWhiteSpace($preflightResult.ErrorText)) {
357
- "checkpoint missing at $script:OrchestratorStateCheckpointPath"
358
- } else {
359
- $preflightResult.ErrorText
360
- }
361
- return "ORCHESTRATOR_STATE_PREFLIGHT_FAILED: $preflightSummary"
362
- }
363
- }
364
-
365
- # Receipt verification: extends, and does not replace, the previously-allowed path.
366
- if ($hasBodyFile -and $ContextExists) {
367
- $receiptReason = Test-PrAuthorReceiptVerification -CommandText $CommandText
368
- if ($receiptReason) {
369
- return $receiptReason
370
- }
371
- }
372
-
373
- return $null
374
- }
375
-
376
- function Invoke-PrAuthorSkillDecision {
377
- <#
378
- .SYNOPSIS
379
- Parse CLAUDE_TOOL_INPUT and return an allow-or-block decision.
380
- .PARAMETER ToolInputRaw
381
- The raw JSON tool payload supplied by Claude Code.
382
- .OUTPUTS
383
- System.Collections.Specialized.OrderedDictionary
384
- .NOTES
385
- Missing tool input or missing command text is treated as allow.
386
- #>
387
- [CmdletBinding()]
388
- [OutputType([System.Collections.Specialized.OrderedDictionary])]
389
- param(
390
- [string] $ToolInputRaw
391
- )
392
-
393
- if (-not $ToolInputRaw) {
394
- return Get-PrAuthorSkillAllowDecision
395
- }
396
-
397
- try {
398
- $toolInput = $ToolInputRaw | ConvertFrom-Json -ErrorAction Stop
399
- } catch {
400
- throw "enforce-pr-author-skill hook received malformed JSON in CLAUDE_TOOL_INPUT: $_"
401
- }
402
-
403
- $commandText = $toolInput.command
404
- if (-not $commandText) {
405
- return Get-PrAuthorSkillAllowDecision
406
- }
407
-
408
- $contextExists = Get-PrContextArtifactExistence
409
- $reason = Get-PrAuthorBypassReason -CommandText $commandText -ContextExists $contextExists
410
-
411
- if ($reason) {
412
- return Get-PrAuthorSkillBlockDecision -Reason $reason
413
- }
414
-
415
- return Get-PrAuthorSkillAllowDecision
416
- }
417
-
418
- function Get-PrAuthorSkillAllowDecision {
419
- <#
420
- .SYNOPSIS
421
- Construct the PreToolUse allow decision for a permitted Bash command.
422
- .OUTPUTS
423
- System.Collections.Specialized.OrderedDictionary
424
- #>
425
- [CmdletBinding()]
426
- [OutputType([System.Collections.Specialized.OrderedDictionary])]
427
- param()
428
-
429
- return [ordered]@{
430
- hookSpecificOutput = [ordered]@{
431
- hookEventName = 'PreToolUse'
432
- permissionDecision = 'allow'
433
- }
434
- }
435
- }
436
-
437
- function Get-PrAuthorSkillBlockDecision {
438
- <#
439
- .SYNOPSIS
440
- Construct the PreToolUse deny decision for a forbidden Bash command.
441
- .PARAMETER Reason
442
- The specific deny reason to surface in the decision.
443
- .OUTPUTS
444
- System.Collections.Specialized.OrderedDictionary
445
- #>
446
- [CmdletBinding()]
447
- [OutputType([System.Collections.Specialized.OrderedDictionary])]
448
- param(
449
- [Parameter(Mandatory)]
450
- [string] $Reason
451
- )
452
-
453
- return [ordered]@{
454
- hookSpecificOutput = [ordered]@{
455
- hookEventName = 'PreToolUse'
456
- permissionDecision = 'deny'
457
- permissionDecisionReason = $Reason
458
- }
459
- }
460
- }
461
-
462
- function Test-PrAuthorBypassRequired {
463
- <#
464
- .SYNOPSIS
465
- Return $true when a Bash command requires the pr-author skill to run first.
466
- .PARAMETER CommandText
467
- The Bash command text extracted from CLAUDE_TOOL_INPUT.
468
- .PARAMETER ContextExists
469
- Whether artifacts/pr_context.summary.txt currently exists on disk.
470
- .OUTPUTS
471
- System.Boolean
472
- #>
473
- [CmdletBinding()]
474
- [OutputType([bool])]
475
- param(
476
- [Parameter(Mandatory)]
477
- [string] $CommandText,
478
-
479
- [Parameter(Mandatory)]
480
- [bool] $ContextExists
481
- )
482
-
483
- return ($null -ne (Get-PrAuthorBypassReason -CommandText $CommandText -ContextExists $ContextExists))
484
- }
485
-
486
- # Allow dot-sourcing in tests without executing the entrypoint.
487
- if ($MyInvocation.InvocationName -eq '.') {
488
- return
489
- }
490
-
491
- try {
492
- $decision = Invoke-PrAuthorSkillDecision -ToolInputRaw $env:CLAUDE_TOOL_INPUT
493
- } catch {
494
- Write-Error $_
495
- exit 1
496
- }
497
-
498
- $decision | ConvertTo-Json -Compress -Depth 5 | Write-Output
499
-
500
- exit 0