@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.
- package/out/mcp-server.js +10268 -380
- package/package.json +2 -2
- package/resources/claude-customizations/.claude/agents/orchestrator.md +6 -2
- package/resources/claude-customizations/.claude/agents/pr-author.md +78 -0
- package/resources/claude-customizations/.claude/agents/task-researcher.md +10 -6
- package/resources/claude-customizations/.claude/hooks/enforce-checkpoint-monotonic.ps1 +68 -10
- package/resources/claude-customizations/.claude/hooks/enforce-completion-consistency.ps1 +148 -11
- package/resources/claude-customizations/.claude/hooks/enforce-completion-helpers.ps1 +163 -0
- package/resources/claude-customizations/.claude/hooks/enforce-evidence-locations.ps1 +40 -14
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +198 -0
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.ps1 +153 -10
- package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output.ps1 +68 -1
- package/resources/claude-customizations/.claude/hooks/validate-pr-author-output.ps1 +136 -0
- package/resources/claude-customizations/.claude/hooks/validate-task-researcher-output.ps1 +22 -6
- package/resources/claude-customizations/.claude/settings.json +22 -0
- package/resources/claude-customizations/.claude/skills/evidence-and-timestamp-conventions/SKILL.md +0 -1
- package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +76 -2
- package/resources/claude-customizations/.claude/skills/research-issue/SKILL.md +5 -3
- package/resources/claude-customizations/.claude-variants/csharp-legacy/agents/csharp-typed-engineer.md +69 -0
- package/resources/claude-customizations/.claude-variants/csharp-legacy/rules/csharp.md +96 -0
- package/resources/claude-customizations/.claude-variants/csharp-legacy/skills/csharp-qa-gate/SKILL.md +77 -0
- package/resources/claude-customizations/.claude-variants/csharp-legacy/skills/invoke-csharp-engineer/SKILL.md +64 -0
- package/resources/claude-customizations/pack-manifests/core.json +65 -0
- package/resources/claude-customizations/pack-manifests/csharp-legacy.json +11 -0
- package/resources/claude-customizations/pack-manifests/csharp-modern.json +12 -0
- package/resources/claude-customizations/pack-manifests/powershell.json +14 -0
- package/resources/claude-customizations/pack-manifests/python.json +14 -0
- package/resources/claude-customizations/pack-manifests/typescript.json +9 -0
- package/resources/codex-and-agents-customizations/.agents/skills/feature-promotion-lifecycle/SKILL.md +30 -13
- package/resources/codex-and-agents-customizations/.agents/skills/orchestrate/SKILL.md +113 -10
- package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-workflow/SKILL.md +33 -8
- package/resources/codex-and-agents-customizations/.agents/skills/repo-automation-adapter/SKILL.md +12 -7
- package/resources/codex-and-agents-customizations/.codex/config.toml +65 -6
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-checkpoint-monotonic.ps1 +303 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-completion-consistency.ps1 +300 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate.ps1 +148 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-pr-author-skill.ps1 +336 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-promotion-mcp-only.ps1 +83 -17
- package/resources/config/orchestration-routing.json +5 -13
- package/resources/customizations/.github/agents/pr-author.agent.md +25 -0
- package/resources/customizations/.github/agents/task-researcher.agent.md +4 -4
- package/resources/customizations/.github/prompts/fillout-prd-feature.prompt.md +1 -1
- package/resources/customizations/.github/prompts/research-issue.prompt.md +4 -3
- package/resources/codex-and-agents-customizations/.codex/scripts/post-codex-worktree-session.ps1 +0 -5
- package/resources/scripts/dev_tools/__init__.py +0 -0
- package/resources/scripts/dev_tools/_orchestrator_state_human_interaction.py +0 -127
- package/resources/scripts/dev_tools/_orchestrator_state_routing.py +0 -216
- package/resources/scripts/dev_tools/agentic_sync.py +0 -819
- package/resources/scripts/dev_tools/codex_native_converter/__init__.py +0 -11
- package/resources/scripts/dev_tools/codex_native_converter/__main__.py +0 -6
- package/resources/scripts/dev_tools/codex_native_converter/cli.py +0 -11
- package/resources/scripts/dev_tools/new_active_feature_folder.py +0 -79
- package/resources/scripts/dev_tools/new_active_feature_folder_docs.py +0 -268
- package/resources/scripts/dev_tools/new_active_feature_folder_flow.py +0 -366
- package/resources/scripts/dev_tools/new_active_feature_folder_io.py +0 -306
- package/resources/scripts/dev_tools/new_active_feature_folder_markdown.py +0 -252
- package/resources/scripts/dev_tools/new_active_feature_folder_models.py +0 -136
- package/resources/scripts/dev_tools/new_potential_bug_entry.py +0 -465
- package/resources/scripts/dev_tools/potential_to_issue.py +0 -421
- package/resources/scripts/dev_tools/potential_to_issue_content.py +0 -212
- package/resources/scripts/dev_tools/pr_context/__init__.py +0 -0
- package/resources/scripts/dev_tools/pr_context/collector.py +0 -619
- package/resources/scripts/dev_tools/pr_context/feature_docs.py +0 -349
- package/resources/scripts/dev_tools/pr_context/git.py +0 -153
- package/resources/scripts/dev_tools/pr_context/github.py +0 -549
- package/resources/scripts/dev_tools/pr_context/models.py +0 -198
- package/resources/scripts/dev_tools/pr_context/render.py +0 -342
- package/resources/scripts/dev_tools/pr_context/render_feature_excerpts.py +0 -256
- package/resources/scripts/dev_tools/pr_context/render_pr_helpers.py +0 -291
- package/resources/scripts/dev_tools/pr_context/summary_helpers.py +0 -386
- package/resources/scripts/dev_tools/pr_context/verification_evidence.py +0 -171
- package/resources/scripts/dev_tools/prompt_mode_contract.py +0 -152
- package/resources/scripts/dev_tools/push_down_claude_customizations.py +0 -374
- package/resources/scripts/dev_tools/push_down_codex_and_agents_customizations.py +0 -139
- package/resources/scripts/dev_tools/push_down_copilot_customizations.py +0 -504
- package/resources/scripts/dev_tools/push_down_copilot_customizations_filesystem.py +0 -217
- package/resources/scripts/dev_tools/push_down_copilot_customizations_rewrites.py +0 -293
- package/resources/scripts/dev_tools/resolve_file_prompt.py +0 -457
- package/resources/scripts/dev_tools/resolve_hard_lock_prompt.py +0 -444
- package/resources/scripts/dev_tools/validate_orchestration_artifacts.py +0 -246
- package/resources/scripts/dev_tools/validate_orchestration_review_artifacts.py +0 -107
- package/resources/scripts/dev_tools/validate_orchestrator_state.py +0 -428
- package/resources/scripts/dev_tools/validate_policy_audit_artifact.py +0 -448
- package/resources/templates/codex_native_converter.py +0 -35
- package/resources/templates/collect_commit_context.py +0 -212
- package/resources/templates/collect_pr_context.py +0 -74
- package/resources/templates/hello_python.py +0 -11
- package/resources/templates/new_active_feature_folder.py +0 -67
- package/resources/templates/new_potential_bug_entry.py +0 -54
- package/resources/templates/potential_to_issue.py +0 -55
- package/resources/templates/push_down_claude_customizations.py +0 -409
- package/resources/templates/push_down_codex_and_agents_customizations.py +0 -95
- package/resources/templates/push_down_copilot_customizations.py +0 -124
- package/resources/templates/resolve_atomic_plan_prompt.py +0 -75
- package/resources/templates/resolve_hard_lock_prompt.py +0 -65
- 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-
|
|
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-
|
|
40
|
-
"
|
|
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-
|
|
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
|
|
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 `
|
|
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 `
|
|
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 `
|
|
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 `
|
|
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
|
|
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
|
|
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
|
-
- `
|
|
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
|
|
|
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
|