@danmoisan/drm-copilot-mcp 0.0.5 → 1.0.0

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 (96) hide show
  1. package/out/mcp-server.js +10268 -380
  2. package/package.json +2 -2
  3. package/resources/claude-customizations/.claude/agents/orchestrator.md +6 -2
  4. package/resources/claude-customizations/.claude/agents/pr-author.md +78 -0
  5. package/resources/claude-customizations/.claude/agents/task-researcher.md +10 -6
  6. package/resources/claude-customizations/.claude/hooks/enforce-checkpoint-monotonic.ps1 +68 -10
  7. package/resources/claude-customizations/.claude/hooks/enforce-completion-consistency.ps1 +148 -11
  8. package/resources/claude-customizations/.claude/hooks/enforce-completion-helpers.ps1 +163 -0
  9. package/resources/claude-customizations/.claude/hooks/enforce-evidence-locations.ps1 +40 -14
  10. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +198 -0
  11. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.ps1 +153 -10
  12. package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output.ps1 +68 -1
  13. package/resources/claude-customizations/.claude/hooks/validate-pr-author-output.ps1 +136 -0
  14. package/resources/claude-customizations/.claude/hooks/validate-task-researcher-output.ps1 +22 -6
  15. package/resources/claude-customizations/.claude/settings.json +22 -0
  16. package/resources/claude-customizations/.claude/skills/evidence-and-timestamp-conventions/SKILL.md +0 -1
  17. package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +76 -2
  18. package/resources/claude-customizations/.claude/skills/research-issue/SKILL.md +5 -3
  19. package/resources/claude-customizations/.claude-variants/csharp-legacy/agents/csharp-typed-engineer.md +69 -0
  20. package/resources/claude-customizations/.claude-variants/csharp-legacy/rules/csharp.md +96 -0
  21. package/resources/claude-customizations/.claude-variants/csharp-legacy/skills/csharp-qa-gate/SKILL.md +77 -0
  22. package/resources/claude-customizations/.claude-variants/csharp-legacy/skills/invoke-csharp-engineer/SKILL.md +64 -0
  23. package/resources/claude-customizations/pack-manifests/core.json +65 -0
  24. package/resources/claude-customizations/pack-manifests/csharp-legacy.json +11 -0
  25. package/resources/claude-customizations/pack-manifests/csharp-modern.json +12 -0
  26. package/resources/claude-customizations/pack-manifests/powershell.json +14 -0
  27. package/resources/claude-customizations/pack-manifests/python.json +14 -0
  28. package/resources/claude-customizations/pack-manifests/typescript.json +9 -0
  29. package/resources/codex-and-agents-customizations/.agents/skills/feature-promotion-lifecycle/SKILL.md +30 -13
  30. package/resources/codex-and-agents-customizations/.agents/skills/orchestrate/SKILL.md +113 -10
  31. package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-workflow/SKILL.md +33 -8
  32. package/resources/codex-and-agents-customizations/.agents/skills/repo-automation-adapter/SKILL.md +12 -7
  33. package/resources/codex-and-agents-customizations/.codex/config.toml +65 -6
  34. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-checkpoint-monotonic.ps1 +303 -0
  35. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-completion-consistency.ps1 +300 -0
  36. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate.ps1 +148 -0
  37. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-pr-author-skill.ps1 +336 -0
  38. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-promotion-mcp-only.ps1 +83 -17
  39. package/resources/config/orchestration-routing.json +5 -13
  40. package/resources/customizations/.github/agents/pr-author.agent.md +25 -0
  41. package/resources/customizations/.github/agents/task-researcher.agent.md +4 -4
  42. package/resources/customizations/.github/prompts/fillout-prd-feature.prompt.md +1 -1
  43. package/resources/customizations/.github/prompts/research-issue.prompt.md +4 -3
  44. package/resources/codex-and-agents-customizations/.codex/scripts/post-codex-worktree-session.ps1 +0 -5
  45. package/resources/scripts/dev_tools/__init__.py +0 -0
  46. package/resources/scripts/dev_tools/_orchestrator_state_human_interaction.py +0 -127
  47. package/resources/scripts/dev_tools/_orchestrator_state_routing.py +0 -216
  48. package/resources/scripts/dev_tools/agentic_sync.py +0 -819
  49. package/resources/scripts/dev_tools/codex_native_converter/__init__.py +0 -11
  50. package/resources/scripts/dev_tools/codex_native_converter/__main__.py +0 -6
  51. package/resources/scripts/dev_tools/codex_native_converter/cli.py +0 -11
  52. package/resources/scripts/dev_tools/new_active_feature_folder.py +0 -79
  53. package/resources/scripts/dev_tools/new_active_feature_folder_docs.py +0 -268
  54. package/resources/scripts/dev_tools/new_active_feature_folder_flow.py +0 -366
  55. package/resources/scripts/dev_tools/new_active_feature_folder_io.py +0 -306
  56. package/resources/scripts/dev_tools/new_active_feature_folder_markdown.py +0 -252
  57. package/resources/scripts/dev_tools/new_active_feature_folder_models.py +0 -136
  58. package/resources/scripts/dev_tools/new_potential_bug_entry.py +0 -465
  59. package/resources/scripts/dev_tools/potential_to_issue.py +0 -421
  60. package/resources/scripts/dev_tools/potential_to_issue_content.py +0 -212
  61. package/resources/scripts/dev_tools/pr_context/__init__.py +0 -0
  62. package/resources/scripts/dev_tools/pr_context/collector.py +0 -619
  63. package/resources/scripts/dev_tools/pr_context/feature_docs.py +0 -349
  64. package/resources/scripts/dev_tools/pr_context/git.py +0 -153
  65. package/resources/scripts/dev_tools/pr_context/github.py +0 -549
  66. package/resources/scripts/dev_tools/pr_context/models.py +0 -198
  67. package/resources/scripts/dev_tools/pr_context/render.py +0 -342
  68. package/resources/scripts/dev_tools/pr_context/render_feature_excerpts.py +0 -256
  69. package/resources/scripts/dev_tools/pr_context/render_pr_helpers.py +0 -291
  70. package/resources/scripts/dev_tools/pr_context/summary_helpers.py +0 -386
  71. package/resources/scripts/dev_tools/pr_context/verification_evidence.py +0 -171
  72. package/resources/scripts/dev_tools/prompt_mode_contract.py +0 -152
  73. package/resources/scripts/dev_tools/push_down_claude_customizations.py +0 -374
  74. package/resources/scripts/dev_tools/push_down_codex_and_agents_customizations.py +0 -139
  75. package/resources/scripts/dev_tools/push_down_copilot_customizations.py +0 -504
  76. package/resources/scripts/dev_tools/push_down_copilot_customizations_filesystem.py +0 -217
  77. package/resources/scripts/dev_tools/push_down_copilot_customizations_rewrites.py +0 -293
  78. package/resources/scripts/dev_tools/resolve_file_prompt.py +0 -457
  79. package/resources/scripts/dev_tools/resolve_hard_lock_prompt.py +0 -444
  80. package/resources/scripts/dev_tools/validate_orchestration_artifacts.py +0 -246
  81. package/resources/scripts/dev_tools/validate_orchestration_review_artifacts.py +0 -107
  82. package/resources/scripts/dev_tools/validate_orchestrator_state.py +0 -428
  83. package/resources/scripts/dev_tools/validate_policy_audit_artifact.py +0 -448
  84. package/resources/templates/codex_native_converter.py +0 -35
  85. package/resources/templates/collect_commit_context.py +0 -212
  86. package/resources/templates/collect_pr_context.py +0 -74
  87. package/resources/templates/hello_python.py +0 -11
  88. package/resources/templates/new_active_feature_folder.py +0 -67
  89. package/resources/templates/new_potential_bug_entry.py +0 -54
  90. package/resources/templates/potential_to_issue.py +0 -55
  91. package/resources/templates/push_down_claude_customizations.py +0 -409
  92. package/resources/templates/push_down_codex_and_agents_customizations.py +0 -95
  93. package/resources/templates/push_down_copilot_customizations.py +0 -124
  94. package/resources/templates/resolve_atomic_plan_prompt.py +0 -75
  95. package/resources/templates/resolve_hard_lock_prompt.py +0 -65
  96. package/resources/templates/validate_orchestration_artifacts.py +0 -55
