@danmoisan/drm-copilot-mcp 1.1.19 → 1.1.20
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 +165 -4
- package/package.json +1 -1
- package/resources/claude-customizations/.claude/agents/atomic-executor.md +17 -6
- package/resources/claude-customizations/.claude/agents/atomic-planner.md +10 -2
- package/resources/claude-customizations/.claude/agents/epic-orchestrator.md +5 -0
- package/resources/claude-customizations/.claude/agents/orchestrator.md +4 -1
- package/resources/claude-customizations/.claude/agents/parallel-orchestrator.md +4 -0
- package/resources/claude-customizations/.claude/agents/task-researcher.md +8 -0
- package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate-command.ps1 +190 -0
- package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate.ps1 +22 -53
- package/resources/claude-customizations/.claude/hooks/hook-docs-only-diff.ps1 +5 -1
- package/resources/claude-customizations/.claude/hooks/premortem-section.ps1 +149 -0
- package/resources/claude-customizations/.claude/hooks/validate-executor-output.ps1 +210 -84
- package/resources/claude-customizations/.claude/hooks/validate-planner-output.ps1 +13 -1
- package/resources/claude-customizations/.claude/hooks/validate-task-researcher-output.ps1 +24 -5
- package/resources/claude-customizations/.claude/lib/ci-gate/Invoke-MergeConfirmationParser.ps1 +203 -0
- package/resources/claude-customizations/.claude/rules/plan-acceptance-gates.md +46 -10
- package/resources/claude-customizations/.claude/skills/atomic-plan-contract/SKILL.md +48 -16
- package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/SKILL.md +7 -3
- package/resources/claude-customizations/.claude/skills/csharp-change-budget-router/SKILL.md +2 -2
- package/resources/claude-customizations/.claude/skills/epic-orchestrate/SKILL.md +12 -3
- package/resources/claude-customizations/.claude/skills/epic-plan/SKILL.md +4 -2
- package/resources/claude-customizations/.claude/skills/execution-environment/SKILL.md +134 -0
- package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +22 -9
- package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +29 -5
- package/resources/claude-customizations/.claude/skills/powershell-change-budget-router/SKILL.md +1 -1
- package/resources/claude-customizations/.claude/skills/python-change-budget-router/SKILL.md +1 -1
- package/resources/claude-customizations/.claude/skills/remediation-handoff-atomic-planner/SKILL.md +14 -6
- package/resources/claude-customizations/.claude/skills/research-issue/SKILL.md +6 -0
- package/resources/claude-customizations/pack-manifests/core.json +4 -0
- package/resources/codex-and-agents-customizations/.agents/skills/README.md +1 -0
- package/resources/codex-and-agents-customizations/.agents/skills/atomic-executor/SKILL.md +8 -7
- package/resources/codex-and-agents-customizations/.agents/skills/atomic-plan-contract/SKILL.md +39 -4
- package/resources/codex-and-agents-customizations/.agents/skills/atomic-planner/SKILL.md +8 -6
- package/resources/codex-and-agents-customizations/.agents/skills/csharp-change-budget-router/SKILL.md +2 -2
- package/resources/codex-and-agents-customizations/.agents/skills/epic-orchestrate/SKILL.md +8 -1
- package/resources/codex-and-agents-customizations/.agents/skills/execution-environment/SKILL.md +134 -0
- package/resources/codex-and-agents-customizations/.agents/skills/orchestrate/SKILL.md +12 -2
- package/resources/codex-and-agents-customizations/.agents/skills/powershell-change-budget-router/SKILL.md +1 -1
- package/resources/codex-and-agents-customizations/.agents/skills/python-change-budget-router/SKILL.md +1 -1
- package/resources/codex-and-agents-customizations/.agents/skills/remediation-handoff-atomic-planner/SKILL.md +7 -3
- package/resources/codex-and-agents-customizations/.agents/skills/research-issue/SKILL.md +6 -0
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c1.toml +10 -6
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c2.toml +10 -6
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c3-elevated.toml +10 -6
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c3.toml +10 -6
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c4.toml +10 -6
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor.toml +10 -6
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c1.toml +5 -2
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c2.toml +5 -2
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c3-elevated.toml +5 -2
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c3.toml +5 -2
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c4.toml +5 -2
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner.toml +5 -2
- package/resources/codex-and-agents-customizations/.codex/agents/atomic-planning.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/csharp-atomic-planning.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/epic-orchestrator.toml +2 -0
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c1.toml +5 -2
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c2.toml +5 -2
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c3-elevated.toml +5 -2
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c3.toml +5 -2
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c4.toml +5 -2
- package/resources/codex-and-agents-customizations/.codex/agents/orchestrator.toml +5 -2
- package/resources/codex-and-agents-customizations/.codex/agents/powershell-atomic-planning.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/python-atomic-planning.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/agents/task-researcher-c1.toml +6 -0
- package/resources/codex-and-agents-customizations/.codex/agents/task-researcher-c2.toml +6 -0
- package/resources/codex-and-agents-customizations/.codex/agents/task-researcher-c3-elevated.toml +6 -0
- package/resources/codex-and-agents-customizations/.codex/agents/task-researcher-c3.toml +6 -0
- package/resources/codex-and-agents-customizations/.codex/agents/task-researcher-c4.toml +6 -0
- package/resources/codex-and-agents-customizations/.codex/agents/task-researcher.toml +6 -0
- package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-merge-gate-command.ps1 +190 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-merge-gate.ps1 +17 -38
- package/resources/codex-and-agents-customizations/.codex/hooks/hook-docs-only-diff.ps1 +5 -1
- package/resources/codex-and-agents-customizations/.codex/prompts/feature-review-remediate.md +6 -4
- package/resources/codex-and-agents-customizations/pack-manifests/core.json +2 -0
- package/resources/customizations/.github/agents/atomic_executor.agent.md +21 -11
- package/resources/customizations/.github/agents/atomic_planning.agent.md +8 -3
- package/resources/customizations/.github/agents/csharp-atomic-executor.agent.md +13 -9
- package/resources/customizations/.github/agents/csharp-atomic-planning.agent.md +2 -2
- package/resources/customizations/.github/agents/csharp-orchestrator.agent.md +2 -2
- package/resources/customizations/.github/agents/csharp-typed-engineer.agent.md +3 -3
- package/resources/customizations/.github/agents/orchestrator.agent.md +8 -5
- package/resources/customizations/.github/agents/powershell-atomic-executor.agent.md +13 -9
- package/resources/customizations/.github/agents/powershell-atomic-planning.agent.md +2 -2
- package/resources/customizations/.github/agents/powershell-orchestrator.agent.md +2 -2
- package/resources/customizations/.github/agents/powershell-typed-engineer.agent.md +3 -3
- package/resources/customizations/.github/agents/python-atomic-executor.agent.md +13 -9
- package/resources/customizations/.github/agents/python-atomic-planning.agent.md +2 -2
- package/resources/customizations/.github/agents/python-orchestrator.agent.md +2 -2
- package/resources/customizations/.github/agents/task-researcher.agent.md +6 -0
- package/resources/customizations/.github/prompts/generate-atomic-plan.prompt.md +16 -10
- package/resources/customizations/.github/prompts/orchestrate-csharp-work.prompt.md +2 -2
- package/resources/customizations/.github/prompts/orchestrate-powershell-work.prompt.md +1 -1
- package/resources/customizations/.github/prompts/orchestrate-python-work.prompt.md +1 -1
- package/resources/customizations/.github/prompts/orchestrate-work.prompt.md +2 -2
- package/resources/customizations/.github/skills/atomic-plan-contract/SKILL.md +39 -4
- package/resources/customizations/.github/skills/csharp-change-budget-router/SKILL.md +1 -1
- package/resources/customizations/.github/skills/execution-environment/SKILL.md +134 -0
- package/resources/customizations/.github/skills/powershell-change-budget-router/SKILL.md +1 -1
- package/resources/customizations/.github/skills/remediation-handoff-atomic-planner/SKILL.md +7 -3
- package/resources/feature-templates/bug/plan.yyyy-MM-ddTHH-mm.md +10 -0
- package/resources/feature-templates/feature/plan.yyyy-MM-ddTHH-mm.md +11 -0
- package/resources/feature-templates/refactor/plan.yyyy-MM-ddTHH-mm.md +11 -0
- package/resources/powershell/PoshQC/PoshQC.CoverageMerge.psm1 +163 -0
- package/resources/powershell/PoshQC/PoshQC.Shard.psm1 +296 -0
- package/resources/powershell/PoshQC/PoshQC.Testing.psm1 +23 -4
- package/resources/powershell/PoshQC/PoshQC.psd1 +7 -1
- package/resources/powershell/PoshQC/PoshQC.psm1 +17 -5
- package/resources/powershell/PoshQC/README.md +13 -1
- package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +3 -0
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
<#
|
|
2
2
|
.SYNOPSIS
|
|
3
|
-
Pre-tool-use hook that gates
|
|
3
|
+
Pre-tool-use hook that gates every pull-request merge or merge-queue enqueue behind checkpoint state.
|
|
4
4
|
|
|
5
5
|
.DESCRIPTION
|
|
6
6
|
Invoked by the Claude Code PreToolUse hook on the "Bash" matcher before any Bash
|
|
7
|
-
command runs.
|
|
8
|
-
the envelope's tool_input.command and, when
|
|
7
|
+
command runs. Brings every gh pr merge invocation except a pure --disable-auto, and every gh
|
|
8
|
+
api merge or enqueue mutation, into scope from the envelope's tool_input.command and, when
|
|
9
|
+
matched, allows the action only when one of four
|
|
9
10
|
checkpoint-only conditions holds:
|
|
10
11
|
|
|
11
12
|
1. Child-feature path: artifacts/orchestration/orchestrator-state.json exists,
|
|
@@ -31,7 +32,10 @@
|
|
|
31
32
|
non-parallel) run never satisfies conditions 1-3, so its only route to a merge is an
|
|
32
33
|
explicit, PR-specific, session-bound record under condition 4; a bare command with no
|
|
33
34
|
PR number never satisfies condition 4, and a blanket flag is rejected rather than read.
|
|
34
|
-
Trigger scope
|
|
35
|
+
Trigger scope (issue #952): every merge method (--merge/-m, --squash/-s, --rebase/-r), --auto,
|
|
36
|
+
--admin, and a bare gh pr merge are gated alike, because under a merge queue each spelling
|
|
37
|
+
enqueues the pull request; only a pure --disable-auto is out of scope. The predicate and the
|
|
38
|
+
PR selector reader live in enforce-epic-merge-gate-command.ps1.
|
|
35
39
|
|
|
36
40
|
Design decision: branches 1-4 trust the on-disk checkpoint and never call gh. Issue #927
|
|
37
41
|
adds a docs-only branch after branch 4 fails, only for an explicit PR number whose item,
|
|
@@ -78,6 +82,8 @@ try { . (Join-Path $PSScriptRoot 'hook-command-invocation.ps1') } catch { Add-Ho
|
|
|
78
82
|
try { . (Join-Path $PSScriptRoot 'enforce-epic-merge-gate-authorization.ps1') } catch { Add-HookDependencyFailure -Name 'enforce-epic-merge-gate-authorization.ps1' -ErrorRecord $_ }
|
|
79
83
|
# Checkpoint read seams, the import guard, and run-target resolution (issue #690).
|
|
80
84
|
try { . (Join-Path $PSScriptRoot 'enforce-epic-merge-gate-resolution.ps1') } catch { Add-HookDependencyFailure -Name 'enforce-epic-merge-gate-resolution.ps1' -ErrorRecord $_ }
|
|
85
|
+
# Merge and enqueue scope predicate and the PR selector reader (issue #952).
|
|
86
|
+
try { . (Join-Path $PSScriptRoot 'enforce-epic-merge-gate-command.ps1') } catch { Add-HookDependencyFailure -Name 'enforce-epic-merge-gate-command.ps1' -ErrorRecord $_ }
|
|
81
87
|
# Shared docs-only diff classification and the merge-side docs-only exemption (issue #927).
|
|
82
88
|
try { . (Join-Path $PSScriptRoot 'hook-docs-only-diff.ps1') } catch { Add-HookDependencyFailure -Name 'hook-docs-only-diff.ps1' -ErrorRecord $_ }
|
|
83
89
|
|
|
@@ -111,21 +117,13 @@ function Get-EpicMergeGateCommandPrNumber {
|
|
|
111
117
|
.SYNOPSIS
|
|
112
118
|
Extract an explicit PR number argument from a gh pr merge command, or $null.
|
|
113
119
|
.DESCRIPTION
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
the parallel
|
|
120
|
-
|
|
121
|
-
The deleted unanchored branch scanned the WHOLE command text for the first run of
|
|
122
|
-
digits once "gh pr merge" appeared anywhere in it, and that failed in both
|
|
123
|
-
directions. Fail-closed: a leading "cd <path>" whose path carries a timestamp
|
|
124
|
-
component supplied a digit run that was not a pull request number, so an authorized
|
|
125
|
-
merge was blocked. False-allow: with authorized item 501 and unauthorized item 777,
|
|
126
|
-
"cd /repo/worktrees/501 && gh pr merge --merge 777" extracted 501, matched the
|
|
127
|
-
authorized item, and permitted the merge of PR 777. Taking the number from the
|
|
128
|
-
matched segment's own operand or flag value closes both directions.
|
|
120
|
+
The number is resolved by Get-EpicMergeGateSelectorPrNumber in
|
|
121
|
+
enforce-epic-merge-gate-command.ps1 (issue #952), from the segment that structurally
|
|
122
|
+
invokes gh pr merge or gh api and never from the whole command line: value flags are
|
|
123
|
+
skipped with their values, an all-digit or /pull/<n> URL selector is accepted, and a
|
|
124
|
+
pulls/<n>/merge path supplies the number for gh api. A command with no resolvable number
|
|
125
|
+
yields $null, which the parallel and standalone branches treat as fail-closed. No
|
|
126
|
+
whole-line digit scan exists, so a cd path component can never supply the number.
|
|
129
127
|
.PARAMETER CommandText
|
|
130
128
|
The Bash command text under evaluation.
|
|
131
129
|
.OUTPUTS
|
|
@@ -138,18 +136,7 @@ function Get-EpicMergeGateCommandPrNumber {
|
|
|
138
136
|
[string] $CommandText
|
|
139
137
|
)
|
|
140
138
|
|
|
141
|
-
|
|
142
|
-
if ($operand -match '^\d+$') {
|
|
143
|
-
return [int]$operand
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
$flagValue = Get-CommandLineFlagValue -CommandText $CommandText -CommandWord 'gh' -SubcommandPath @('pr', 'merge') -FlagName '--merge'
|
|
148
|
-
if ($null -ne $flagValue -and $flagValue -match '^\d+$') {
|
|
149
|
-
return [int]$flagValue
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
return $null
|
|
139
|
+
return Get-EpicMergeGateSelectorPrNumber -CommandText $CommandText
|
|
153
140
|
}
|
|
154
141
|
|
|
155
142
|
function Test-ChildCheckpointAllowsEpicMerge {
|
|
@@ -341,27 +328,9 @@ function Invoke-EpicMergeGateDecision {
|
|
|
341
328
|
return Get-EpicMergeGateAllowDecision
|
|
342
329
|
}
|
|
343
330
|
|
|
344
|
-
#
|
|
345
|
-
#
|
|
346
|
-
|
|
347
|
-
# belonging to some other segment no longer bring a command into scope.
|
|
348
|
-
#
|
|
349
|
-
# The flag leg needs a raw-scan fallback: a wrapper's quoted argument collapses into ONE
|
|
350
|
-
# token, so a flag inside it is never read as a token, and the token-only read took
|
|
351
|
-
# bash -c "gh pr merge --merge 688" out of scope. The fallback reads only the segments the
|
|
352
|
-
# scanner already scans raw, so a masked mention in a non-wrapper segment stays out.
|
|
353
|
-
$isMergeInvocation = Test-CommandLineInvocation -CommandText $commandText -CommandWord 'gh' -SubcommandPath @('pr', 'merge')
|
|
354
|
-
$hasMergeFlag = Test-CommandLineFlag -CommandText $commandText -CommandWord 'gh' -SubcommandPath @('pr', 'merge') -FlagName '--merge'
|
|
355
|
-
if (-not $hasMergeFlag) {
|
|
356
|
-
foreach ($segment in @(Read-CommandLineSegment -CommandText $commandText)) {
|
|
357
|
-
if ((Test-CommandLineSegmentRawScan -Segment $segment) -and
|
|
358
|
-
$segment.ScanText.IndexOf('--merge', [System.StringComparison]::OrdinalIgnoreCase) -ge 0) {
|
|
359
|
-
$hasMergeFlag = $true
|
|
360
|
-
break
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
if (-not $isMergeInvocation -or -not $hasMergeFlag) {
|
|
331
|
+
# Scope (issue #952): every gh pr merge invocation except a pure --disable-auto, and every gh api
|
|
332
|
+
# merge or enqueue mutation, is gated; Test-EpicMergeGateCommandInScope holds the predicate.
|
|
333
|
+
if (-not (Test-EpicMergeGateCommandInScope -CommandText $commandText)) {
|
|
365
334
|
return Get-EpicMergeGateAllowDecision
|
|
366
335
|
}
|
|
367
336
|
|
|
@@ -431,7 +400,7 @@ function Invoke-EpicMergeGateDecision {
|
|
|
431
400
|
return Get-EpicMergeGateBlockDecision -Reason ('EPIC_MERGE_GATE_BLOCKED: ' + $prefix + $standalone.ReasonCode + ': ' + $standalone.Message + $docsOnlySuffix)
|
|
432
401
|
}
|
|
433
402
|
|
|
434
|
-
return Get-EpicMergeGateBlockDecision -Reason 'EPIC_MERGE_GATE_BLOCKED: gh pr merge --merge requires either a per-feature checkpoint with epic_mode == true and step9_status == "passed", an epic checkpoint with epic_merge_pr.ci_gate.conclusion == "success" and a matching pr_number, or a parallel-orchestrator checkpoint with route_id == "parallel" whose target item (matched by pr_number) has merge_status == "ci_green". No checkpoint satisfied this gate.'
|
|
403
|
+
return Get-EpicMergeGateBlockDecision -Reason 'EPIC_MERGE_GATE_BLOCKED: a pull-request merge or merge-queue enqueue (gh pr merge with any method, --auto, or --admin, or a gh api merge or enqueue mutation) requires either a per-feature checkpoint with epic_mode == true and step9_status == "passed", an epic checkpoint with epic_merge_pr.ci_gate.conclusion == "success" and a matching pr_number, or a parallel-orchestrator checkpoint with route_id == "parallel" whose target item (matched by pr_number) has merge_status == "ci_green". No checkpoint satisfied this gate.'
|
|
435
404
|
}
|
|
436
405
|
|
|
437
406
|
function Invoke-EpicMergeGateEntryPoint {
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
is balanced; a structural gh pr merge; no inline
|
|
20
20
|
VAR= prefix token (GH_REPO=, GH_HOST=); no
|
|
21
21
|
single-dash token containing R; no --repo or
|
|
22
|
-
--repo= token; no --auto token
|
|
22
|
+
--repo= token; no --auto token; no token
|
|
23
|
+
containing /pull/ (a URL selector).
|
|
23
24
|
M2 DOCS_ONLY_EXEMPTION_PR_UNRESOLVED - the worktree root is an existing directory.
|
|
24
25
|
M3 DOCS_ONLY_EXEMPTION_PR_UNRESOLVED - gh pr view <N> --json baseRefName,headRefOid
|
|
25
26
|
exits 0 and returns a JSON object; headRefOid
|
|
@@ -368,6 +369,9 @@ function Get-DocsOnlyMergeShapeViolation {
|
|
|
368
369
|
if ($text -eq '--repo' -or $text.StartsWith('--repo=', [System.StringComparison]::OrdinalIgnoreCase)) {
|
|
369
370
|
return $prefix + "the repository override '$text' is not accepted."
|
|
370
371
|
}
|
|
372
|
+
if ($text.Contains('/pull/')) {
|
|
373
|
+
return $prefix + "the URL selector '$text' is not accepted."
|
|
374
|
+
}
|
|
371
375
|
if ($text -eq '--auto' -or $text.StartsWith('--auto=', [System.StringComparison]::OrdinalIgnoreCase)) {
|
|
372
376
|
return $prefix + 'auto-merge (--auto) is not eligible.'
|
|
373
377
|
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Shared Pre-Mortem section check for the planner and researcher SubagentStop hooks (issue #964).
|
|
4
|
+
|
|
5
|
+
.DESCRIPTION
|
|
6
|
+
Test-PreMortemSection reads Markdown text and requires a `## Pre-Mortem` heading followed, before
|
|
7
|
+
the next level-one or level-two heading, by one bullet that begins with each of the seven labels:
|
|
8
|
+
Review checklists, Coverage, Mirror parity, File size, Test isolation, CI-only behavior, and
|
|
9
|
+
Evidence. Labels match case-insensitively and may be bold. Each bullet must carry text after its
|
|
10
|
+
label; a bullet whose text is "not applicable" must also state a reason.
|
|
11
|
+
|
|
12
|
+
.NOTES
|
|
13
|
+
Pure functions: they read no file, environment variable, or clock, and keep no script-scope state. The calling hooks dot-source this
|
|
14
|
+
file under the guarded Add-HookDependencyFailure pattern and pass the document text they read
|
|
15
|
+
through their own file seams.
|
|
16
|
+
#>
|
|
17
|
+
|
|
18
|
+
Set-StrictMode -Version Latest
|
|
19
|
+
|
|
20
|
+
function Get-PreMortemLabel {
|
|
21
|
+
<#
|
|
22
|
+
.SYNOPSIS
|
|
23
|
+
Returns the seven required Pre-Mortem labels in their canonical order.
|
|
24
|
+
#>
|
|
25
|
+
[CmdletBinding()]
|
|
26
|
+
[OutputType([string[]])]
|
|
27
|
+
param()
|
|
28
|
+
|
|
29
|
+
return [string[]]@(
|
|
30
|
+
'Review checklists',
|
|
31
|
+
'Coverage',
|
|
32
|
+
'Mirror parity',
|
|
33
|
+
'File size',
|
|
34
|
+
'Test isolation',
|
|
35
|
+
'CI-only behavior',
|
|
36
|
+
'Evidence'
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function Get-PreMortemSectionLine {
|
|
41
|
+
<#
|
|
42
|
+
.SYNOPSIS
|
|
43
|
+
Returns the lines of the `## Pre-Mortem` section, or $null when the heading is absent.
|
|
44
|
+
#>
|
|
45
|
+
[CmdletBinding()]
|
|
46
|
+
[OutputType([string[]])]
|
|
47
|
+
param(
|
|
48
|
+
[Parameter(Mandatory = $true)]
|
|
49
|
+
[AllowEmptyString()]
|
|
50
|
+
[string] $Content
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
$lines = @($Content -split "`r?`n")
|
|
54
|
+
$start = -1
|
|
55
|
+
for ($index = 0; $index -lt $lines.Count; $index++) {
|
|
56
|
+
if ($lines[$index] -match '^##[ \t]+Pre-Mortem[ \t]*$') {
|
|
57
|
+
$start = $index
|
|
58
|
+
break
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if ($start -lt 0) {
|
|
62
|
+
return $null
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
$section = [System.Collections.Generic.List[string]]::new()
|
|
66
|
+
for ($index = $start + 1; $index -lt $lines.Count; $index++) {
|
|
67
|
+
if ($lines[$index] -match '^#{1,2}[ \t]') {
|
|
68
|
+
break
|
|
69
|
+
}
|
|
70
|
+
$section.Add($lines[$index])
|
|
71
|
+
}
|
|
72
|
+
return [string[]]@($section)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function Test-PreMortemBulletText {
|
|
76
|
+
<#
|
|
77
|
+
.SYNOPSIS
|
|
78
|
+
Returns $true when the text after a label is substantive or is "not applicable" with a reason.
|
|
79
|
+
#>
|
|
80
|
+
[CmdletBinding()]
|
|
81
|
+
[OutputType([bool])]
|
|
82
|
+
param(
|
|
83
|
+
[Parameter(Mandatory = $true)]
|
|
84
|
+
[AllowEmptyString()]
|
|
85
|
+
[string] $Text
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
$body = ($Text -replace '^[\s:*\-\u2013\u2014]+', '').Trim()
|
|
89
|
+
if ($body -notmatch '[A-Za-z0-9]') {
|
|
90
|
+
return $false
|
|
91
|
+
}
|
|
92
|
+
$notApplicable = [regex]::Match($body, '^not applicable\b(?<Reason>.*)$', 'IgnoreCase')
|
|
93
|
+
if (-not $notApplicable.Success) {
|
|
94
|
+
return $true
|
|
95
|
+
}
|
|
96
|
+
return $notApplicable.Groups['Reason'].Value -match '[A-Za-z0-9]'
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function Test-PreMortemSection {
|
|
100
|
+
<#
|
|
101
|
+
.SYNOPSIS
|
|
102
|
+
Validates the `## Pre-Mortem` section of a plan or research document.
|
|
103
|
+
.OUTPUTS
|
|
104
|
+
Hashtable with Ok (bool), Message (string or $null), and MissingLabels (string[]): the labels
|
|
105
|
+
that have no bullet, or whose bullet has no text or says "not applicable" without a reason.
|
|
106
|
+
#>
|
|
107
|
+
[CmdletBinding()]
|
|
108
|
+
[OutputType([hashtable])]
|
|
109
|
+
param(
|
|
110
|
+
[Parameter(Mandatory = $true)]
|
|
111
|
+
[AllowEmptyString()]
|
|
112
|
+
[string] $Content
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
$labels = Get-PreMortemLabel
|
|
116
|
+
$section = Get-PreMortemSectionLine -Content $Content
|
|
117
|
+
if ($null -eq $section) {
|
|
118
|
+
return @{
|
|
119
|
+
Ok = $false
|
|
120
|
+
Message = 'document has no `## Pre-Mortem` section; add one bullet for each of: ' + ($labels -join ', ') + '.'
|
|
121
|
+
MissingLabels = $labels
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
$missing = [System.Collections.Generic.List[string]]::new()
|
|
126
|
+
foreach ($label in $labels) {
|
|
127
|
+
$pattern = '^\s*[-*+][ \t]+(?:\*\*)?' + [regex]::Escape($label) + '(?![A-Za-z])(?<Rest>.*)$'
|
|
128
|
+
$valid = $false
|
|
129
|
+
foreach ($line in $section) {
|
|
130
|
+
$match = [regex]::Match($line, $pattern, 'IgnoreCase')
|
|
131
|
+
if ($match.Success -and (Test-PreMortemBulletText -Text $match.Groups['Rest'].Value)) {
|
|
132
|
+
$valid = $true
|
|
133
|
+
break
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (-not $valid) {
|
|
137
|
+
$missing.Add($label)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if ($missing.Count -gt 0) {
|
|
142
|
+
return @{
|
|
143
|
+
Ok = $false
|
|
144
|
+
Message = '`## Pre-Mortem` section lacks a bullet with text (or "not applicable" with a reason) for: ' + ($missing -join ', ') + '.'
|
|
145
|
+
MissingLabels = [string[]]@($missing)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return @{ Ok = $true; Message = $null; MissingLabels = [string[]]@() }
|
|
149
|
+
}
|