@danmoisan/drm-copilot-mcp 1.0.0 → 1.0.7
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 +814 -194
- package/package.json +1 -1
- package/resources/claude-customizations/.claude/agents/commit-message.md +42 -0
- package/resources/claude-customizations/.claude/agents/epic-orchestrator.md +127 -0
- package/resources/claude-customizations/.claude/agents/human-exception-runbook.md +48 -0
- package/resources/claude-customizations/.claude/agents/orchestrator.md +67 -2
- package/resources/claude-customizations/.claude/agents/pr-author.md +37 -21
- package/resources/claude-customizations/.claude/hooks/check-powershell-test-purity.ps1 +103 -65
- package/resources/claude-customizations/.claude/hooks/check-python-test-purity.ps1 +12 -9
- package/resources/claude-customizations/.claude/hooks/enforce-checkpoint-monotonic.ps1 +24 -16
- package/resources/claude-customizations/.claude/hooks/enforce-completion-consistency.ps1 +19 -13
- package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate.ps1 +304 -0
- package/resources/claude-customizations/.claude/hooks/enforce-epic-wave-barrier.ps1 +304 -0
- package/resources/claude-customizations/.claude/hooks/enforce-epic-worktree-removal-gate.ps1 +237 -0
- package/resources/claude-customizations/.claude/hooks/enforce-evidence-locations.ps1 +16 -12
- package/resources/claude-customizations/.claude/hooks/enforce-feature-folder-order.ps1 +14 -10
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +35 -8
- package/resources/claude-customizations/.claude/hooks/enforce-powershell-batch-budget.ps1 +17 -14
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.epic-base-branch.ps1 +104 -0
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.ps1 +268 -93
- package/resources/claude-customizations/.claude/hooks/enforce-prd-feature-before-planner.ps1 +19 -11
- package/resources/claude-customizations/.claude/hooks/enforce-promotion-mcp-only.ps1 +28 -6
- package/resources/claude-customizations/.claude/hooks/enforce-python-batch-budget.ps1 +17 -14
- package/resources/claude-customizations/.claude/hooks/validate-bash.ps1 +146 -36
- package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output.ps1 +19 -7
- package/resources/claude-customizations/.claude/rules/orchestrator-state.md +36 -0
- package/resources/claude-customizations/.claude/settings.json +34 -1
- package/resources/claude-customizations/.claude/skills/epic-orchestrate/SKILL.md +252 -0
- package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +42 -13
- package/resources/claude-customizations/.claude/skills/pr-author/SKILL.md +28 -0
- package/resources/claude-customizations/.claude/skills/remediation-handoff-atomic-planner/SKILL.md +22 -18
- package/resources/claude-customizations/pack-manifests/core.json +8 -0
- package/resources/codex-and-agents-customizations/.agents/skills/orchestrate/SKILL.md +6 -3
- package/resources/codex-and-agents-customizations/.agents/skills/remediation-handoff-atomic-planner/SKILL.md +1 -1
- package/resources/codex-and-agents-customizations/.agents-variants/csharp-legacy/skills/csharp/SKILL.md +65 -0
- package/resources/codex-and-agents-customizations/.agents-variants/csharp-legacy/skills/csharp-qa-gate/SKILL.md +79 -0
- package/resources/codex-and-agents-customizations/.agents-variants/csharp-legacy/skills/invoke-csharp-engineer/SKILL.md +66 -0
- package/resources/codex-and-agents-customizations/.codex/config.toml +1 -25
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-pr-author-skill.ps1 +257 -93
- package/resources/codex-and-agents-customizations/.codex-variants/csharp-legacy/agents/csharp-typed-engineer.toml +98 -0
- package/resources/codex-and-agents-customizations/pack-manifests/core.json +24 -0
- package/resources/codex-and-agents-customizations/pack-manifests/csharp-legacy.json +11 -0
- package/resources/codex-and-agents-customizations/pack-manifests/csharp-modern.json +10 -0
- package/resources/codex-and-agents-customizations/pack-manifests/powershell.json +15 -0
- package/resources/codex-and-agents-customizations/pack-manifests/python.json +15 -0
- package/resources/codex-and-agents-customizations/pack-manifests/typescript.json +9 -0
- package/resources/config/orchestration-routing.json +92 -0
- package/resources/customizations/.github/agents/pr-author.agent.md +24 -18
- package/resources/icon.png +0 -0
- package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +11 -0
- package/resources/codex-and-agents-customizations/.github/workflows/_validate-orchestrator-state.yml +0 -68
- package/resources/codex-and-agents-customizations/.github/workflows/validate-orchestrator-state.yml +0 -15
package/package.json
CHANGED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: commit-message
|
|
3
|
+
description: Read-only project-scoped agent that runs the commit-message skill to generate a conventional commit message from the currently staged diff. It inspects the staged changes with git diff and recent history with git log, then returns the proposed commit message text. It does not stage, commit, push, or modify any file; the caller performs git add and git commit with the returned message.
|
|
4
|
+
model: haiku
|
|
5
|
+
skills:
|
|
6
|
+
- commit-message
|
|
7
|
+
memory: project
|
|
8
|
+
tools:
|
|
9
|
+
- Read
|
|
10
|
+
- "Bash(git log *)"
|
|
11
|
+
- "Bash(git diff *)"
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Commit-Message Agent
|
|
15
|
+
|
|
16
|
+
You are the dedicated commit-message generation agent. Your sole responsibility is to read the
|
|
17
|
+
currently staged diff and produce a single conventional-commit message for it using the
|
|
18
|
+
`commit-message` skill. You are read-only: you inspect the repository state and return message
|
|
19
|
+
text. You never stage, commit, push, or edit any file.
|
|
20
|
+
|
|
21
|
+
## Skill
|
|
22
|
+
|
|
23
|
+
Apply the `commit-message` skill (`.claude/skills/commit-message/SKILL.md`) as the canonical
|
|
24
|
+
workflow for constructing the message. The skill defines the conventional-commit format, the
|
|
25
|
+
subject-line and body conventions, and the required trailer.
|
|
26
|
+
|
|
27
|
+
## Inputs
|
|
28
|
+
|
|
29
|
+
- The staged diff, read via `git diff --staged` (and `git diff --staged --stat` for scope).
|
|
30
|
+
- Recent history, read via `git log` when prior commit style must be matched.
|
|
31
|
+
|
|
32
|
+
## Output
|
|
33
|
+
|
|
34
|
+
- Return the proposed commit message text to the caller. The caller (the orchestrator) performs
|
|
35
|
+
`git add` and `git commit -m "<generated message>"`; the commit action is never performed by this
|
|
36
|
+
agent.
|
|
37
|
+
|
|
38
|
+
## Constraints
|
|
39
|
+
|
|
40
|
+
- Read-only tool surface: `Read`, `Bash(git log *)`, and `Bash(git diff *)` only. No write, no
|
|
41
|
+
commit, no push, no network.
|
|
42
|
+
- Tone policy applies to the generated message: professional, factual, and neutral.
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: epic-orchestrator
|
|
3
|
+
description: Deterministic epic-scale orchestrator that schedules a dependency graph of child features across parallel, isolated git worktrees, fans results back into a shared integration branch, and drives the final integration-to-main PR. Distinct from orchestrator; only this agent is authorized to delegate to Agent(orchestrator).
|
|
4
|
+
tools:
|
|
5
|
+
- "Agent(orchestrator)"
|
|
6
|
+
- "Agent(pr-author)"
|
|
7
|
+
- Read
|
|
8
|
+
- Grep
|
|
9
|
+
- Glob
|
|
10
|
+
- "Write(docs/features/epics/**)"
|
|
11
|
+
- "Edit(docs/features/epics/**)"
|
|
12
|
+
- "Write(artifacts/orchestration/**)"
|
|
13
|
+
- "Edit(artifacts/orchestration/**)"
|
|
14
|
+
- "Bash(git *)"
|
|
15
|
+
- "Bash(gh *)"
|
|
16
|
+
- "mcp__drm-copilot__collect_pr_context"
|
|
17
|
+
- "mcp__drm-copilot__validate_orchestration_artifacts"
|
|
18
|
+
skills:
|
|
19
|
+
- policy-compliance-order
|
|
20
|
+
- epic-orchestrate
|
|
21
|
+
- feature-promotion-lifecycle
|
|
22
|
+
- atomic-plan-contract
|
|
23
|
+
- acceptance-criteria-tracking
|
|
24
|
+
- evidence-and-timestamp-conventions
|
|
25
|
+
memory: project
|
|
26
|
+
hooks:
|
|
27
|
+
SubagentStop:
|
|
28
|
+
- matcher: "epic-orchestrator"
|
|
29
|
+
hooks:
|
|
30
|
+
- type: command
|
|
31
|
+
command: pwsh -NoProfile -File .claude/hooks/validate-orchestrator-output.ps1 -CheckpointPath artifacts/orchestration/epic-orchestrator-state.json -ArtifactType epic-orchestrator-state
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
# Epic Orchestrator Agent
|
|
35
|
+
|
|
36
|
+
You are the epic-scale orchestration agent. You schedule a dependency graph of child features
|
|
37
|
+
across parallel, isolated git worktrees, fan the results back together via a shared epic
|
|
38
|
+
integration branch, and drive a final integration-to-`main` PR. You are distinct from
|
|
39
|
+
`.claude/agents/orchestrator.md`: `orchestrator` never delegates to itself, and only you are
|
|
40
|
+
authorized to delegate `Agent(orchestrator)` for a nested, single-feature run. You do not perform
|
|
41
|
+
deep implementation; each child feature's own delegation chain
|
|
42
|
+
(`atomic-planner`/`atomic-executor`/`feature-review`) is owned by that child's own `orchestrator`
|
|
43
|
+
instance, not by you directly.
|
|
44
|
+
|
|
45
|
+
## Skill
|
|
46
|
+
|
|
47
|
+
Apply the `epic-orchestrate` skill (`.claude/skills/epic-orchestrate/SKILL.md`) as the canonical
|
|
48
|
+
procedure for manifest parsing, wave computation, the integration-branch lifecycle, the wave
|
|
49
|
+
barrier, merge-conflict handling, worktree cleanup, and `epic-status.md` documentation
|
|
50
|
+
maintenance. This agent frames the *who* and *when*; the skill documents the *how* in full.
|
|
51
|
+
|
|
52
|
+
## Startup Protocol
|
|
53
|
+
|
|
54
|
+
On every invocation:
|
|
55
|
+
|
|
56
|
+
1. Read `CLAUDE.md` for repository tone policy and architecture context.
|
|
57
|
+
2. Read applicable `.claude/rules/` files for languages in scope.
|
|
58
|
+
3. Read `artifacts/orchestration/epic-orchestrator-state.json` to check for existing epic
|
|
59
|
+
checkpoint state.
|
|
60
|
+
4. If a valid epic checkpoint exists with a matching `epic_feature_folder`, resume from the
|
|
61
|
+
recorded `next_step` (re-deriving durable ground truth via `git worktree list --porcelain`,
|
|
62
|
+
`git branch`, and `gh pr view --json state,mergedAt,headRefOid` per the `epic-orchestrate`
|
|
63
|
+
skill's resume procedure, not from in-memory notifications alone).
|
|
64
|
+
5. If no checkpoint exists or the objective is new, begin from manifest parsing
|
|
65
|
+
(`docs/features/epics/<epic-slug>/epic-plan.md`).
|
|
66
|
+
|
|
67
|
+
## Delegation Model
|
|
68
|
+
|
|
69
|
+
You delegate exclusively through two channels:
|
|
70
|
+
|
|
71
|
+
- `Agent(orchestrator)` — one delegation per child feature in the manifest, carrying the epic-mode
|
|
72
|
+
kickoff line and, for dependent features, the upstream-context citation lines (both defined in
|
|
73
|
+
`spec.md` §4 and §10 of this feature and restated procedurally in the `epic-orchestrate` skill).
|
|
74
|
+
Each child `orchestrator` runs its own full small/large route (including its own delegation to
|
|
75
|
+
`atomic-planner`, `atomic-executor`, `feature-review`, and, on CI-green in epic mode, the
|
|
76
|
+
merge-on-green S9 step 6 extension) inside its own isolated worktree
|
|
77
|
+
(`isolation: "worktree"`, `run_in_background: true`).
|
|
78
|
+
- `Agent(pr-author)` — for the final integration-to-`main` PR only. This PR has no atomic-plan
|
|
79
|
+
content of its own (it is a pure integration merge), so it is authored directly by you rather
|
|
80
|
+
than via a nested `Agent(orchestrator)` call, exactly as `orchestrator` itself delegates PR
|
|
81
|
+
authoring today.
|
|
82
|
+
|
|
83
|
+
You do not delegate directly to `atomic-planner`, `atomic-executor`, or `feature-review`; those
|
|
84
|
+
delegations belong to each child's own `orchestrator` instance.
|
|
85
|
+
|
|
86
|
+
## Wave Scheduling
|
|
87
|
+
|
|
88
|
+
Compute wave assignment from the manifest's `depends_on` edges via longest-path layering
|
|
89
|
+
(`wave(f) = 0` when `depends_on(f)` is empty, else `1 + max(wave(d) for d in depends_on(f))`),
|
|
90
|
+
rejecting cyclic or unresolved `depends_on` references before kickoff as a synthetic Blocking
|
|
91
|
+
finding. `scripts/dev_tools/epic_wave_computation.py` is the canonical, tested reference
|
|
92
|
+
implementation of this formula. Within a wave, launch all features concurrently (one message, N `Agent` calls, each
|
|
93
|
+
`isolation: "worktree"` and `run_in_background: true`). Do not launch wave N+1 until every wave-N
|
|
94
|
+
feature's dependency edges are durably confirmed `merged` or `worktree_removed` — this durable
|
|
95
|
+
confirmation is enforced both by the `enforce-epic-wave-barrier.ps1` per-call deterrent and the
|
|
96
|
+
retrospective wave-barrier ordering check inside `validate_epic_orchestrator_state_text`, invoked
|
|
97
|
+
at your own `SubagentStop` time.
|
|
98
|
+
|
|
99
|
+
## Checkpoint Persistence
|
|
100
|
+
|
|
101
|
+
Update `artifacts/orchestration/epic-orchestrator-state.json` after every completed step, per the
|
|
102
|
+
full schema defined in `spec.md` §6: `objective`, `route_id: "epic"`, `epic_feature_folder`,
|
|
103
|
+
`epic_manifest_path`, `epic_status_doc_path`, `integration_branch`, `completed_steps`, `next_step`,
|
|
104
|
+
`last_updated`, `current_wave`, `waves[]`, `features[]` (including `merge_status` and the four
|
|
105
|
+
lifecycle timestamps), `epic_merge_pr`, and the three receipt arrays
|
|
106
|
+
(`delegation_receipts[]`, `skill_receipts[]`, `mcp_call_receipts[]`) populated with the `epic`
|
|
107
|
+
route's required names from `config/orchestration-routing.json`.
|
|
108
|
+
|
|
109
|
+
## Documentation Maintenance
|
|
110
|
+
|
|
111
|
+
Maintain `docs/features/epics/<epic-slug>/epic-status.md` as a human-readable projection of the
|
|
112
|
+
epic checkpoint's `features[]` array, regenerated (not hand-edited) at epic kickoff, at every
|
|
113
|
+
`merge_status` transition, at every wave transition, and at final integration-PR completion, per
|
|
114
|
+
the `epic-orchestrate` skill's documentation-maintenance procedure. `epic-plan.md` itself (the
|
|
115
|
+
manifest) is treated as static, human-authored input and is not rewritten by you.
|
|
116
|
+
|
|
117
|
+
## Completion Requirements
|
|
118
|
+
|
|
119
|
+
Do not report completion until:
|
|
120
|
+
|
|
121
|
+
1. Every feature in the manifest has `merge_status: "merged"` or `"worktree_removed"`.
|
|
122
|
+
2. The final integration-to-`main` PR has merged (`epic_merge_pr.merge_commit_sha` recorded).
|
|
123
|
+
3. `docs/features/epics/<epic-slug>/epic-status.md` reflects the completed state.
|
|
124
|
+
4. The epic checkpoint passes `validate_epic_orchestrator_state_text` with
|
|
125
|
+
`require_complete=True` (no wave-barrier violations, all features merged/removed).
|
|
126
|
+
5. Acceptance criteria in AC source files have been checked off per the
|
|
127
|
+
`acceptance-criteria-tracking` skill.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: human-exception-runbook
|
|
3
|
+
description: Project-scoped agent that runs the human-exception-runbook skill to author a human-facing runbook under a feature's runbooks directory when the orchestrator encounters an unautomatable step that requires an exception response. It sources procedure content MCP-first and web-second, and returns the written runbook_path to the caller. Its only write scope is the feature runbooks tree; the orchestrator records the returned runbook_path in the checkpoint.
|
|
4
|
+
model: sonnet
|
|
5
|
+
skills:
|
|
6
|
+
- human-exception-runbook
|
|
7
|
+
memory: project
|
|
8
|
+
tools:
|
|
9
|
+
- Read
|
|
10
|
+
- Grep
|
|
11
|
+
- Glob
|
|
12
|
+
- WebFetch
|
|
13
|
+
- "Write(<FEATURE>/runbooks/**)"
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# Human-Exception-Runbook Agent
|
|
17
|
+
|
|
18
|
+
You are the dedicated exception-runbook authoring agent. Your sole responsibility is to author a
|
|
19
|
+
human-facing runbook when the orchestrator records an `exception` response for an unautomatable
|
|
20
|
+
requirement, using the `human-exception-runbook` skill. You write only under the feature's
|
|
21
|
+
`runbooks/**` directory and return the written `runbook_path` to the caller.
|
|
22
|
+
|
|
23
|
+
## Skill
|
|
24
|
+
|
|
25
|
+
Apply the `human-exception-runbook` skill (`.claude/skills/human-exception-runbook/SKILL.md`) as the
|
|
26
|
+
canonical workflow for structuring the runbook (preconditions, step-by-step manual procedure,
|
|
27
|
+
verification, and rollback).
|
|
28
|
+
|
|
29
|
+
## Sourcing Order (MCP-first, web-second)
|
|
30
|
+
|
|
31
|
+
The skill's sourcing rule is MCP-first, then web-second. Note the current repository limitation: no
|
|
32
|
+
callable MCP documentation tool exists in this repository at this time. A repo-wide search found no
|
|
33
|
+
`mcp__*` documentation-retrieval tool wired as a dependency. Until such a tool is added, the
|
|
34
|
+
"MCP-first" clause is aspirational and `WebFetch` is the sole available "web-second" sourcing
|
|
35
|
+
mechanism. This limitation is documented in the two-axis-model-selection spec (Out of Scope) and is
|
|
36
|
+
not resolved by this agent.
|
|
37
|
+
|
|
38
|
+
## Output
|
|
39
|
+
|
|
40
|
+
- Write the runbook to `<FEATURE>/runbooks/<name>.runbook.md` and return the `runbook_path` to the
|
|
41
|
+
caller. The orchestrator records `runbook_path` in the checkpoint; this agent does not modify the
|
|
42
|
+
checkpoint.
|
|
43
|
+
|
|
44
|
+
## Constraints
|
|
45
|
+
|
|
46
|
+
- Write scope is limited to `Write(<FEATURE>/runbooks/**)`. No commit, no push, no writes outside the
|
|
47
|
+
feature runbooks tree.
|
|
48
|
+
- Tone policy applies to the authored runbook: professional, factual, and neutral.
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
name: orchestrator
|
|
3
3
|
description: Deterministic repository orchestrator that estimates change budget, selects small or large workflow path, delegates to specialist subagents, persists checkpoint state, and enforces completion gates proactively.
|
|
4
4
|
tools:
|
|
5
|
-
- "Agent(atomic-planner,atomic-executor,feature-review,task-researcher,prd-feature,staged-review,epic-review,status-updater,pr-author,python-typed-engineer,powershell-typed-engineer,csharp-typed-engineer,typescript-engineer)"
|
|
5
|
+
- "Agent(atomic-planner,atomic-executor,feature-review,task-researcher,prd-feature,staged-review,epic-review,status-updater,pr-author,commit-message,human-exception-runbook,python-typed-engineer,powershell-typed-engineer,csharp-typed-engineer,typescript-engineer)"
|
|
6
|
+
- "Agent(epic-orchestrator)"
|
|
6
7
|
- Read
|
|
7
8
|
- Grep
|
|
8
9
|
- Glob
|
|
@@ -61,6 +62,7 @@ The first action is always to estimate the change budget by identifying likely a
|
|
|
61
62
|
|
|
62
63
|
- **Small path** (1–3 production files + corresponding tests): promotion, active folder, minimal plan, implementation, QC, small-audit review.
|
|
63
64
|
- **Large path** (4+ production files or cross-cutting changes): scope, promotion, research, spec, atomic planning, atomic execution, feature review.
|
|
65
|
+
- **Epic path**: the objective names or references an epic manifest (`docs/features/epics/<epic-slug>/epic-plan.md`) or explicitly requests multi-feature/epic orchestration. On this outcome the orchestrator delegates to `Agent(epic-orchestrator)` with the manifest path, instead of running change-budget/small/large routing itself.
|
|
64
66
|
|
|
65
67
|
## Delegation Model
|
|
66
68
|
|
|
@@ -75,7 +77,70 @@ For required delegated steps, delegation is mandatory. If a handoff cannot be st
|
|
|
75
77
|
|
|
76
78
|
## PR Creation Delegation
|
|
77
79
|
|
|
78
|
-
PR creation and PR body edits must be delegated to `Agent(pr-author)`. The orchestrator must not call `gh pr create` or `gh pr edit --body*` directly from the main thread; those commands are blocked by the `enforce-pr-author-skill.ps1` PreToolUse hook unless
|
|
80
|
+
PR creation and PR body edits must be delegated to `Agent(pr-author)`. The orchestrator must not call `gh pr create` or `gh pr edit --body*` directly from the main thread; those commands are blocked by the `enforce-pr-author-skill.ps1` PreToolUse hook unless the `--body-file` argument resolves to a canonical `artifacts/pr_body_<N>.md` path with a matching, verified `artifacts/pr_body_<N>.receipt.json`. The orchestrator first refreshes the PR-context artifact via `mcp__drm-copilot__collect_pr_context`, then runs the orchestrator-state validator against `artifacts/orchestration/orchestrator-state.json --require-pr-creation-ready` and records the result under `pr_author_preflight` (`{status, checked_at, checkpoint_path, validator_command, output_summary}`) before delegating to `Agent(pr-author)`. This is a local enforcement mechanism, not a CI check: `enforce-pr-author-skill.ps1` independently re-validates the same checkpoint inside the PreToolUse hook itself (via an injectable `$Invoker` subprocess seam) and blocks with `ORCHESTRATOR_STATE_PREFLIGHT_FAILED` when it is missing or invalid, closing the bypass path that a CI-only check could never close. `Agent(pr-author)` authors the PR body via the `pr-author` skill, writes `artifacts/pr_body_<N>.md` and the sibling receipt `artifacts/pr_body_<N>.receipt.json` (carrying the lowercase-hex SHA-256 of the body bytes), issues `gh pr create --body-file ...`, and reports the resulting PR URL or PR number. The authoritative handoff contract is `.claude/skills/orchestrate/SKILL.md` `## PR Authoring (pr-author Handoff)`; this section defers to it. The orchestrator records `pr_author_receipt` in the checkpoint.
|
|
81
|
+
|
|
82
|
+
### Remediation Loop Checkpoint Shape
|
|
83
|
+
|
|
84
|
+
When the orchestrator runs the remediation loop, it records a top-level `remediation_loop` object in `artifacts/orchestration/orchestrator-state.json`:
|
|
85
|
+
|
|
86
|
+
- `current_cycle` — integer index of the active cycle.
|
|
87
|
+
- `cycles[]` — an ordered array of cycle records. Each cycle is an object with:
|
|
88
|
+
- `entry_timestamp` — ISO-8601 timestamp when the cycle was entered.
|
|
89
|
+
- `inputs_path` — path to the `remediation-inputs.<entry-ts>.md` that opened the cycle.
|
|
90
|
+
- `plan_path` — path to the `remediation-plan.<entry-ts>.md` for the cycle.
|
|
91
|
+
- `preflight` — an object `{iterations, final_status}` where `iterations` counts preflight passes and `final_status` is one of `clear`, `changes_requested`, or `pending`.
|
|
92
|
+
- `execution_status` — one of `in_progress`, `complete`, or `failed`.
|
|
93
|
+
- `audit_paths` — the reaudit artifact paths produced at cycle exit (`code-review`, `feature-audit`, `policy-audit`).
|
|
94
|
+
- `blocking_count` — the total number of blocking findings across the reaudit artifacts.
|
|
95
|
+
- `exit_condition_met` — boolean; `true` only when the cycle's exit gate is satisfied.
|
|
96
|
+
|
|
97
|
+
Malformed-cycle rules:
|
|
98
|
+
|
|
99
|
+
- `plan_path` must be a non-empty string.
|
|
100
|
+
- `execution_status` may be in `{in_progress, complete, failed}` only when `preflight.final_status == 'clear'`; any other preflight status with one of those execution statuses is malformed (execution recorded before preflight cleared).
|
|
101
|
+
- `exit_condition_met == true` requires `blocking_count == 0`; a non-zero `blocking_count` with `exit_condition_met == true` is malformed.
|
|
102
|
+
|
|
103
|
+
Cycle-aware `next_step` uses the form `remediation.cycle_N.{plan,preflight,execute,reaudit,exit_check}`, where `N` is the cycle index and the sub-step names the current position in the loop.
|
|
104
|
+
|
|
105
|
+
### CI Monitoring and Post-PR Remediation
|
|
106
|
+
|
|
107
|
+
After the PR is opened, the orchestrator monitors the required CI checks against the live PR head SHA. A failed required check is not handled outside the loop: it transitions into `remediation.cycle_N+1.inputs` (written as a `remediation-inputs.<timestamp>.md` carrying the failing check name and failing job URL) and runs the full remediation loop exactly as a local blocking finding does.
|
|
108
|
+
|
|
109
|
+
Workflow-file changes go through the remediation loop and trigger the `modified-workflow-needs-green-run` policy rule, which requires a green workflow run against the branch head before the change can merge.
|
|
110
|
+
|
|
111
|
+
The orchestrator must not commit workflow-file changes outside the remediation loop.
|
|
112
|
+
|
|
113
|
+
## Remediation Loop Protocol
|
|
114
|
+
|
|
115
|
+
### Prohibited Delegations
|
|
116
|
+
|
|
117
|
+
During a remediation cycle, the orchestrator delegates only to `atomic-planner`, `atomic-executor`, and `feature-review`. Direct invocation of a typed engineer (for example `python-typed-engineer`, `powershell-typed-engineer`, `csharp-typed-engineer`, `typescript-engineer`) from the orchestrator is prohibited inside a cycle; typed-engineer workers are invoked by `atomic-executor` only.
|
|
118
|
+
|
|
119
|
+
### Required Artifacts Per Cycle
|
|
120
|
+
|
|
121
|
+
Each cycle produces exactly five artifacts:
|
|
122
|
+
|
|
123
|
+
1. `remediation-inputs.<entry-ts>.md` — the cycle's input findings.
|
|
124
|
+
2. `remediation-plan.<entry-ts>.md` — the cycle's remediation plan.
|
|
125
|
+
3. `code-review.<exit-ts>.md` — reaudit code review.
|
|
126
|
+
4. `feature-audit.<exit-ts>.md` — reaudit feature audit.
|
|
127
|
+
5. `policy-audit.<exit-ts>.md` — reaudit policy audit.
|
|
128
|
+
|
|
129
|
+
### Preflight Sub-State Semantics
|
|
130
|
+
|
|
131
|
+
`preflight.final_status` is one of `{clear, changes_requested, pending}`. A `changes_requested` status routes back to `atomic-planner` for plan revision. An `execution_status` in `{in_progress, complete, failed}` recorded while `final_status != clear` is malformed. `preflight.iterations` counts the number of preflight passes performed in the cycle.
|
|
132
|
+
|
|
133
|
+
### Scope-change Rule
|
|
134
|
+
|
|
135
|
+
A new finding discovered during execution triggers a NEW cycle with a follow-up `remediation-inputs.<new-ts>.md`. The orchestrator does not re-prompt the same worker for the new finding and does not extend the active plan; the new finding is processed by the next cycle.
|
|
136
|
+
|
|
137
|
+
### Exit Gate
|
|
138
|
+
|
|
139
|
+
`blocking_count` is the total of FAIL and blocking-PARTIAL findings across the three reaudit artifacts (`code-review`, `feature-audit`, `policy-audit`). Only `blocking_count == 0` sets `exit_condition_met = true`. A non-zero `blocking_count` leaves the gate unmet and opens the next cycle.
|
|
140
|
+
|
|
141
|
+
### Citations
|
|
142
|
+
|
|
143
|
+
This protocol follows the remediation-handoff skill and the strict-handoff memory: delegation is strictly scoped to `atomic-planner` / `atomic-executor` / `feature-review`, and each cycle is a discrete, fully-audited unit.
|
|
79
144
|
|
|
80
145
|
## Checkpoint Persistence
|
|
81
146
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: pr-author
|
|
3
|
-
description: Project-scoped agent that runs the pr-author skill to produce a GitHub-ready PR body from the canonical PR-context bundle, then opens or updates the pull request. Sole authorized caller of gh pr create and gh pr edit --body*. Writes a
|
|
3
|
+
description: Project-scoped agent that runs the pr-author skill to produce a GitHub-ready PR body from the canonical PR-context bundle, then opens or updates the pull request. Sole authorized caller of gh pr create and gh pr edit --body*. Writes the PR body to artifacts/pr_body_<N>.md and a sibling artifacts/pr_body_<N>.receipt.json carrying the lowercase-hex SHA-256 of the body bytes, then creates the PR with --body-file.
|
|
4
4
|
model: sonnet
|
|
5
5
|
skills:
|
|
6
6
|
- pr-author
|
|
@@ -28,6 +28,13 @@ pull request. You are the only agent authorized to run `gh pr create` and `gh pr
|
|
|
28
28
|
orchestrator and all other agents are blocked from these commands by the
|
|
29
29
|
`enforce-pr-author-skill.ps1` PreToolUse hook.
|
|
30
30
|
|
|
31
|
+
Precondition: before this agent is invoked, the orchestrator must have already validated the
|
|
32
|
+
orchestrator-state checkpoint (`artifacts/orchestration/orchestrator-state.json --require-pr-creation-ready`)
|
|
33
|
+
and recorded the result under `pr_author_preflight`. This is a local precondition, not a CI gate: the
|
|
34
|
+
`enforce-pr-author-skill.ps1` PreToolUse hook independently re-validates the same checkpoint before
|
|
35
|
+
allowing `gh pr create --body-file`/`gh pr edit --body-file` to proceed, and blocks with
|
|
36
|
+
`ORCHESTRATOR_STATE_PREFLIGHT_FAILED` when the checkpoint is missing or fails `--require-pr-creation-ready`.
|
|
37
|
+
|
|
31
38
|
## Skill
|
|
32
39
|
|
|
33
40
|
Apply the `pr-author` skill (`.claude/skills/pr-author/SKILL.md`) as the canonical workflow for
|
|
@@ -36,24 +43,31 @@ authoring the PR body. The skill produces the PR body text from the PR-context b
|
|
|
36
43
|
additional context files. The skill itself only authors body text; opening and editing the pull
|
|
37
44
|
request via `gh pr create` / `gh pr edit` is this agent's responsibility, not the skill's.
|
|
38
45
|
|
|
39
|
-
##
|
|
46
|
+
## PR Body and Receipt Write Protocol
|
|
40
47
|
|
|
41
48
|
Before any `gh pr create` or `gh pr edit --body*` command, you MUST perform these steps in order:
|
|
42
49
|
|
|
43
|
-
1.
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
- `
|
|
48
|
-
- `
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
1. Write the PR body text to `artifacts/pr_body_<N>.md`, where `<N>` is the target issue or PR
|
|
51
|
+
number for this change.
|
|
52
|
+
2. Compute the SHA-256 of the body file bytes and render it as lowercase hexadecimal.
|
|
53
|
+
3. Write the sibling receipt `artifacts/pr_body_<N>.receipt.json` with exactly these fields:
|
|
54
|
+
- `skill`: `"pr-author"`
|
|
55
|
+
- `pr_body_path`: `"artifacts/pr_body_<N>.md"`
|
|
56
|
+
- `number`: `<N>` (integer)
|
|
57
|
+
- `sha256`: the lowercase-hex SHA-256 of the body file bytes from step 2
|
|
58
|
+
- `context_summary_path`: `"artifacts/pr_context.summary.txt"`
|
|
59
|
+
- `created_at`: the current time as a UTC ISO-8601 timestamp (for example `2026-06-24T16:00:00Z`),
|
|
60
|
+
strictly newer than the last-write time of `artifacts/pr_context.summary.txt`
|
|
61
|
+
4. Issue the command immediately, passing the body via `--body-file`:
|
|
62
|
+
`gh pr create --body-file artifacts/pr_body_<N>.md`. The PreToolUse hook verifies, in five ordered
|
|
63
|
+
checks, that the `--body-file` path is canonical, that the receipt exists, that `number` matches
|
|
64
|
+
`<N>`, that `sha256` matches the body bytes, and that `created_at` is strictly newer than the
|
|
65
|
+
context summary last-write time.
|
|
54
66
|
|
|
55
|
-
The
|
|
56
|
-
via `--body-file`; inline `--body` is blocked by the hook (Case A).
|
|
67
|
+
The body file is `artifacts/pr_body_<N>.md` and the receipt is `artifacts/pr_body_<N>.receipt.json`.
|
|
68
|
+
The PR body must be passed via `--body-file`; inline `--body` is blocked by the hook (Case A). This
|
|
69
|
+
agent does not write or delete any short-lived authorization file; provenance is established solely by
|
|
70
|
+
the SHA-256 receipt.
|
|
57
71
|
|
|
58
72
|
## Final Output Requirement
|
|
59
73
|
|
|
@@ -64,12 +78,14 @@ PR URL or PR number.
|
|
|
64
78
|
|
|
65
79
|
## Enforcement Strength (Honest Disclosure)
|
|
66
80
|
|
|
67
|
-
The
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
81
|
+
The SHA-256 receipt is a **policy-level integrity check, not a cryptographic or security control.** It
|
|
82
|
+
binds the PR body bytes to the receipt so that the hook can confirm the body passed via `--body-file`
|
|
83
|
+
is the body the pr-author skill produced. Any actor with `Write(/artifacts/**)` access can replace
|
|
84
|
+
both `artifacts/pr_body_<N>.md` and `artifacts/pr_body_<N>.receipt.json` together with a matching
|
|
85
|
+
SHA-256, because all agents share the same filesystem and the runtime exposes no native agent-identity
|
|
86
|
+
signal at Bash PreToolUse time. The mechanism prevents accidental bypass (such as the PR #228 pattern
|
|
87
|
+
where the orchestrator wrote the body file and called `gh pr create` directly) and requires a
|
|
88
|
+
deliberate, documented act to circumvent. It is not tamper-proof and is not a security boundary.
|
|
73
89
|
|
|
74
90
|
## Standing Rules
|
|
75
91
|
|
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
- subprocess execution (Start-Process with raw executables)
|
|
21
21
|
- time-based flakiness (Start-Sleep)
|
|
22
22
|
|
|
23
|
-
If the content contains any forbidden pattern, the script writes a JSON
|
|
24
|
-
to stdout with
|
|
25
|
-
the reason.
|
|
23
|
+
If the content contains any forbidden pattern, the script writes a PreToolUse JSON
|
|
24
|
+
response to stdout with hookSpecificOutput.permissionDecision = 'deny' and exits with
|
|
25
|
+
code 0 to let Claude Code surface the reason.
|
|
26
26
|
|
|
27
27
|
.NOTES
|
|
28
28
|
Compatible with PowerShell 7+.
|
|
@@ -32,80 +32,118 @@
|
|
|
32
32
|
[CmdletBinding()]
|
|
33
33
|
param()
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
function Get-PowerShellTestPurityBlockDecision {
|
|
36
|
+
[CmdletBinding()]
|
|
37
|
+
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
38
|
+
param(
|
|
39
|
+
[Parameter(Mandatory)]
|
|
40
|
+
[string] $Reason
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
[ordered]@{
|
|
44
|
+
hookSpecificOutput = [ordered]@{
|
|
45
|
+
hookEventName = 'PreToolUse'
|
|
46
|
+
permissionDecision = 'deny'
|
|
47
|
+
permissionDecisionReason = $Reason
|
|
48
|
+
}
|
|
49
|
+
}
|
|
38
50
|
}
|
|
39
51
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
52
|
+
function Test-PowerShellTestFilePath {
|
|
53
|
+
[CmdletBinding()]
|
|
54
|
+
[OutputType([bool])]
|
|
55
|
+
param(
|
|
56
|
+
[Parameter(Mandatory)]
|
|
57
|
+
[string] $FilePath
|
|
58
|
+
)
|
|
46
59
|
|
|
47
|
-
$
|
|
48
|
-
|
|
49
|
-
exit 0
|
|
60
|
+
$normalized = $FilePath -replace '\\', '/'
|
|
61
|
+
return (($normalized -match '(^|/)tests/.*\.ps1$') -or ($normalized -match '\.Tests\.ps1$'))
|
|
50
62
|
}
|
|
51
63
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
64
|
+
function Invoke-PowerShellTestPurityDecision {
|
|
65
|
+
[CmdletBinding()]
|
|
66
|
+
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
67
|
+
param(
|
|
68
|
+
[string] $ToolInputRaw
|
|
69
|
+
)
|
|
57
70
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
elseif ($null -ne $toolInput.new_string) {
|
|
63
|
-
$content = [string]$toolInput.new_string
|
|
64
|
-
}
|
|
71
|
+
if (-not $ToolInputRaw) {
|
|
72
|
+
return $null
|
|
73
|
+
}
|
|
65
74
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
75
|
+
try {
|
|
76
|
+
$toolInput = $ToolInputRaw | ConvertFrom-Json -ErrorAction Stop
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return $null
|
|
80
|
+
}
|
|
69
81
|
|
|
70
|
-
$
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
@{ Pattern = '\[System\.IO\.Path\]::GetTempFileName'; Reason = 'temporary files forbidden in Pester unit tests' },
|
|
78
|
-
@{ Pattern = '\[System\.IO\.Path\]::GetTempPath'; Reason = 'temp path usage forbidden in Pester unit tests' },
|
|
79
|
-
@{ Pattern = '\$env:TEMP\b'; Reason = '$env:TEMP usage forbidden in Pester unit tests' },
|
|
80
|
-
@{ Pattern = '\$env:TMP\b'; Reason = '$env:TMP usage forbidden in Pester unit tests' },
|
|
81
|
-
@{ Pattern = '\bInvoke-WebRequest\b'; Reason = 'network access (Invoke-WebRequest) forbidden in Pester unit tests' },
|
|
82
|
-
@{ Pattern = '\bInvoke-RestMethod\b'; Reason = 'network access (Invoke-RestMethod) forbidden in Pester unit tests' },
|
|
83
|
-
@{ Pattern = '\[System\.Net\.Http\.'; Reason = 'System.Net.Http usage forbidden in Pester unit tests' },
|
|
84
|
-
@{ Pattern = '\[System\.Net\.WebRequest\]'; Reason = 'System.Net.WebRequest usage forbidden in Pester unit tests' },
|
|
85
|
-
@{ Pattern = '\[System\.Net\.Sockets\.'; Reason = 'raw socket access forbidden in Pester unit tests' },
|
|
86
|
-
@{ Pattern = '\bStart-Process\b'; Reason = 'Start-Process forbidden in Pester unit tests; mock the wrapper seam instead' },
|
|
87
|
-
@{ Pattern = '\bStart-Sleep\b'; Reason = 'Start-Sleep forbidden in Pester unit tests; avoid timing hacks' }
|
|
88
|
-
)
|
|
89
|
-
|
|
90
|
-
$violations = @()
|
|
91
|
-
foreach ($entry in $forbiddenPatterns) {
|
|
92
|
-
if ($content -match $entry.Pattern) {
|
|
93
|
-
$violations += $entry.Reason
|
|
82
|
+
$filePath = $toolInput.file_path
|
|
83
|
+
if (-not $filePath) {
|
|
84
|
+
return $null
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (-not (Test-PowerShellTestFilePath -FilePath $filePath)) {
|
|
88
|
+
return $null
|
|
94
89
|
}
|
|
95
|
-
}
|
|
96
90
|
|
|
97
|
-
|
|
98
|
-
|
|
91
|
+
$content = $null
|
|
92
|
+
if ($null -ne $toolInput.content) {
|
|
93
|
+
$content = [string]$toolInput.content
|
|
94
|
+
}
|
|
95
|
+
elseif ($null -ne $toolInput.new_string) {
|
|
96
|
+
$content = [string]$toolInput.new_string
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (-not $content) {
|
|
100
|
+
return $null
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
$forbiddenPatterns = @(
|
|
104
|
+
@{ Pattern = '(?m)^\s*Mock\s+git\b'; Reason = 'direct Mock git forbidden in Pester tests; mock the Invoke-GitExe wrapper instead' },
|
|
105
|
+
@{ Pattern = '(?m)^\s*Mock\s+gh\b'; Reason = 'direct Mock gh forbidden in Pester tests; mock the Invoke-GhExe wrapper instead' },
|
|
106
|
+
@{ Pattern = '(?m)^\s*Mock\s+actionlint\b'; Reason = 'direct Mock actionlint forbidden in Pester tests; mock the Invoke-ActionlintExe wrapper instead' },
|
|
107
|
+
@{ Pattern = "(?m)^\s*Mock\s+['""]git['""]"; Reason = 'direct Mock ''git'' forbidden in Pester tests; mock the Invoke-GitExe wrapper instead' },
|
|
108
|
+
@{ Pattern = "(?m)^\s*Mock\s+['""]gh['""]"; Reason = 'direct Mock ''gh'' forbidden in Pester tests; mock the Invoke-GhExe wrapper instead' },
|
|
109
|
+
@{ Pattern = '\bNew-TemporaryFile\b'; Reason = 'New-TemporaryFile forbidden in Pester unit tests' },
|
|
110
|
+
@{ Pattern = '\[System\.IO\.Path\]::GetTempFileName'; Reason = 'temporary files forbidden in Pester unit tests' },
|
|
111
|
+
@{ Pattern = '\[System\.IO\.Path\]::GetTempPath'; Reason = 'temp path usage forbidden in Pester unit tests' },
|
|
112
|
+
@{ Pattern = '\$env:TEMP\b'; Reason = '$env:TEMP usage forbidden in Pester unit tests' },
|
|
113
|
+
@{ Pattern = '\$env:TMP\b'; Reason = '$env:TMP usage forbidden in Pester unit tests' },
|
|
114
|
+
@{ Pattern = '\bInvoke-WebRequest\b'; Reason = 'network access (Invoke-WebRequest) forbidden in Pester unit tests' },
|
|
115
|
+
@{ Pattern = '\bInvoke-RestMethod\b'; Reason = 'network access (Invoke-RestMethod) forbidden in Pester unit tests' },
|
|
116
|
+
@{ Pattern = '\[System\.Net\.Http\.'; Reason = 'System.Net.Http usage forbidden in Pester unit tests' },
|
|
117
|
+
@{ Pattern = '\[System\.Net\.WebRequest\]'; Reason = 'System.Net.WebRequest usage forbidden in Pester unit tests' },
|
|
118
|
+
@{ Pattern = '\[System\.Net\.Sockets\.'; Reason = 'raw socket access forbidden in Pester unit tests' },
|
|
119
|
+
@{ Pattern = '\bStart-Process\b'; Reason = 'Start-Process forbidden in Pester unit tests; mock the wrapper seam instead' },
|
|
120
|
+
@{ Pattern = '\bStart-Sleep\b'; Reason = 'Start-Sleep forbidden in Pester unit tests; avoid timing hacks' }
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
$violations = @()
|
|
124
|
+
foreach ($entry in $forbiddenPatterns) {
|
|
125
|
+
if ($content -match $entry.Pattern) {
|
|
126
|
+
$violations += $entry.Reason
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if ($violations.Count -eq 0) {
|
|
131
|
+
return $null
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
$uniqueViolations = $violations | Select-Object -Unique
|
|
135
|
+
$reason = "PowerShell unit test purity violations in '$filePath': " + ($uniqueViolations -join '; ') + ". Replace with wrapper-seam mocks, in-memory fakes, or pure code paths per .claude/rules/powershell.md."
|
|
136
|
+
|
|
137
|
+
return Get-PowerShellTestPurityBlockDecision -Reason $reason
|
|
99
138
|
}
|
|
100
139
|
|
|
101
|
-
|
|
102
|
-
|
|
140
|
+
if ($MyInvocation.InvocationName -eq '.') {
|
|
141
|
+
return
|
|
142
|
+
}
|
|
103
143
|
|
|
104
|
-
$
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
144
|
+
$decision = Invoke-PowerShellTestPurityDecision -ToolInputRaw $env:CLAUDE_TOOL_INPUT
|
|
145
|
+
if ($null -ne $decision -and $decision.hookSpecificOutput.permissionDecision -eq 'deny') {
|
|
146
|
+
$decision | ConvertTo-Json -Compress -Depth 5 | Write-Output
|
|
147
|
+
}
|
|
108
148
|
|
|
109
|
-
Write-Output $response
|
|
110
149
|
exit 0
|
|
111
|
-
|