@@ -7,14 +7,11 @@
7
7
  "required_agents": [
8
8
  "atomic-planner",
9
9
  "atomic-executor",
10
- "feature-reviewer",
11
- "commit-steward"
10
+ "feature-review"
12
11
  ],
13
12
  "required_skills": [
14
13
  "orchestrate",
15
- "orchestrator-workflow",
16
14
  "feature-promotion-lifecycle",
17
- "repo-automation-adapter",
18
15
  "atomic-plan-contract",
19
16
  "acceptance-criteria-tracking",
20
17
  "pr-context-artifacts",
@@ -24,20 +21,20 @@
24
21
  "new_potential_entry",
25
22
  "potential_to_issue",
26
23
  "new_active_feature_folder",
27
- "collect_commit_context",
28
24
  "collect_pr_context",
29
25
  "validate_orchestration_artifacts"
30
26
  ]
31
27
  },
32
28
  "large": {
33
29
  "description": "Full feature or bug path for work outside small-path budget.",
30
+ "requires_pr_gate": true,
34
31
  "required_agents": [
35
32
  "task-researcher",
36
33
  "prd-feature",
37
34
  "atomic-planner",
38
35
  "atomic-executor",
39
- "feature-reviewer",
40
- "commit-steward"
36
+ "feature-review",
37
+ "pr-author"
41
38
  ],
42
39
  "required_skills": [
43
40
  "orchestrate",
@@ -53,7 +50,6 @@
53
50
  "new_potential_entry",
54
51
  "potential_to_issue",
55
52
  "new_active_feature_folder",
56
- "collect_commit_context",
57
53
  "collect_pr_context",
58
54
  "validate_orchestration_artifacts"
59
55
  ]
@@ -63,19 +59,15 @@
63
59
  "required_agents": [
64
60
  "atomic-planner",
65
61
  "atomic-executor",
66
- "feature-reviewer",
67
- "commit-steward"
62
+ "feature-review"
68
63
  ],
69
64
  "required_skills": [
70
65
  "orchestrate",
71
- "orchestrator-workflow",
72
- "repo-automation-adapter",
73
66
  "atomic-plan-contract",
74
67
  "acceptance-criteria-tracking",
75
68
  "pr-context-artifacts"
76
69
  ],
77
70
  "required_mcp_tools": [
78
- "collect_commit_context",
79
71
  "collect_pr_context",
80
72
  "validate_orchestration_artifacts"
81
73
  ]
@@ -136,3 +136,28 @@ Use these sections, in this order:
136
136
  - No hallucinated issue/PR numbers.
137
137
 
138
138
  That’s it. Produce the PR body.
139
+
140
+ ## Authorization Sentinel Protocol (Documentation-Only in This Ecosystem)
141
+
142
+ When this agent opens or updates a pull request via `gh pr create` or `gh pr edit --body*`, it follows
143
+ the cross-ecosystem authorization sentinel protocol:
144
+
145
+ 1. Run `git rev-parse HEAD` to obtain `head_sha`.
146
+ 2. Write `artifacts/pr_author_authorization.json` with exactly: `issued_by` set to `"pr-author"`,
147
+ `issued_at` as a UTC ISO-8601 timestamp, `head_sha` from step 1, and `ttl_seconds` set to `120`.
148
+ 3. Issue the `gh pr create` / `gh pr edit --body-file ...` command immediately, within the 120-second
149
+ TTL. Pass the PR body via `--body-file`; inline `--body` is not used.
150
+ 4. Delete `artifacts/pr_author_authorization.json` after the command completes (success or failure).
151
+
152
+ After opening or updating the PR, report the resulting PR URL or PR number.
153
+
154
+ Enforcement in the GitHub Copilot ecosystem is **documentation-only**: the Copilot ecosystem has no
155
+ PreToolUse hook surface, so the sentinel protocol cannot be mechanically enforced here. The protocol
156
+ is documented for consistency with the Claude and Codex ecosystems, where a PreToolUse hook verifies
157
+ the sentinel.
158
+
159
+ The authorization sentinel is a **policy guardrail, not a cryptographic or security control.** Any
160
+ actor with write access to `artifacts/` can forge `artifacts/pr_author_authorization.json`, because
161
+ all agents share the same filesystem and the runtime exposes no native agent-identity signal at tool
162
+ pre-use time. The mechanism prevents accidental bypass and requires a deliberate, documented act to
163
+ circumvent. It is not tamper-proof and is not a security boundary.
@@ -8,13 +8,13 @@ tools: [vscode/getProjectSetupInfo, vscode/installExtension, vscode/newWorkspace
8
8
 
9
9
  ## Role Definition
10
10
 
11
- You are a research-only specialist who performs deep, comprehensive analysis for task planning. Your sole responsibility is to write transient research notes in the untracked scratch area `artifacts/research/`. You MUST NOT make changes to any other files, code, or configurations.
11
+ You are a research-only specialist who performs deep, comprehensive analysis for task planning. Your sole responsibility is to write research notes to one of the two tracked research roots: `docs/features/<feature>/research/` for feature-associated research, or `docs/research/` for one-off research not tied to a feature. The orchestrator resolves which root to use and supplies the path; do not infer the feature folder independently. You MUST NOT make changes to any other files, code, or configurations.
12
12
 
13
13
  ## Core Research Principles
14
14
 
15
15
  You MUST operate under these constraints:
16
16
 
17
- - You WILL ONLY do deep research using ALL available tools and create/edit files in `artifacts/research/` (intentionally untracked scratch space) without modifying source code or configurations
17
+ - You WILL ONLY do deep research using ALL available tools and create/edit files in the orchestrator-supplied research root (`docs/features/<feature>/research/` for feature-associated research, or `docs/research/` for one-off research) without modifying source code or configurations
18
18
  - You WILL document ONLY verified findings from actual tool usage, never assumptions, ensuring all research is backed by concrete evidence
19
19
  - You MUST cross-reference findings across multiple authoritative sources to validate accuracy
20
20
  - You WILL understand underlying principles and implementation rationale beyond surface-level patterns
@@ -71,7 +71,7 @@ In the final research document, "remove alternatives" means:
71
71
 
72
72
  ## Operational Constraints
73
73
 
74
- You WILL use read tools throughout the entire workspace and external sources. You MUST create and edit files ONLY in `artifacts/research/`. You MUST NOT modify any source code, configurations, or other project files.
74
+ You WILL use read tools throughout the entire workspace and external sources. You MUST create and edit files ONLY in the orchestrator-supplied research root: `docs/features/<feature>/research/` (feature-associated) or `docs/research/` (one-off). You MUST NOT modify any source code, configurations, or other project files.
75
75
 
76
76
  You WILL provide brief, focused updates without overwhelming details. You WILL present discoveries and guide user toward single solution selection. You WILL keep all conversation focused on research activities and findings. You WILL NEVER repeat information already documented in research files.
77
77
 
@@ -205,7 +205,7 @@ For each research activity, you MUST:
205
205
 
206
206
  You MUST maintain research files as living documents:
207
207
 
208
- 1. Search for existing research files in `artifacts/research/`
208
+ 1. Search for existing research files in the orchestrator-supplied research root (`docs/features/<feature>/research/` for feature-associated research, or `docs/research/` for one-off research)
209
209
  2. Create new research file if none exists for the topic
210
210
  3. Initialize with comprehensive research template structure
211
211
 
@@ -18,7 +18,7 @@ Use this prompt when invoking the prd_feature agent via `/fillout-prd-feature <p
18
18
 
19
19
  1. Load each supplied path in order; note missing or unreadable files explicitly.
20
20
  2. Extract available details (issue number, owner, status, story statements, constraints, inputs/outputs, CLI flags, personas, risks, acceptance criteria, non-goals).
21
- 3. If research exists under `artifacts/research`, the caller must include the specific research file paths in the `/fillout-prd-feature` command; the agent must read each provided research file before writing.
21
+ 3. If research exists under `docs/features/<feature>/research/` (feature-associated) or `docs/research/` (one-off), the caller must include the specific research file paths in the `/fillout-prd-feature` command; the agent must read each provided research file before writing.
22
22
  4. Identify gaps; if the research document is insufficient to populate required sections, delegate additional research to the Task Researcher Agent and pause drafting until that research is complete.
23
23
  5. Before writing, run a technical completeness check: confirm Proposed Fix, Assumptions, Data/Config Impact, Test Strategy, and Acceptance Criteria can be filled with domain-specific, testable details (no placeholders).
24
24
  6. Apply the prd_feature agent’s section guidance when filling content:
@@ -16,7 +16,7 @@ Your output must be actionable for an engineer/agent to implement, and must be g
16
16
 
17
17
  ## Scope and operating rules
18
18
 
19
- Follow the operating rules in your agent definition (research-only, write to `artifacts/research/`, evidence-based). Do not restate those rules in the research notes; apply them.
19
+ Follow the operating rules in your agent definition (research-only; write to the orchestrator-supplied research root under `docs/features/<feature>/research/` for feature-associated research or `docs/research/` for one-off research; evidence-based). Do not restate those rules in the research notes; apply them.
20
20
 
21
21
  ## Inputs / Context (authoritative)
22
22
 
@@ -31,11 +31,12 @@ Also identify (from the feature docs) the **implementation target files** in the
31
31
 
32
32
  ## Research Outputs (required)
33
33
 
34
- Create (or update) a single research file under `artifacts/research/` using the Task Researcher template exactly.
34
+ Create (or update) a single research file under the orchestrator-supplied research root using the Task Researcher template exactly. The two tracked research roots are `docs/features/<feature>/research/` (feature-associated; for example `docs/features/active/<feature>/research/`) and `docs/research/` (one-off, not tied to a feature). Routing rule: use the feature research root when an active feature folder is in scope (the orchestrator supplies the resolved path from `feature-folder` in `orchestrator-state.json`); otherwise use `docs/research/`.
35
35
 
36
36
  Filename convention:
37
37
 
38
- - `artifacts/research/YYYYMMDD-<short-feature-name>-implementation-research.md`
38
+ - `docs/features/<feature>/research/YYYYMMDD-<short-feature-name>-implementation-research.md` (feature-associated), or
39
+ - `docs/research/YYYYMMDD-<short-feature-name>-implementation-research.md` (one-off)
39
40
 
40
41
  In the research file, keep any discussion of non-selected approaches *brief and non-exhaustive*. After selecting a recommendation, remove detailed notes for other approaches and keep only a short “Rejected alternatives” summary (what was rejected and why).
41
42
 
@@ -1,5 +0,0 @@
1
- [CmdletBinding()]
2
- param()
3
-
4
- Set-StrictMode -Version Latest
5
- $ErrorActionPreference = 'Stop'
File without changes
@@ -1,127 +0,0 @@
1
- """Human-interaction invariants for orchestrator-state checkpoints.
2
-
3
- Purpose:
4
- Hold the optional ``human_interaction`` block constants and the
5
- ``_validate_human_interaction`` helper so the primary validator module
6
- (`scripts.dev_tools.validate_orchestrator_state`) can stay within the
7
- repository's 500-line file limit while preserving the existing validator
8
- contract.
9
-
10
- Usage:
11
- Import ``HUMAN_INTERACTION_KEY`` and ``_validate_human_interaction`` from
12
- this module. The primary validator re-exports both symbols so existing
13
- callers and tests continue to resolve them from
14
- ``scripts.dev_tools.validate_orchestrator_state`` unchanged.
15
-
16
- Invariants / Constraints:
17
- - The three permitted ``response`` values are ``scope_change``,
18
- ``exception``, and ``halt``.
19
- - An ``exception`` response requires a non-empty ``runbook_path`` string.
20
- - The validator never imports ``schemas/orchestrator-state.schema.json``;
21
- the invariants are expressed directly here per
22
- `.claude/rules/orchestrator-state.md`.
23
-
24
- Side Effects:
25
- None.
26
- """
27
-
28
- from __future__ import annotations
29
-
30
- from typing import Any, cast
31
-
32
- # Declare the module's intended exported surface. Listing ``_validate_human_
33
- # interaction`` here marks it as a deliberate re-export consumed by
34
- # ``validate_orchestrator_state``, so static analysis does not flag the helper
35
- # as unused locally or as private-usage when imported across the module boundary.
36
- __all__ = [
37
- "HUMAN_INTERACTION_KEY",
38
- "HUMAN_INTERACTION_REQUIREMENTS_KEY",
39
- "HUMAN_INTERACTION_RESPONSE_ENUM",
40
- "HUMAN_INTERACTION_EXCEPTION_RESPONSE",
41
- "_validate_human_interaction",
42
- ]
43
-
44
- HUMAN_INTERACTION_KEY = "human_interaction"
45
- HUMAN_INTERACTION_REQUIREMENTS_KEY = "requirements"
46
- # The three permitted responses for an unautomatable requirement under the
47
- # autonomous-execution mandate (see `.claude/skills/orchestrate/SKILL.md`).
48
- HUMAN_INTERACTION_RESPONSE_ENUM = {"scope_change", "exception", "halt"}
49
- HUMAN_INTERACTION_EXCEPTION_RESPONSE = "exception"
50
-
51
-
52
- def _validate_human_interaction(human_interaction: object) -> list[str]:
53
- """Validate the optional ``human_interaction`` block invariants.
54
-
55
- Purpose:
56
- Apply the autonomous-execution mandate invariants to the checkpoint's
57
- optional top-level ``human_interaction`` object, mirroring the schema
58
- invariants documented in `.claude/rules/orchestrator-state.md`. The
59
- validator never imports `schemas/orchestrator-state.schema.json`; the
60
- invariants are expressed directly here in the existing helper-plus-
61
- error-list style.
62
-
63
- Args:
64
- human_interaction (object): The raw value of the checkpoint's top-level
65
- ``human_interaction`` key. Callers invoke this helper only when the
66
- key is present, so a non-object value is itself a malformed block.
67
-
68
- Returns:
69
- list[str]: One error string per violated invariant; an empty list when
70
- the block is well-formed. The three invariants are: ``requirements`` is
71
- present and is a list; each requirement is an object whose ``response``
72
- is within the permitted enum; a requirement whose ``response`` is
73
- ``exception`` carries a non-empty ``runbook_path`` string.
74
-
75
- Raises:
76
- None.
77
-
78
- Side Effects:
79
- None.
80
- """
81
-
82
- errors: list[str] = []
83
-
84
- # A non-object human_interaction cannot carry a requirements list; the key
85
- # was present, so this is a malformed block rather than an absent one.
86
- if not isinstance(human_interaction, dict):
87
- errors.append("Checkpoint human_interaction must be an object when present.")
88
- return errors
89
- human_interaction_map = cast("dict[str, Any]", human_interaction)
90
-
91
- # Invariant 1: requirements must be present and a list.
92
- requirements = human_interaction_map.get(HUMAN_INTERACTION_REQUIREMENTS_KEY)
93
- if not isinstance(requirements, list):
94
- errors.append("Checkpoint human_interaction.requirements must be a list.")
95
- return errors
96
- requirement_list = cast("list[object]", requirements)
97
-
98
- # Validate each requirement independently so callers receive a complete
99
- # error list instead of stopping at the first malformed requirement.
100
- for index, requirement in enumerate(requirement_list):
101
- if not isinstance(requirement, dict):
102
- errors.append(
103
- f"Checkpoint human_interaction.requirements #{index} must be an "
104
- "object."
105
- )
106
- continue
107
- requirement_map = cast("dict[str, Any]", requirement)
108
-
109
- # Invariant 2: response must be within the permitted enum.
110
- response = requirement_map.get("response")
111
- if response not in HUMAN_INTERACTION_RESPONSE_ENUM:
112
- errors.append(
113
- f"Checkpoint human_interaction.requirements #{index} response "
114
- f"must be one of scope_change, exception, halt; got: {response}"
115
- )
116
- continue
117
-
118
- # Invariant 3: an exception response requires a non-empty runbook_path.
119
- if response == HUMAN_INTERACTION_EXCEPTION_RESPONSE:
120
- runbook_path = requirement_map.get("runbook_path")
121
- if not isinstance(runbook_path, str) or not runbook_path.strip():
122
- errors.append(
123
- f"Checkpoint human_interaction.requirements #{index} "
124
- "response is exception but runbook_path is missing or empty."
125
- )
126
-
127
- return errors
@@ -1,216 +0,0 @@
1
- """Routing and mandatory handoff invariants for orchestrator checkpoints."""
2
-
3
- from __future__ import annotations
4
-
5
- import json
6
- from pathlib import Path
7
- from typing import Any, cast
8
-
9
- ROUTING_MATRIX_PATH = (
10
- Path(__file__).resolve().parents[2] / "config" / "orchestration-routing.json"
11
- )
12
-
13
-
14
- def load_routing_matrix(path: Path = ROUTING_MATRIX_PATH) -> dict[str, Any]:
15
- """Load the repository routing matrix from disk."""
16
-
17
- loaded: object = json.loads(path.read_text(encoding="utf-8"))
18
- return cast("dict[str, Any]", loaded)
19
-
20
-
21
- def _string_list(value: object) -> list[str] | None:
22
- """Return a list of strings only when the value has that exact shape."""
23
-
24
- if not isinstance(value, list):
25
- return None
26
- values = cast("list[object]", value)
27
- if not all(isinstance(item, str) and item.strip() for item in values):
28
- return None
29
- return cast("list[str]", values)
30
-
31
-
32
- def _route_list(route: dict[str, Any], key: str) -> list[str]:
33
- """Read a required string-list field from one route entry."""
34
-
35
- value = _string_list(route.get(key))
36
- return [] if value is None else value
37
-
38
-
39
- def _state_list(
40
- state: dict[str, Any], key: str, route_id: str, expected: list[str]
41
- ) -> list[str] | None:
42
- """Validate a state list against the routing matrix list."""
43
-
44
- value = _string_list(state.get(key))
45
- if value is None:
46
- return None
47
- if value != expected:
48
- return None
49
- return value
50
-
51
-
52
- def _list_receipts(receipts: object) -> list[dict[str, Any]]:
53
- """Return legacy list delegation receipts as typed dictionaries."""
54
-
55
- if not isinstance(receipts, list):
56
- return []
57
- receipt_list = cast("list[object]", receipts)
58
- return [
59
- cast("dict[str, Any]", item) for item in receipt_list if isinstance(item, dict)
60
- ]
61
-
62
-
63
- def _receipt_agents(state: dict[str, Any]) -> set[str]:
64
- """Collect agent names from delegation receipts."""
65
-
66
- agents: set[str] = set()
67
- for receipt in _list_receipts(state.get("delegation_receipts")):
68
- agent_name = receipt.get("agent_name")
69
- if isinstance(agent_name, str) and agent_name.strip():
70
- agents.add(agent_name)
71
- return agents
72
-
73
-
74
- def _receipt_skills(state: dict[str, Any]) -> set[str]:
75
- """Collect acknowledged skill names from skill receipts."""
76
-
77
- skills: set[str] = set()
78
- receipts = state.get("skill_receipts")
79
- if not isinstance(receipts, list):
80
- return skills
81
- for receipt in cast("list[object]", receipts):
82
- if not isinstance(receipt, dict):
83
- continue
84
- receipt_map = cast("dict[str, object]", receipt)
85
- skill = receipt_map.get("skill")
86
- required = receipt_map.get("required")
87
- evidence = receipt_map.get("evidence")
88
- if (
89
- isinstance(skill, str)
90
- and skill.strip()
91
- and required is True
92
- and isinstance(evidence, str)
93
- and evidence.strip()
94
- ):
95
- skills.add(skill)
96
- return skills
97
-
98
-
99
- def _mcp_tools(state: dict[str, Any]) -> set[str]:
100
- """Collect successful MCP tool receipts from checkpoint state."""
101
-
102
- tools: set[str] = set()
103
- receipts = state.get("mcp_call_receipts")
104
- if not isinstance(receipts, list):
105
- return tools
106
- for receipt in cast("list[object]", receipts):
107
- if not isinstance(receipt, dict):
108
- continue
109
- receipt_map = cast("dict[str, object]", receipt)
110
- tool = receipt_map.get("tool")
111
- ok = receipt_map.get("ok")
112
- evidence = receipt_map.get("evidence")
113
- if (
114
- isinstance(tool, str)
115
- and tool.strip()
116
- and ok is True
117
- and isinstance(evidence, str)
118
- and evidence.strip()
119
- ):
120
- tools.add(tool)
121
- return tools
122
-
123
-
124
- def _validate_empty_list_field(state: dict[str, Any], key: str) -> list[str]:
125
- """Require a checkpoint field to exist as an empty list."""
126
-
127
- value = state.get(key)
128
- if not isinstance(value, list):
129
- return [f"Checkpoint {key} must be an empty list at completion."]
130
- if value:
131
- return [f"Checkpoint {key} must be empty at completion."]
132
- return []
133
-
134
-
135
- def _validate_lifecycle_operations(state: dict[str, Any]) -> list[str]:
136
- """Reject lifecycle-operation records that did not use MCP."""
137
-
138
- operations = state.get("lifecycle_operations")
139
- if operations is None:
140
- return []
141
- if not isinstance(operations, list):
142
- return ["Checkpoint lifecycle_operations must be a list when present."]
143
- errors: list[str] = []
144
- for index, operation in enumerate(cast("list[object]", operations)):
145
- if not isinstance(operation, dict):
146
- errors.append(
147
- f"Checkpoint lifecycle_operations #{index} must be an object."
148
- )
149
- continue
150
- operation_map = cast("dict[str, object]", operation)
151
- if operation_map.get("surface") != "mcp":
152
- errors.append(
153
- f"Checkpoint lifecycle_operations #{index} did not use MCP surface."
154
- )
155
- return errors
156
-
157
-
158
- def validate_routing_contract(
159
- state: dict[str, Any], *, routing_matrix: dict[str, Any] | None = None
160
- ) -> list[str]:
161
- """Validate mandatory route, handoff, skill, and MCP completion evidence."""
162
-
163
- matrix = routing_matrix if routing_matrix is not None else load_routing_matrix()
164
- raw_routes = matrix.get("routes")
165
- if not isinstance(raw_routes, dict):
166
- return ["Routing matrix missing routes object."]
167
- routes = cast("dict[str, object]", raw_routes)
168
-
169
- route_id = state.get("route_id", state.get("path_selected"))
170
- if not isinstance(route_id, str) or not route_id.strip():
171
- return ["Checkpoint route_id or path_selected must select a route."]
172
- raw_route = routes.get(route_id)
173
- if not isinstance(raw_route, dict):
174
- return [f"Checkpoint selected route has no routing-matrix entry: {route_id}."]
175
- route_map = cast("dict[str, Any]", raw_route)
176
-
177
- errors: list[str] = []
178
- required_agents = _route_list(route_map, "required_agents")
179
- required_skills = _route_list(route_map, "required_skills")
180
- required_mcp_tools = _route_list(route_map, "required_mcp_tools")
181
-
182
- if _state_list(state, "required_agents", route_id, required_agents) is None:
183
- errors.append(
184
- "Checkpoint required_agents must match routing matrix for route "
185
- f"{route_id}."
186
- )
187
- if _state_list(state, "required_skills", route_id, required_skills) is None:
188
- errors.append(
189
- "Checkpoint required_skills must match routing matrix for route "
190
- f"{route_id}."
191
- )
192
- if _state_list(state, "required_mcp_tools", route_id, required_mcp_tools) is None:
193
- errors.append(
194
- "Checkpoint required_mcp_tools must match routing matrix for route "
195
- f"{route_id}."
196
- )
197
-
198
- actual_agents = _receipt_agents(state)
199
- for agent in required_agents:
200
- if agent not in actual_agents:
201
- errors.append(f"Checkpoint missing required agent receipt: {agent}.")
202
-
203
- actual_skills = _receipt_skills(state)
204
- for skill in required_skills:
205
- if skill not in actual_skills:
206
- errors.append(f"Checkpoint missing required skill receipt: {skill}.")
207
-
208
- actual_tools = _mcp_tools(state)
209
- for tool in required_mcp_tools:
210
- if tool not in actual_tools:
211
- errors.append(f"Checkpoint missing successful MCP receipt: {tool}.")
212
-
213
- errors.extend(_validate_empty_list_field(state, "local_execution_overrides"))
214
- errors.extend(_validate_empty_list_field(state, "delegation_bypasses"))
215
- errors.extend(_validate_lifecycle_operations(state))
216
- return errors