@danmoisan/drm-copilot-mcp 1.1.17 → 1.1.19

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 (225) hide show
  1. package/out/mcp-server.js +1405 -684
  2. package/package.json +6 -4
  3. package/resources/claude-customizations/.claude/agents/atomic-executor.md +11 -0
  4. package/resources/claude-customizations/.claude/agents/atomic-planner.md +10 -0
  5. package/resources/claude-customizations/.claude/agents/epic-orchestrator.md +12 -4
  6. package/resources/claude-customizations/.claude/agents/epic-planner.md +5 -4
  7. package/resources/claude-customizations/.claude/agents/feature-review.md +16 -5
  8. package/resources/claude-customizations/.claude/agents/orchestrator.md +21 -7
  9. package/resources/claude-customizations/.claude/agents/parallel-orchestrator.md +37 -13
  10. package/resources/claude-customizations/.claude/agents/parallel-planner.md +9 -6
  11. package/resources/claude-customizations/.claude/agents/pr-author.md +15 -2
  12. package/resources/claude-customizations/.claude/agents/prd-feature.md +1 -1
  13. package/resources/claude-customizations/.claude/hooks/check-powershell-test-purity.ps1 +7 -1
  14. package/resources/claude-customizations/.claude/hooks/check-python-test-purity.ps1 +7 -1
  15. package/resources/claude-customizations/.claude/hooks/enforce-checkpoint-monotonic.ps1 +7 -1
  16. package/resources/claude-customizations/.claude/hooks/enforce-completion-consistency.ps1 +111 -46
  17. package/resources/claude-customizations/.claude/hooks/enforce-completion-helpers.ps1 +105 -0
  18. package/resources/claude-customizations/.claude/hooks/enforce-discovery-artifact-gate.ps1 +8 -1
  19. package/resources/claude-customizations/.claude/hooks/enforce-epic-invocation-origin.ps1 +7 -1
  20. package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate-resolution.ps1 +116 -59
  21. package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate.ps1 +55 -23
  22. package/resources/claude-customizations/.claude/hooks/enforce-epic-wave-barrier.ps1 +74 -75
  23. package/resources/claude-customizations/.claude/hooks/enforce-epic-worktree-removal-gate-resolution.ps1 +127 -35
  24. package/resources/claude-customizations/.claude/hooks/enforce-epic-worktree-removal-gate.ps1 +61 -57
  25. package/resources/claude-customizations/.claude/hooks/enforce-evidence-locations.ps1 +7 -1
  26. package/resources/claude-customizations/.claude/hooks/enforce-feature-folder-order.ps1 +129 -39
  27. package/resources/claude-customizations/.claude/hooks/enforce-mermaid-validation.ps1 +7 -1
  28. package/resources/claude-customizations/.claude/hooks/enforce-model-routing-receipt.ps1 +9 -3
  29. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-epic-scope.ps1 +39 -51
  30. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-helpers.ps1 +31 -58
  31. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-modes.ps1 +63 -47
  32. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-targets.ps1 +433 -0
  33. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +17 -12
  34. package/resources/claude-customizations/.claude/hooks/enforce-parallel-abandon-gate.ps1 +9 -3
  35. package/resources/claude-customizations/.claude/hooks/enforce-parallel-cohort-barrier.ps1 +65 -50
  36. package/resources/claude-customizations/.claude/hooks/enforce-parallel-drift-gate.ps1 +49 -42
  37. package/resources/claude-customizations/.claude/hooks/enforce-parallel-worktree-removal-gate.ps1 +54 -72
  38. package/resources/claude-customizations/.claude/hooks/enforce-powershell-batch-budget.ps1 +8 -2
  39. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-command-allowlist.ps1 +279 -0
  40. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill-helpers.ps1 +129 -75
  41. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.artifact-root.ps1 +233 -0
  42. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.docs-only.ps1 +224 -0
  43. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.ps1 +69 -24
  44. package/resources/claude-customizations/.claude/hooks/enforce-prd-feature-before-planner-helpers.ps1 +29 -18
  45. package/resources/claude-customizations/.claude/hooks/enforce-prd-feature-before-planner.ps1 +10 -4
  46. package/resources/claude-customizations/.claude/hooks/enforce-promotion-mcp-only.ps1 +14 -5
  47. package/resources/claude-customizations/.claude/hooks/enforce-python-batch-budget.ps1 +8 -2
  48. package/resources/claude-customizations/.claude/hooks/feature-folder-resolution.ps1 +373 -0
  49. package/resources/claude-customizations/.claude/hooks/feature-review-coverage-thresholds.ps1 +85 -0
  50. package/resources/claude-customizations/.claude/hooks/hook-command-heredoc.ps1 +119 -0
  51. package/resources/claude-customizations/.claude/hooks/hook-command-invocation-operands.ps1 +198 -0
  52. package/resources/claude-customizations/.claude/hooks/hook-command-invocation.ps1 +254 -255
  53. package/resources/claude-customizations/.claude/hooks/hook-command-payload-powershell.ps1 +201 -0
  54. package/resources/claude-customizations/.claude/hooks/hook-command-payload.ps1 +484 -0
  55. package/resources/claude-customizations/.claude/hooks/hook-command-scanner.ps1 +27 -110
  56. package/resources/claude-customizations/.claude/hooks/hook-dependency-guard.ps1 +114 -0
  57. package/resources/claude-customizations/.claude/hooks/hook-docs-only-diff.ps1 +493 -0
  58. package/resources/claude-customizations/.claude/hooks/subagentstop-envelope.ps1 +192 -0
  59. package/resources/claude-customizations/.claude/hooks/validate-bash.ps1 +9 -3
  60. package/resources/claude-customizations/.claude/hooks/validate-discovery-artifact-gate.ps1 +40 -42
  61. package/resources/claude-customizations/.claude/hooks/validate-executor-output.ps1 +66 -38
  62. package/resources/claude-customizations/.claude/hooks/validate-feature-review-coverage-metrics.ps1 +180 -0
  63. package/resources/claude-customizations/.claude/hooks/validate-feature-review-coverage-rules.ps1 +195 -0
  64. package/resources/claude-customizations/.claude/hooks/validate-feature-review-coverage.ps1 +141 -395
  65. package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output-resolution.ps1 +311 -0
  66. package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output-routing.ps1 +170 -0
  67. package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output.ps1 +115 -190
  68. package/resources/claude-customizations/.claude/hooks/validate-planner-output.ps1 +64 -32
  69. package/resources/claude-customizations/.claude/hooks/validate-pr-author-output.ps1 +39 -44
  70. package/resources/claude-customizations/.claude/hooks/validate-prd-feature-output.ps1 +43 -14
  71. package/resources/claude-customizations/.claude/hooks/validate-required-artifact-output.ps1 +66 -36
  72. package/resources/claude-customizations/.claude/hooks/validate-task-researcher-output.ps1 +31 -34
  73. package/resources/claude-customizations/.claude/lib/bash/mutate-parallel-run.sh +191 -0
  74. package/resources/claude-customizations/.claude/lib/bash/parallel-cohorts.sh +13 -3
  75. package/resources/claude-customizations/.claude/lib/bash/parallel-lane-assertion.sh +4 -2
  76. package/resources/claude-customizations/.claude/lib/bash/parallel-mutation-cli.sh +152 -0
  77. package/resources/claude-customizations/.claude/lib/bash/parallel-mutation-ops.sh +193 -0
  78. package/resources/claude-customizations/.claude/lib/bash/parallel-mutation.sh +322 -0
  79. package/resources/claude-customizations/.claude/lib/bash/remove-parallel-item.sh +146 -0
  80. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusExtraction.psm1 +23 -33
  81. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusTokenShape.psm1 +86 -6
  82. package/resources/claude-customizations/.claude/lib/ci-gate/Invoke-CiGateParser.ps1 +91 -19
  83. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorState.psm1 +6 -13
  84. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateEpicWaveBarrier.psm1 +346 -0
  85. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateIssueAdoption.psm1 +396 -0
  86. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateReceipts.psm1 +22 -15
  87. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateRemediationAccounting.psm1 +337 -0
  88. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateRoutingContract.psm1 +9 -1
  89. package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeItemResolution.psm1 +79 -2
  90. package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeRunResolution.psm1 +3 -3
  91. package/resources/claude-customizations/.claude/rules/architecture-boundaries.md +5 -5
  92. package/resources/claude-customizations/.claude/rules/csharp.md +2 -2
  93. package/resources/claude-customizations/.claude/rules/general-code-change.md +1 -1
  94. package/resources/claude-customizations/.claude/rules/general-unit-test.md +3 -2
  95. package/resources/claude-customizations/.claude/rules/orchestrator-state.md +153 -4
  96. package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +50 -5
  97. package/resources/claude-customizations/.claude/rules/plan-acceptance-gates.md +47 -8
  98. package/resources/claude-customizations/.claude/rules/quality-tiers.md +11 -7
  99. package/resources/claude-customizations/.claude/rules/shell.md +10 -8
  100. package/resources/claude-customizations/.claude/rules/typescript.md +1 -1
  101. package/resources/claude-customizations/.claude/settings.json +3 -4
  102. package/resources/claude-customizations/.claude/skills/acceptance-criteria-tracking/SKILL.md +9 -1
  103. package/resources/claude-customizations/.claude/skills/atomic-plan-contract/SKILL.md +7 -1
  104. package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/SKILL.md +11 -3
  105. package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup-worktrees.sh +11 -2
  106. package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_detached_lib.sh +1 -1
  107. package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_enumerate_lib.sh +171 -5
  108. package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_preserve_lib.sh +1 -1
  109. package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_report_records_lib.sh +6 -43
  110. package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/scripts/cleanup_worktrees_scan_helper.sh +6 -15
  111. package/resources/claude-customizations/.claude/skills/epic-orchestrate/SKILL.md +45 -11
  112. package/resources/claude-customizations/.claude/skills/epic-plan/SKILL.md +1 -1
  113. package/resources/claude-customizations/.claude/skills/evidence-and-timestamp-conventions/SKILL.md +13 -0
  114. package/resources/claude-customizations/.claude/skills/feature-promotion-lifecycle/SKILL.md +10 -0
  115. package/resources/claude-customizations/.claude/skills/feature-review-workflow/SKILL.md +7 -4
  116. package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +42 -16
  117. package/resources/claude-customizations/.claude/skills/parallel-add/SKILL.md +52 -14
  118. package/resources/claude-customizations/.claude/skills/parallel-close/SKILL.md +22 -9
  119. package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +97 -19
  120. package/resources/claude-customizations/.claude/skills/parallel-plan/SKILL.md +63 -7
  121. package/resources/claude-customizations/.claude/skills/parallel-remove/SKILL.md +35 -12
  122. package/resources/claude-customizations/.claude/skills/pr-author/SKILL.md +3 -1
  123. package/resources/claude-customizations/.claude/skills/quota-throttling/SKILL.md +1 -1
  124. package/resources/claude-customizations/.claude/skills/remediation-handoff-atomic-planner/SKILL.md +7 -3
  125. package/resources/claude-customizations/pack-manifests/core.json +25 -0
  126. package/resources/codex-and-agents-customizations/.agents/skills/acceptance-criteria-tracking/SKILL.md +9 -1
  127. package/resources/codex-and-agents-customizations/.agents/skills/architecture-boundaries/SKILL.md +5 -5
  128. package/resources/codex-and-agents-customizations/.agents/skills/atomic-executor/SKILL.md +11 -0
  129. package/resources/codex-and-agents-customizations/.agents/skills/atomic-plan-contract/SKILL.md +4 -0
  130. package/resources/codex-and-agents-customizations/.agents/skills/atomic-planner/SKILL.md +10 -0
  131. package/resources/codex-and-agents-customizations/.agents/skills/csharp/SKILL.md +2 -2
  132. package/resources/codex-and-agents-customizations/.agents/skills/csharp-qa-gate/SKILL.md +2 -2
  133. package/resources/codex-and-agents-customizations/.agents/skills/epic-orchestrate/SKILL.md +13 -3
  134. package/resources/codex-and-agents-customizations/.agents/skills/epic-plan/SKILL.md +17 -1
  135. package/resources/codex-and-agents-customizations/.agents/skills/epic-run/SKILL.md +2 -1
  136. package/resources/codex-and-agents-customizations/.agents/skills/evidence-and-timestamp-conventions/SKILL.md +13 -0
  137. package/resources/codex-and-agents-customizations/.agents/skills/feature-promotion-lifecycle/SKILL.md +1 -1
  138. package/resources/codex-and-agents-customizations/.agents/skills/feature-review/SKILL.md +30 -6
  139. package/resources/codex-and-agents-customizations/.agents/skills/feature-review-workflow/SKILL.md +16 -6
  140. package/resources/codex-and-agents-customizations/.agents/skills/general-code-change/SKILL.md +1 -1
  141. package/resources/codex-and-agents-customizations/.agents/skills/general-unit-test/SKILL.md +3 -2
  142. package/resources/codex-and-agents-customizations/.agents/skills/orchestrate/SKILL.md +43 -11
  143. package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-state/SKILL.md +41 -2
  144. package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-workflow/SKILL.md +19 -4
  145. package/resources/codex-and-agents-customizations/.agents/skills/quality-tiers/SKILL.md +11 -7
  146. package/resources/codex-and-agents-customizations/.agents/skills/remediation-handoff-atomic-planner/SKILL.md +2 -0
  147. package/resources/codex-and-agents-customizations/.agents/skills/translate-claude-to-codex/SKILL.md +1 -1
  148. package/resources/codex-and-agents-customizations/.agents-variants/csharp-legacy/skills/csharp/SKILL.md +2 -2
  149. package/resources/codex-and-agents-customizations/.agents-variants/csharp-legacy/skills/csharp-qa-gate/SKILL.md +2 -2
  150. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c1.toml +11 -0
  151. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c2.toml +11 -0
  152. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c3-elevated.toml +11 -0
  153. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c3.toml +11 -0
  154. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c4.toml +11 -0
  155. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor.toml +11 -0
  156. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c1.toml +10 -0
  157. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c2.toml +10 -0
  158. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c3-elevated.toml +10 -0
  159. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c3.toml +10 -0
  160. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c4.toml +10 -0
  161. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner.toml +10 -0
  162. package/resources/codex-and-agents-customizations/.codex/agents/epic-orchestrator.toml +2 -1
  163. package/resources/codex-and-agents-customizations/.codex/agents/feature-review.toml +6 -6
  164. package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c1.toml +1 -0
  165. package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c2.toml +1 -0
  166. package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c3-elevated.toml +1 -0
  167. package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c3.toml +1 -0
  168. package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c4.toml +1 -0
  169. package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer.toml +1 -0
  170. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c1.toml +3 -3
  171. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c2.toml +3 -3
  172. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c3-elevated.toml +3 -3
  173. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c3.toml +3 -3
  174. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c4.toml +3 -3
  175. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator.toml +3 -3
  176. package/resources/codex-and-agents-customizations/.codex/codex-web-setup.sh +318 -273
  177. package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
  178. package/resources/codex-and-agents-customizations/.codex/hooks/check-powershell-test-purity.ps1 +7 -1
  179. package/resources/codex-and-agents-customizations/.codex/hooks/check-python-test-purity.ps1 +7 -1
  180. package/resources/codex-and-agents-customizations/.codex/hooks/codex-epic-child-launch-attestation.ps1 +1 -1
  181. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-checkpoint-monotonic.ps1 +7 -1
  182. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-codex-model-routing.ps1 +8 -2
  183. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-completion-consistency.ps1 +97 -44
  184. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-completion-helpers.ps1 +105 -0
  185. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-child-worktree-binding.ps1 +7 -1
  186. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-merge-gate.ps1 +37 -5
  187. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-planning-only.ps1 +6 -0
  188. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-root-invocation.ps1 +7 -1
  189. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-wave-barrier.ps1 +7 -1
  190. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-worktree-removal-gate.ps1 +54 -41
  191. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-evidence-locations.ps1 +7 -1
  192. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-epic-scope.ps1 +35 -14
  193. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-helpers.ps1 +31 -58
  194. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-modes.ps1 +63 -47
  195. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-targets.ps1 +433 -0
  196. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate.ps1 +24 -11
  197. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-powershell-batch-budget.ps1 +8 -2
  198. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-promotion-mcp-only.ps1 +13 -4
  199. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-python-batch-budget.ps1 +8 -2
  200. package/resources/codex-and-agents-customizations/.codex/hooks/feature-folder-resolution.ps1 +373 -0
  201. package/resources/codex-and-agents-customizations/.codex/hooks/feature-review-coverage-thresholds.ps1 +85 -0
  202. package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-heredoc.ps1 +119 -0
  203. package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-invocation-operands.ps1 +198 -0
  204. package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-invocation.ps1 +254 -255
  205. package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-payload-powershell.ps1 +201 -0
  206. package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-payload.ps1 +484 -0
  207. package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-scanner.ps1 +27 -110
  208. package/resources/codex-and-agents-customizations/.codex/hooks/hook-dependency-guard.ps1 +114 -0
  209. package/resources/codex-and-agents-customizations/.codex/hooks/hook-docs-only-diff.ps1 +493 -0
  210. package/resources/codex-and-agents-customizations/.codex/hooks/validate-bash.ps1 +8 -2
  211. package/resources/codex-and-agents-customizations/.codex/hooks/validate-codex-subagent-routing.ps1 +7 -1
  212. package/resources/codex-and-agents-customizations/.codex/hooks/validate-feature-review-coverage.ps1 +24 -8
  213. package/resources/codex-and-agents-customizations/pack-manifests/core.json +9 -0
  214. package/resources/customizations/.github/agents/csharp-typed-engineer.agent.md +2 -2
  215. package/resources/customizations/.github/instructions/csharp-code-change.instructions.md +4 -4
  216. package/resources/customizations/.github/instructions/csharp-unit-test.instructions.md +2 -2
  217. package/resources/customizations/.github/skills/acceptance-criteria-tracking/SKILL.md +9 -1
  218. package/resources/customizations/.github/skills/atomic-plan-contract/SKILL.md +4 -0
  219. package/resources/customizations/.github/skills/evidence-and-timestamp-conventions/SKILL.md +13 -0
  220. package/resources/customizations/.github/skills/feature-review-workflow/SKILL.md +1 -1
  221. package/resources/powershell/PoshQC/PoshQC.Coverage.psm1 +314 -0
  222. package/resources/powershell/PoshQC/PoshQC.Testing.psm1 +25 -28
  223. package/resources/powershell/PoshQC/PoshQC.psm1 +1 -0
  224. package/resources/powershell/PoshQC/README.md +8 -2
  225. package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +2 -315
@@ -0,0 +1,322 @@
1
+ #!/usr/bin/env bash
2
+ # parallel-mutation.sh: sourceable bash port of the three removal functions of
3
+ # the parallel mutation engine, so a workspace that received the Claude
4
+ # customization payload can decide a removal, recolor the unstarted subgraph,
5
+ # and build the removal's mutations[] record without an interpreter.
6
+ #
7
+ # Parity reference (the repository authority; this file reproduces its
8
+ # observable outputs and rejection messages byte for byte):
9
+ # decide_removal scripts/dev_tools/parallel_mutation_protocol.py
10
+ # recolor_unstarted scripts/dev_tools/parallel_mutation_protocol.py
11
+ # build_remove_entry scripts/dev_tools/_parallel_mutation_entries.py
12
+ # MutationEntry scripts/dev_tools/_parallel_mutation_models.py
13
+ # rejection messages scripts/dev_tools/_parallel_mutation_errors.py
14
+ #
15
+ # Declared divergences (also listed in both parity lanes):
16
+ # 1. A `--removal-disposition` value outside `detach abandon` is a usage error
17
+ # (exit 2) raised by the command line before the library runs. A direct
18
+ # library call and the Python `UnknownEnumMemberError` both reject it with
19
+ # the same message, which lists `detach, abandon`
20
+ # (spec D5; aligned under #890).
21
+ # 2. `at` is a caller-supplied string instead of a `datetime`.
22
+ # 3. Option abbreviations are rejected.
23
+ #
24
+ # Contract. Every public function is pure: no file I/O, no network access, no
25
+ # clock read, and no external utility beyond the ones parallel-cohorts.sh
26
+ # already uses. Each sets PM_RESULT to one compact JSON object and returns 0 on
27
+ # success, or sets PM_ERROR to the exact reference message and returns 1 on a
28
+ # rule rejection. Integer arguments are expected in canonical decimal form; the
29
+ # entry point remove-parallel-item.sh validates them before calling here.
30
+ #
31
+ # shellcheck disable=SC2034
32
+ # SC2034 is disabled file-wide because PM_RESULT and PM_ERROR are written here
33
+ # and read by the entry point remove-parallel-item.sh and by the bats suites,
34
+ # which shellcheck analyses separately.
35
+
36
+ # Resolve this file's own directory so its dependencies source regardless of
37
+ # the caller's working directory.
38
+ PM_LIB_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
39
+ # shellcheck source=.claude/lib/bash/parallel-cohorts.sh
40
+ # shellcheck disable=SC1091
41
+ source "$PM_LIB_DIR/parallel-cohorts.sh"
42
+
43
+ # The compact JSON object produced by the most recent success.
44
+ PM_RESULT=""
45
+
46
+ # The rejection message set by the most recent non-zero return.
47
+ PM_ERROR=""
48
+
49
+ # Literal prefix shared by every engine rejection message.
50
+ PM_REJECTION_PREFIX="Parallel mutation rejected:"
51
+
52
+ # Item states whose items have not started (vertices of the recolored subgraph).
53
+ PM_UNSTARTED_STATES="proposed admitted prepared scheduled"
54
+
55
+ # The single item state that pins an item against recoloring.
56
+ PM_PINNED_STATE="in_flight"
57
+
58
+ # The two accepted dispositions of an in-flight removal, in canonical order.
59
+ PM_VALID_DISPOSITIONS="detach abandon"
60
+
61
+ pm_fail() {
62
+ # Record a rejection message and return 1.
63
+ #
64
+ # Args: $1 = the complete, literal rejection message.
65
+ PM_ERROR="$1"
66
+ return 1
67
+ }
68
+
69
+ pm_json_string() {
70
+ # Echo a JSON string literal for a raw value.
71
+ #
72
+ # Args: $1 = the raw value. Escapes the backslash first so a later escape's
73
+ # own backslash is not doubled, then the quote and the three control
74
+ # characters a caller-supplied token could carry.
75
+ local body="$1"
76
+ body=${body//\\/\\\\}
77
+ body=${body//\"/\\\"}
78
+ body=${body//$'\n'/\\n}
79
+ body=${body//$'\r'/\\r}
80
+ body=${body//$'\t'/\\t}
81
+ printf '"%s"' "$body"
82
+ }
83
+
84
+ pm_json_nullable_string() {
85
+ # Echo a JSON string literal, or null for an empty value.
86
+ #
87
+ # Args: $1 = the raw value; empty means absent (Python None).
88
+ if [[ -z ${1-} ]]; then
89
+ printf 'null'
90
+ else
91
+ pm_json_string "$1"
92
+ fi
93
+ }
94
+
95
+ pm_unknown_item_message() {
96
+ # Echo the UnknownItemError message for one item key.
97
+ #
98
+ # Args: $1 = the offending integer key, rendered as Python repr renders it.
99
+ printf '%s item key %s does not resolve to a tracked items[].issue_num.' \
100
+ "$PM_REJECTION_PREFIX" "$1"
101
+ }
102
+
103
+ pm_entry_contract_message() {
104
+ # Echo the MutationEntryContractError message for one op.
105
+ #
106
+ # Args: $1 = field name, $2 = the already-rendered repr of the value, $3 =
107
+ # the op name, optional and defaulting to `remove` so every removal call
108
+ # site passes two arguments unchanged.
109
+ printf "Parallel mutation entry rejected: %s %s violates the F3 mutations[] contract for op '%s'." \
110
+ "$1" "$2" "${3:-remove}"
111
+ }
112
+
113
+ pm_decide_removal() {
114
+ # Decide the outcome of removing one item, per the FR2 behavior table.
115
+ #
116
+ # Args: $1 = item key, $2 = the item's state, or empty when the key resolves
117
+ # to no tracked item, $3 = disposition, or empty when none was supplied.
118
+ # Returns 0 with PM_RESULT holding the decision, 1 with PM_ERROR holding the
119
+ # reference rejection message.
120
+ local item_key="$1" state="${2-}" disposition="${3-}" prior
121
+ PM_RESULT=""
122
+ PM_ERROR=""
123
+ # An absent record and a non-positive key both name nothing the run tracks;
124
+ # the reference rejects the latter while constructing the item record.
125
+ if [[ -z $state ]] || ((item_key <= 0)); then
126
+ pm_fail "$(pm_unknown_item_message "$item_key")"
127
+ return 1
128
+ fi
129
+ if ! pc_enum_members_contains "$PC_VALID_ITEM_STATES" "$state"; then
130
+ pm_fail "$PM_REJECTION_PREFIX item $item_key state $(pc_repr str "$state") is not one of $PC_VALID_ITEM_STATES."
131
+ return 1
132
+ fi
133
+
134
+ # Routing table, one branch per FR2 row. An unstarted item is a vertex of
135
+ # the recolored subgraph, so its removal recomputes and records no
136
+ # disposition even when one was supplied.
137
+ if pc_contains_word "$PM_UNSTARTED_STATES" "$state"; then
138
+ PM_RESULT="{\"item_key\":$item_key,\"prior_state\":$(pm_json_string "$state"),\"new_state\":\"withdrawn\",\"disposition\":null,\"triggers_recompute\":true}"
139
+ return 0
140
+ fi
141
+ if [[ $state == "$PM_PINNED_STATE" ]]; then
142
+ if [[ -z $disposition ]]; then
143
+ pm_fail "$PM_REJECTION_PREFIX removal of in-flight item $item_key requires an explicit disposition, one of detach, abandon; no default is inferred."
144
+ return 1
145
+ fi
146
+ if ! pc_contains_word "$PM_VALID_DISPOSITIONS" "$disposition"; then
147
+ pm_fail "$PM_REJECTION_PREFIX item $item_key disposition $(pc_repr str "$disposition") is not one of ${PM_VALID_DISPOSITIONS// /, }."
148
+ return 1
149
+ fi
150
+ prior="$PM_PINNED_STATE"
151
+ PM_RESULT="{\"item_key\":$item_key,\"prior_state\":\"$prior\",\"new_state\":\"withdrawn\",\"disposition\":\"$disposition\",\"triggers_recompute\":false}"
152
+ return 0
153
+ fi
154
+ if [[ $state == merged ]]; then
155
+ pm_fail "$PM_REJECTION_PREFIX item $item_key is already merged into main and cannot be removed."
156
+ return 1
157
+ fi
158
+ # Only withdrawn and blocked remain; neither is a live removal target.
159
+ pm_fail "$(pm_unknown_item_message "$item_key")"
160
+ return 1
161
+ }
162
+
163
+ pm_smallest_overlap() {
164
+ # Echo the smallest key present in both key lists, or nothing.
165
+ #
166
+ # Args: $1 = space-separated unstarted keys, $2 = space-separated pinned keys.
167
+ local key smallest=""
168
+ pcoh_split_words "$1"
169
+ local -a unstarted_keys=("${PCOH_WORDS[@]}")
170
+ pcoh_split_words "$2"
171
+ local pinned=" ${PCOH_WORDS[*]} "
172
+ for key in "${unstarted_keys[@]}"; do
173
+ [[ $pinned == *" $key "* ]] || continue
174
+ if [[ -z $smallest ]] || ((key < smallest)); then
175
+ smallest="$key"
176
+ fi
177
+ done
178
+ printf '%s' "$smallest"
179
+ }
180
+
181
+ pm_render_assignments() {
182
+ # Echo the cohort_assignments object for a cohort list and an offset.
183
+ #
184
+ # Args: $1 = compact JSON array of arrays from pcoh_compute_cohorts, $2 = the
185
+ # uniform offset. List position is the local color index; keys are emitted
186
+ # in ascending numeric order, matching the reference rendering.
187
+ local cohorts="$1" offset="$2" group key index=0 lines="" ordered rendered=""
188
+ # Strip the outer brackets and the first and last inner bracket, then turn
189
+ # each `],[` boundary into a space so every group is one comma-joined word.
190
+ cohorts=${cohorts#[}
191
+ cohorts=${cohorts%]}
192
+ cohorts=${cohorts#[}
193
+ cohorts=${cohorts%]}
194
+ pcoh_split_words "${cohorts//'],['/ }"
195
+ local -a groups=("${PCOH_WORDS[@]}")
196
+ for group in "${groups[@]}"; do
197
+ pcoh_split_words "${group//,/ }"
198
+ for key in "${PCOH_WORDS[@]}"; do
199
+ lines="$lines$key $((offset + index))"$'\n'
200
+ done
201
+ index=$((index + 1))
202
+ done
203
+ if [[ -n $lines ]]; then
204
+ ordered=$(printf '%s' "$lines" | LC_ALL=C sort -k1,1n)
205
+ while IFS=' ' read -r key index; do
206
+ [[ -n $key ]] || continue
207
+ [[ -z $rendered ]] || rendered="$rendered,"
208
+ rendered="$rendered\"$key\":$index"
209
+ done <<<"$ordered"
210
+ fi
211
+ printf '{%s}' "$rendered"
212
+ }
213
+
214
+ pm_recolor_unstarted() {
215
+ # Recolor the unstarted subgraph, leaving every pinned item untouched.
216
+ #
217
+ # Args: $1 = space-separated unstarted keys, $2 = space-separated `a:b`
218
+ # conflict edges over all items, $3 = space-separated pinned keys, $4 = the
219
+ # generation before this recolor, $5 = current_cohort, $6 =
220
+ # highest_pinned_cohort. Returns 0 with PM_RESULT holding the assignments
221
+ # and the next generation, 1 with PM_ERROR holding the reference message.
222
+ local unstarted="$1" edges="$2" pinned="$3" generation="$4"
223
+ local current_cohort="$5" highest_pinned="$6"
224
+ local overlap edge first second crosses=0 induced="" offset
225
+ PM_RESULT=""
226
+ PM_ERROR=""
227
+
228
+ # Check order is part of the contract: overlap, then the negative base,
229
+ # then the pinned barrier, then the induced coloring.
230
+ overlap=$(pm_smallest_overlap "$unstarted" "$pinned")
231
+ if [[ -n $overlap ]]; then
232
+ pm_fail "$(pm_unknown_item_message "$overlap")"
233
+ return 1
234
+ fi
235
+ if ((current_cohort < 0)); then
236
+ pm_fail "current_cohort must be >= 0 per F3 invariant 12; received $current_cohort."
237
+ return 1
238
+ fi
239
+
240
+ pcoh_split_words "$unstarted"
241
+ local unstarted_set=" ${PCOH_WORDS[*]} "
242
+ pcoh_split_words "$pinned"
243
+ local pinned_set=" ${PCOH_WORDS[*]} "
244
+ pcoh_split_words "$edges"
245
+ local -a edge_list=("${PCOH_WORDS[@]}")
246
+ # Decide the pinned barrier before the induced restriction discards the
247
+ # unstarted-to-pinned edges, then keep only edges with both ends unstarted.
248
+ for edge in "${edge_list[@]}"; do
249
+ first="${edge%%:*}"
250
+ second="${edge#*:}"
251
+ if [[ $unstarted_set == *" $first "* && $pinned_set == *" $second "* ]] ||
252
+ [[ $unstarted_set == *" $second "* && $pinned_set == *" $first "* ]]; then
253
+ crosses=1
254
+ fi
255
+ if [[ $unstarted_set == *" $first "* && $unstarted_set == *" $second "* ]]; then
256
+ induced="$induced $edge"
257
+ fi
258
+ done
259
+
260
+ if ! pcoh_compute_cohorts "$unstarted" "$induced"; then
261
+ pm_fail "$PCOH_ERROR"
262
+ return 1
263
+ fi
264
+
265
+ # A uniform shift keeps the local-to-absolute map injective.
266
+ if ((crosses == 1)); then
267
+ offset=$((highest_pinned + 1))
268
+ else
269
+ offset="$current_cohort"
270
+ fi
271
+ PM_RESULT="{\"cohort_assignments\":$(pm_render_assignments "$PCOH_RESULT" "$offset"),\"generation\":$((generation + 1))}"
272
+ return 0
273
+ }
274
+
275
+ pm_build_remove_entry() {
276
+ # Build the single mutations[] record for an accepted removal.
277
+ #
278
+ # Args: $1 = item key, $2 = prior state, $3 = disposition, or empty for
279
+ # none, $4 = `true` when the removal recomputes, otherwise `false`, $5 = the
280
+ # generation before the removal, $6 = the `at` timestamp string. Returns 0
281
+ # with PM_RESULT holding the record in F3 field order, 1 with PM_ERROR
282
+ # holding the reference contract message.
283
+ local item_key="$1" prior_state="$2" disposition="${3-}" recompute="$4"
284
+ local generation="$5" at="$6" stamped
285
+ PM_RESULT=""
286
+ PM_ERROR=""
287
+ # The stamped generation is computed first; the record validation then
288
+ # runs in field order, so the first violation names the earliest field.
289
+ stamped="$generation"
290
+ if [[ $recompute == true ]]; then
291
+ stamped=$((generation + 1))
292
+ fi
293
+ if ((stamped < 0)); then
294
+ pm_fail "$(pm_entry_contract_message recolor_generation "$stamped")"
295
+ return 1
296
+ fi
297
+ if ((item_key <= 0)); then
298
+ pm_fail "$(pm_entry_contract_message item_key "$item_key")"
299
+ return 1
300
+ fi
301
+ if ! pc_enum_members_contains "$PC_VALID_ITEM_STATES" "$prior_state"; then
302
+ pm_fail "$PM_REJECTION_PREFIX item $item_key state $(pc_repr str "$prior_state") is not one of $PC_VALID_ITEM_STATES."
303
+ return 1
304
+ fi
305
+ # An in-flight removal must record a valid disposition; every other
306
+ # removal must record none.
307
+ if [[ $prior_state == "$PM_PINNED_STATE" ]]; then
308
+ if [[ -z $disposition ]]; then
309
+ pm_fail "$(pm_entry_contract_message disposition None)"
310
+ return 1
311
+ fi
312
+ if ! pc_contains_word "$PM_VALID_DISPOSITIONS" "$disposition"; then
313
+ pm_fail "$(pm_entry_contract_message disposition "$(pc_repr str "$disposition")")"
314
+ return 1
315
+ fi
316
+ elif [[ -n $disposition ]]; then
317
+ pm_fail "$(pm_entry_contract_message disposition "$(pc_repr str "$disposition")")"
318
+ return 1
319
+ fi
320
+ PM_RESULT="{\"op\":\"remove\",\"item_key\":$item_key,\"at\":$(pm_json_string "$at"),\"prior_state\":$(pm_json_string "$prior_state"),\"new_state\":\"withdrawn\",\"disposition\":$(pm_json_nullable_string "$disposition"),\"recolor_generation\":$stamped}"
321
+ return 0
322
+ }
@@ -0,0 +1,146 @@
1
+ #!/usr/bin/env bash
2
+ # remove-parallel-item.sh: destination-portable command-line entry point for
3
+ # the parallel-remove skill's decision, recolor, and audit-record steps. It
4
+ # exists so a workspace that received the Claude customization payload can run
5
+ # those steps with nothing but bash -- no interpreter, no repository checkout.
6
+ #
7
+ # Usage:
8
+ # bash .claude/lib/bash/remove-parallel-item.sh decide --item <key> \
9
+ # [--state <state>] [--removal-disposition detach|abandon]
10
+ # bash .claude/lib/bash/remove-parallel-item.sh recolor --unstarted "<k> ..." \
11
+ # [--edges "<a>:<b> ..."] --pinned "<k> ..." --generation <g> \
12
+ # --current-cohort <c> --highest-pinned-cohort <h>
13
+ # bash .claude/lib/bash/remove-parallel-item.sh entry --item <key> \
14
+ # --prior-state <state> [--removal-disposition detach|abandon] \
15
+ # --recompute true|false --generation <g> [--at <timestamp>]
16
+ #
17
+ # `decide` without `--state` means the key resolves to no tracked item.
18
+ # `--unstarted`, `--edges`, and `--pinned` accept an empty string. Integer
19
+ # tokens match `-?(0|[1-9][0-9]*)`. `--at` defaults to the current UTC time as
20
+ # yyyy-MM-ddTHH-mm; the clock is read only when `--at` is omitted.
21
+ #
22
+ # Exit-code contract:
23
+ # exit 0 success; one compact JSON object on stdout
24
+ # exit 1 rule rejection; the reference implementation's exact message as
25
+ # one stderr line
26
+ # exit 2 usage error; one stderr line beginning
27
+ # "PARALLEL_MUTATION_ERROR: usage error:"
28
+ # Argument validation runs first, then rule rejection, then output.
29
+ #
30
+ # Parity reference: scripts/dev_tools/parallel_mutation_protocol.py
31
+ # (decide_removal, recolor_unstarted) and
32
+ # scripts/dev_tools/_parallel_mutation_entries.py (build_remove_entry).
33
+ #
34
+ # Declared divergences (also listed in both parity lanes):
35
+ # 1. A `--removal-disposition` value outside `detach abandon` is a usage error
36
+ # (exit 2) raised by the command line before the library runs. A direct
37
+ # library call and the Python `UnknownEnumMemberError` both reject it with
38
+ # the same message, which lists `detach, abandon`
39
+ # (spec D5; aligned under #890).
40
+ # 2. `at` is a caller-supplied string instead of a `datetime`.
41
+ # 3. Option abbreviations are rejected.
42
+ set -euo pipefail
43
+
44
+ # Resolve this script's own directory so the libraries source regardless of
45
+ # cwd. The option-parsing helpers (pmcli_*) are shared with
46
+ # mutate-parallel-run.sh through parallel-mutation-cli.sh (issue #890).
47
+ RPI_SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
48
+ # shellcheck source=.claude/lib/bash/parallel-mutation.sh
49
+ # shellcheck disable=SC1091
50
+ source "$RPI_SCRIPT_DIR/parallel-mutation.sh"
51
+ # shellcheck source=.claude/lib/bash/parallel-mutation-cli.sh
52
+ # shellcheck disable=SC1091
53
+ source "$RPI_SCRIPT_DIR/parallel-mutation-cli.sh"
54
+
55
+ pc_enforce_c_locale
56
+
57
+ rpi_disposition() {
58
+ # Validate the optional disposition; store it, or empty when absent.
59
+ PMCLI_VALUE="${PMCLI_OPTS["removal-disposition"]-}"
60
+ if [[ -n ${PMCLI_OPTS["removal-disposition"]+set} ]] && ! pc_contains_word "detach abandon" "$PMCLI_VALUE"; then
61
+ pmcli_usage_error "--removal-disposition must be one of detach, abandon; found: $PMCLI_VALUE"
62
+ fi
63
+ }
64
+
65
+ rpi_decide() {
66
+ # Run the decide subcommand.
67
+ local item state="" disposition rc=0
68
+ pmcli_parse_options "item state removal-disposition" "$@"
69
+ pmcli_require item
70
+ pmcli_integer "${PMCLI_OPTS[item]}" "--item"
71
+ item="$PMCLI_VALUE"
72
+ if [[ -n ${PMCLI_OPTS[state]+set} ]]; then
73
+ state="${PMCLI_OPTS[state]}"
74
+ pc_enum_members_contains "$PC_VALID_ITEM_STATES" "$state" ||
75
+ pmcli_usage_error "--state must be one of $PC_VALID_ITEM_STATES; found: $state"
76
+ fi
77
+ rpi_disposition
78
+ disposition="$PMCLI_VALUE"
79
+ pm_decide_removal "$item" "$state" "$disposition" || rc=$?
80
+ pmcli_emit "$rc"
81
+ }
82
+
83
+ rpi_recolor() {
84
+ # Run the recolor subcommand.
85
+ local unstarted edges pinned generation current highest rc=0
86
+ pmcli_parse_options "unstarted edges pinned generation current-cohort highest-pinned-cohort" "$@"
87
+ pmcli_require unstarted pinned generation current-cohort highest-pinned-cohort
88
+ pmcli_integer_list "${PMCLI_OPTS[unstarted]}" "--unstarted"
89
+ unstarted="$PMCLI_VALUE"
90
+ pmcli_edge_list "${PMCLI_OPTS[edges]-}"
91
+ edges="$PMCLI_VALUE"
92
+ pmcli_integer_list "${PMCLI_OPTS[pinned]}" "--pinned"
93
+ pinned="$PMCLI_VALUE"
94
+ pmcli_integer "${PMCLI_OPTS[generation]}" "--generation"
95
+ generation="$PMCLI_VALUE"
96
+ pmcli_integer "${PMCLI_OPTS["current-cohort"]}" "--current-cohort"
97
+ current="$PMCLI_VALUE"
98
+ pmcli_integer "${PMCLI_OPTS["highest-pinned-cohort"]}" "--highest-pinned-cohort"
99
+ highest="$PMCLI_VALUE"
100
+ pm_recolor_unstarted "$unstarted" "$edges" "$pinned" "$generation" "$current" "$highest" || rc=$?
101
+ pmcli_emit "$rc"
102
+ }
103
+
104
+ rpi_entry() {
105
+ # Run the entry subcommand.
106
+ local item prior disposition recompute generation at rc=0
107
+ pmcli_parse_options "item prior-state removal-disposition recompute generation at" "$@"
108
+ pmcli_require item prior-state recompute generation
109
+ pmcli_integer "${PMCLI_OPTS[item]}" "--item"
110
+ item="$PMCLI_VALUE"
111
+ prior="${PMCLI_OPTS["prior-state"]}"
112
+ [[ $prior =~ ^[A-Za-z_]+$ ]] || pmcli_usage_error "--prior-state must match [A-Za-z_]+; found: $prior"
113
+ rpi_disposition
114
+ disposition="$PMCLI_VALUE"
115
+ recompute="${PMCLI_OPTS[recompute]}"
116
+ [[ $recompute == true || $recompute == false ]] ||
117
+ pmcli_usage_error "--recompute must be true or false; found: $recompute"
118
+ pmcli_integer "${PMCLI_OPTS[generation]}" "--generation"
119
+ generation="$PMCLI_VALUE"
120
+ # The clock is read only when --at is omitted.
121
+ pmcli_timestamp
122
+ at="$PMCLI_VALUE"
123
+ pm_build_remove_entry "$item" "$prior" "$disposition" "$recompute" "$generation" "$at" || rc=$?
124
+ pmcli_emit "$rc"
125
+ }
126
+
127
+ rpi_main() {
128
+ # Dispatch on the subcommand.
129
+ (($# > 0)) || pmcli_usage_error "missing subcommand; expected one of decide, recolor, entry"
130
+ local subcommand="$1"
131
+ shift
132
+ case "$subcommand" in
133
+ decide) rpi_decide "$@" ;;
134
+ recolor) rpi_recolor "$@" ;;
135
+ entry) rpi_entry "$@" ;;
136
+ *) pmcli_usage_error "unknown subcommand: $subcommand" ;;
137
+ esac
138
+ }
139
+
140
+ # Guard so the file can be sourced without executing main. main's return code
141
+ # is captured and re-exited explicitly as the final statement.
142
+ if [[ ${BASH_SOURCE[0]} == "${0}" ]]; then
143
+ rpi_rc=0
144
+ rpi_main "$@" || rpi_rc=$?
145
+ exit "$rpi_rc"
146
+ fi
@@ -68,31 +68,23 @@ $script:PlanTaskPattern = [regex]::new(
68
68
  # on its own line, from producing spurious spans.
69
69
  $script:InlineCodeSpanPattern = [regex]::new('`([^`]+)`')
70
70
 
71
- # Top-level directories of this repository. A token starting with one of these is
72
- # accepted without needing a recognized extension, which admits ** globs naming a
73
- # subtree. artifacts/ is deliberately absent (issue #489): the process-artifact
74
- # tree is read by mandate rather than written by a work item, so admitting
75
- # artifacts/** as a subtree claim made unrelated items contend.
71
+ # Top-level directories of this repository. A wildcard token starting with one of
72
+ # these is accepted without its final component naming a file (issue #797), which
73
+ # admits ** globs naming a subtree. artifacts/ is deliberately absent (issue
74
+ # #489): the process-artifact tree is read by mandate rather than written by a
75
+ # work item, so admitting artifacts/** as a subtree claim made unrelated items
76
+ # contend.
76
77
  $script:KnownTopLevelSegment = @(
77
78
  'scripts/', 'tests/', 'docs/', 'config/', 'schemas/', 'packages/',
78
79
  'extensions/', '.claude/', '.codex/', '.github/', '.agents/'
79
80
  )
80
81
 
81
82
  # A file citation may carry a trailing line reference such as :90. The suffix is
82
- # stripped before the extension test so a line-anchored citation keeps the
83
- # acceptance its unanchored form has; the token itself is recorded verbatim.
83
+ # stripped before the file-shape test (issue #797) so a line-anchored citation
84
+ # keeps the acceptance its unanchored form has; the token itself is recorded
85
+ # verbatim.
84
86
  $script:LineSuffixPattern = [regex]::new(':\d+$')
85
87
 
86
- # Fallback acceptance rule: a token shaped <segment>/.../<name>.<ext> counts as a
87
- # repository path when its final component carries one of these extensions.
88
- $script:RecognizedPathExtension = [System.Collections.Generic.HashSet[string]]::new(
89
- [string[]] @(
90
- 'cfg', 'cs', 'csproj', 'ini', 'js', 'json', 'jsx', 'lock', 'md', 'ps1',
91
- 'psd1', 'psm1', 'py', 'sh', 'sln', 'toml', 'ts', 'tsx', 'txt', 'xml',
92
- 'yaml', 'yml'
93
- ),
94
- [StringComparer]::Ordinal)
95
-
96
88
  # Classification vocabulary for accepted path tokens. Concrete entries take part
97
89
  # in exact-match checks; glob entries cannot and are matched by pattern.
98
90
  $script:PathKindConcrete = 'concrete'
@@ -249,8 +241,11 @@ function Get-PathTokenKind {
249
241
  bare word such as a function name is a contract identifier, not a path.
250
242
  It must also be repository-relative: a leading separator marks an absolute
251
243
  path and a colon in the leading segment marks a URL scheme or a Windows
252
- drive. Acceptance then requires one of the two documented shape rules, a
253
- known top-level segment or a recognized final extension.
244
+ drive. A wildcard-free token is then accepted only when its final
245
+ component, after any :<line> suffix is stripped, names a file according
246
+ to Test-FileShapedComponent (a letter-led extension or a known file
247
+ name, issue #797); a wildcard token needs a known top-level segment or
248
+ such a final component.
254
249
 
255
250
  A token carrying any configured placeholder or interpolation marker is
256
251
  rejected wherever the marker sits, because it documents a shape rather
@@ -305,7 +300,7 @@ function Get-PathTokenKind {
305
300
  # disagree, and putting the cheaper marker scan first would only add work to
306
301
  # the common accepted case. It runs BEFORE the separator guard because a
307
302
  # marker-bearing token frequently does carry a separator and would otherwise
308
- # sail past that guard and reach the extension rule, which accepts it: the
303
+ # sail past that guard and reach the file-shape rule, which accepts it: the
309
304
  # dominant corpus shape is an angle-bracketed leading segment followed by a
310
305
  # real .md tail, and that is exactly the token this guard exists to reject.
311
306
  if (Test-PlaceholderMarker -Token $Token) {
@@ -320,26 +315,20 @@ function Get-PathTokenKind {
320
315
  return $null
321
316
  }
322
317
 
323
- # Read the final component's extension for the fallback acceptance rule; a
324
- # component with no dot (a directory name or **) has no extension. A trailing
325
- # line reference is stripped first so file.md:90 reads as md rather than as
326
- # the unrecognized extension md:90.
318
+ # Decide whether the final component names a file (issue #797): a letter-led
319
+ # extension or a known file name does; a directory name, a dot-directory, **,
320
+ # a digit-led tail, and a trailing dot do not. A trailing line reference is
321
+ # stripped first so file.md:90 reads as file.md rather than as md:90.
327
322
  $finalComponent = $script:LineSuffixPattern.Replace(
328
323
  $Token.Substring($Token.LastIndexOf('/') + 1), '')
329
- $extension = ''
330
- $dotIndex = $finalComponent.LastIndexOf('.')
331
- if ($dotIndex -ge 0) {
332
- $extension = $finalComponent.Substring($dotIndex + 1).ToLowerInvariant()
333
- }
334
-
335
- $hasExtension = $script:RecognizedPathExtension.Contains($extension)
324
+ $namesFile = Test-FileShapedComponent -Component $finalComponent
336
325
 
337
326
  # A wildcard-free token must name a file, not a directory (issue #489). A
338
327
  # directory-shaped token such as scripts/dev_tools is a location reference,
339
328
  # not a write claim, and admitting it made every item touching anything under
340
329
  # that directory contend at the path level.
341
330
  if ($Token.IndexOf('*') -lt 0) {
342
- if ($hasExtension) {
331
+ if ($namesFile) {
343
332
  return $script:PathKindConcrete
344
333
  }
345
334
  return $null
@@ -356,7 +345,7 @@ function Get-PathTokenKind {
356
345
  # A wildcard-bearing token must still satisfy one of the two documented shape
357
346
  # rules; failing both means the token is prose or a non-path expression that
358
347
  # merely contains a separator, so it is dropped.
359
- if (-not $hasKnownSegment -and -not $hasExtension) {
348
+ if (-not $hasKnownSegment -and -not $namesFile) {
360
349
  return $null
361
350
  }
362
351
 
@@ -469,6 +458,7 @@ Export-ModuleMember -Function `
469
458
  Get-InlineCodeToken, `
470
459
  Test-PlaceholderMarker, `
471
460
  Test-MultipleFeatureFolderSpan, `
461
+ Test-FileShapedComponent, `
472
462
  Get-PathTokenKind, `
473
463
  Get-PathFromLine, `
474
464
  Get-PlanPaths