Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .claude/agents/atomic-executor.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ tools:
- "Bash(npx prettier *)"
- "Bash(npx eslint *)"
- "Bash(npx tsc *)"
- "Bash(npx vitest *)"
- "Bash(npx jest *)"
- "Bash(pwsh *)"
- "Bash(git *)"
- "mcp__drm-copilot__run_poshqc_format"
Expand Down Expand Up @@ -76,7 +76,7 @@ For each task:
Use the scoped tool patterns for quality gates:

- **Python**: `poetry run black`, `poetry run ruff`, `poetry run pyright`, `poetry run pytest`
- **TypeScript**: `npx prettier`, `npx eslint`, `npx tsc`, `npx vitest`
- **TypeScript**: `npx prettier`, `npx eslint`, `npx tsc`, `npx jest`
- **PowerShell**: MCP server functions (`mcp__drm-copilot__run_poshqc_format`, `mcp__drm-copilot__run_poshqc_analyze`, `mcp__drm-copilot__run_poshqc_test`, `mcp__drm-copilot__run_poshqc_analyze_autofix`)
- **Git**: `git diff`, `git status`, `git log`

Expand Down
20 changes: 14 additions & 6 deletions .claude/hooks/validate-orchestrator-output.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -165,9 +165,15 @@ function Invoke-RoutingContractValidation {
string is unchanged for every existing caller of this hook.

Returns a hashtable with keys:
- HasErrors: $true when the validator reported a non-zero exit or
produced any error text; $false when clean.
- ErrorText: the validator's combined output text (empty on success).
- HasErrors: $true only when the validator reported a non-zero exit
code; $false when it exited 0. The exit code is the sole
discriminator, because the validator prints its success
line to stdout on a clean pass and the default Invoker
captures with 2>&1, so output text is present on success.
- ErrorText: the validator's combined captured output text, carried
through unchanged: the error lines on a failure, and the
success line (Python CLI) or empty (portable fallback)
on a clean pass.
#>
[CmdletBinding()]
[OutputType([hashtable])]
Expand Down Expand Up @@ -219,9 +225,11 @@ function Invoke-RoutingContractValidation {
$outputText = ([string]$result.Output).Trim()
}

# The validator signals a routing-contract failure either through a non-zero
# exit code or through emitted error text; either condition blocks DONE.
$hasErrors = ($exitCode -ne 0) -or (-not [string]::IsNullOrWhiteSpace($outputText))
# The exit code is the complete failure discriminator: the validator prints every
# error to stderr and returns non-zero, and prints its success line to stdout and
# returns 0. Because the default invoker captures with 2>&1, the success line lands
# in $outputText on a clean pass, so output text must not influence this decision.
$hasErrors = ($exitCode -ne 0)
return @{ HasErrors = $hasErrors; ErrorText = $outputText }
}

Expand Down
48 changes: 34 additions & 14 deletions .claude/lib/model-routing/ModelRouting.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@
- Resolve-DelegationModel port of scripts/dev_tools/resolve_delegation_model.py

Both functions are pure and deterministic: they read no file at runtime and
encode only the fixed band ordering, the base complexity-to-model table, the
preferred overlay, and the disabled-mode clamp as module-scope constants.
encode only the fixed band ordering, the floor-signal name set, the base
complexity-to-model table, the preferred overlay, and the disabled-mode clamp
as module-scope constants.
Those literals are pinned to config/orchestration-routing.json (model_policy /
model_budget) by a static config-parity Pester test, and the Python modules
remain the validator's authoritative reference. This module is one half of a
Expand All @@ -29,6 +30,20 @@ $script:BAND_ORDER = @('C1', 'C2', 'C3', 'C4')
# The lowest band, returned when no floor signal is present (LOWEST_BAND).
$script:LOWEST_BAND = 'C1'

# The catalog signal names flagged "floor": true in model_policy.complexity. Only
# a signal named here contributes a floor candidate; a "floor": false name and an
# unknown name each contribute nothing. Hard-coded (never read from disk) because
# this module is pushed down to consumer repositories that do not ship
# config/orchestration-routing.json; a static parity Pester test pins this set to
# the config's "floor": true entries. Mirrors FLOOR_SIGNAL_NAMES in
# scripts/dev_tools/compute_complexity_floor.py.
$script:FLOOR_SIGNAL_NAMES = @(
'classifier_or_model_logic',
'auth_or_token_handling',
'concurrency_or_ordering',
'cross_module_contract_change'
)

# Every present floor signal contributes this uniform candidate band, per the
# model_policy.complexity contract (each [floor] signal contributes C3).
$script:FLOOR_CANDIDATE_BAND = 'C3'
Expand Down Expand Up @@ -78,21 +93,22 @@ function Get-ComplexityFloor {
.DESCRIPTION
Faithful PowerShell port of compute_complexity_floor
(scripts/dev_tools/compute_complexity_floor.py). Returns the deterministic
lower-bound complexity band implied by the set of present floor signals:
each present floor signal contributes a candidate band of C3, the floor is
the maximum triggered candidate band, and the floor never exceeds C3
(C4 is never floor-forced). With no floor signal present the floor is the
lowest band C1. The function is pure: it reads no file and does not mutate
its input, and the result is independent of input ordering.
lower-bound complexity band implied by the recorded signal names: the
function intersects the input with FLOOR_SIGNAL_NAMES, each surviving
[floor] signal contributes a candidate band of C3, the floor is the maximum
triggered candidate band, and the floor never exceeds C3 (C4 is never
floor-forced). With no floor signal present the floor is the lowest band C1.
The function is pure: it reads no file and does not mutate its input, and
the result is independent of input ordering.

.PARAMETER SignalsPresent
The names of the present signals flagged [floor] in the
model_policy.complexity catalog. Every element is treated as a triggered
floor signal contributing the candidate band C3. An empty collection means
no floor signal is present.
The full set of recorded signal names, as written to the checkpoint's
signals_present[] array. The caller does not pre-filter: names outside
FLOOR_SIGNAL_NAMES ("floor": false catalog names and unknown names alike)
contribute nothing. An empty collection means no floor signal is present.

.OUTPUTS
System.String. The floor band: C1 when no floor signal is present,
System.String. The floor band: C1 when no recorded name is a floor signal,
otherwise the maximum triggered candidate band clamped to at most C3.
C4 is never returned.
#>
Expand All @@ -104,9 +120,13 @@ function Get-ComplexityFloor {
[string[]] $SignalsPresent
)

# Keep only the recorded names that are floor signals. A "floor": false catalog
# name and an unknown name both drop out here and contribute no candidate band.
$triggered = @($SignalsPresent | Where-Object { $script:FLOOR_SIGNAL_NAMES -contains $_ })

# With no present floor signal there is no candidate band to raise the floor
# above the lowest band, so the floor is C1 (mirrors the empty-input guard).
if (-not $SignalsPresent -or $SignalsPresent.Count -eq 0) {
if ($triggered.Count -eq 0) {
return $script:LOWEST_BAND
}

Expand Down
48 changes: 30 additions & 18 deletions .claude/lib/orchestrator-state/OrchestratorState.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
checkpoint-presence checks (required keys, step-status validity, blocked_reason
validity) from `scripts/dev_tools/validate_orchestrator_state.py`. The base
constants below are pinned to `REQUIRED_STATE_KEYS`, `STEP_STATUS_KEYS`,
`VALID_STEP_STATUS`, and `VALID_BLOCKED_REASONS` in that validator.
`VALID_STEP_STATUS`, `VALID_BLOCKED_REASONS`, and `STEP_SPECIFIC_EXTRA_STATUS`.

Every public function FAILS CLOSED: a missing checkpoint file, invalid JSON, a
missing required key, an invalid step status, or an unmet readiness condition all
Expand Down Expand Up @@ -83,6 +83,15 @@ $script:VALID_STEP_STATUS = @(
'completed'
)

# Per-key additive step-status vocabulary layered on VALID_STEP_STATUS: each value
# below is valid only on its owning key and is still rejected on every other step
# key. Pinned to STEP_SPECIFIC_EXTRA_STATUS in
# scripts/dev_tools/_orchestrator_state_step_status.py.
$script:STEP_SPECIFIC_EXTRA_STATUS = @{
step6_status = @('blocked_remediation_loop_limit')
step9_status = @('passed', 'failed_remediation_required', 'blocked_ci_loop_limit')
}

# The allowed blocked_reason vocabulary. Pinned to VALID_BLOCKED_REASONS in the
# primary validator.
$script:VALID_BLOCKED_REASONS = @(
Expand Down Expand Up @@ -226,11 +235,11 @@ function Get-OrchestratorStateBasePresenceError {
Return the base checkpoint-presence errors, mirroring the primary validator.
.DESCRIPTION
Private base check. Emits one error string per missing required key, one per
step5_status..step10_status value outside VALID_STEP_STATUS, and one when
blocked_reason is present with a value outside VALID_BLOCKED_REASONS. This
mirrors the base block of scripts/dev_tools/validate_orchestrator_state.py
(required keys, step-status validity, blocked_reason validity) that runs
before any mode-specific gate.
step5_status..step10_status value outside VALID_STEP_STATUS and that key's
STEP_SPECIFIC_EXTRA_STATUS set, and one when blocked_reason is present with a
value outside VALID_BLOCKED_REASONS. This mirrors the base block of
scripts/dev_tools/validate_orchestrator_state.py (required keys, step-status
validity, blocked_reason validity) that runs before any mode-specific gate.
.PARAMETER State
The parsed checkpoint PSCustomObject.
.OUTPUTS
Expand All @@ -254,12 +263,16 @@ function Get-OrchestratorStateBasePresenceError {
}
}

# Every present step status must be a member of the allowed vocabulary; an absent
# step key contributes no error (mirrors the primary validator's None guard).
# Every present step status must be a member of the shared vocabulary or of that
# key's additive extra set; an absent step key contributes no error (mirrors the
# primary validator's None guard).
foreach ($key in $script:STEP_STATUS_KEYS) {
$field = Get-OrchestratorStateField -State $State -Name $key
$extra = @()
if ($script:STEP_SPECIFIC_EXTRA_STATUS.ContainsKey($key)) { $extra = @($script:STEP_SPECIFIC_EXTRA_STATUS[$key]) }
if ($field.Present -and $null -ne $field.Value -and
($script:VALID_STEP_STATUS -notcontains [string]$field.Value)) {
($script:VALID_STEP_STATUS -notcontains [string]$field.Value) -and
($extra -notcontains [string]$field.Value)) {
$errors.Add("Checkpoint has invalid $key`: $($field.Value)")
}
}
Expand All @@ -279,12 +292,11 @@ function Get-OrchestratorStatePrCreationReadinessError {
.SYNOPSIS
Return the PR-creation-readiness errors, parity with the Python reference.
.DESCRIPTION
Private readiness check mirroring
validate_orchestrator_state_pr_creation_readiness in
_orchestrator_state_pr_creation_readiness.py: steps 5-8 must not be
pending/blocked; blocked_reason must be `none` or absent; and the
local_execution_overrides / delegation_bypasses lists must be empty when
present. It does not enforce completion, CI, PR, or routing-contract gates.
Private readiness check mirroring validate_orchestrator_state_pr_creation_readiness in
_orchestrator_state_pr_creation_readiness.py: steps 5-8 must not be pending, blocked, or
blocked_remediation_loop_limit; blocked_reason must be `none` or absent; and the
local_execution_overrides / delegation_bypasses lists must be empty when present. It does
not enforce completion, CI, PR, or routing-contract gates.
.PARAMETER State
The parsed checkpoint PSCustomObject.
.OUTPUTS
Expand All @@ -299,11 +311,11 @@ function Get-OrchestratorStatePrCreationReadinessError {

$errors = [System.Collections.Generic.List[string]]::new()

# Reject an upstream step recorded as pending or blocked; steps 5-8 must have
# finished before the first PR of a branch is created.
# Reject an upstream step recorded as pending, blocked, or blocked_remediation_loop_limit; steps
# 5-8 must have finished before the first PR of a branch is created.
foreach ($key in $script:PR_CREATION_READY_STEP_KEYS) {
$field = Get-OrchestratorStateField -State $State -Name $key
if ($field.Present -and ($field.Value -eq 'pending' -or $field.Value -eq 'blocked')) {
if ($field.Present -and (@('pending', 'blocked', 'blocked_remediation_loop_limit') -contains $field.Value)) {
$errors.Add("Checkpoint PR-creation readiness validation failed: $key is $($field.Value).")
}
}
Expand Down
2 changes: 1 addition & 1 deletion .claude/rules/general-code-change.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Run the full seven-stage toolchain in this exact order and repeat until all stag
2. **Linting** (e.g., Ruff, ESLint, PSScriptAnalyzer, .NET analyzers)
3. **Type checking** (e.g., Pyright, TSC, nullable analysis; skip for PowerShell)
4. **Architecture-boundary tests** (e.g., dependency-cruiser, NetArchTest.Rules)
5. **Unit tests** (e.g., Pytest, Vitest, MSTest, Pester) including property-based tests where applicable per `quality-tiers.md`
5. **Unit tests** (e.g., Pytest, Jest, MSTest, Pester) including property-based tests where applicable per `quality-tiers.md`
6. **Contract / schema compatibility checks** (e.g., oasdiff, schema-snapshot diff)
7. **Integration tests**

Expand Down
4 changes: 2 additions & 2 deletions .claude/rules/general-unit-test.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ The correct response to a file that contains untestable lines is to refactor it
**Permitted `exclude` entries** (non-production paths only):
- Build output directories: `dist/**`, `lib/**`, `lib-amd/**`.
- Test files and test infrastructure: `**/*.test.ts`, `tests/**`, `src/test-support/**`.
- Config files that are not production code: `vitest.config.ts`, `eslint.config.mjs`, `.dependency-cruiser.cjs`, `webpack.config.js`.
- Config files that are not production code: `jest.config.cjs`, `eslint.config.mjs`, `.dependency-cruiser.cjs`, `webpack.config.js`.
- `node_modules/**`.

**Prohibited `exclude` entries:**
Expand Down Expand Up @@ -102,4 +102,4 @@ All test code must be deterministic. The following infrastructure requirements a
- **Controllable clock** — use a `Clock` interface (TypeScript) or `TimeProvider` (.NET) injected into code under test. Do not read wall-clock time directly in production code under test.
- **Seeded RNG** — randomness must be supplied via a seedable interface; on test failure the seed must be printed so the failure is reproducible.
- **Banned APIs in test code** — `setTimeout`, `Thread.Sleep`, `Task.Delay`, real wall-clock waits, and `Date.now()` outside the clock interface are prohibited in tests.
- **Virtual scheduler / fake timers / `FakeTimeProvider`** — async tests must use the framework's fake-timer facility (`vi.useFakeTimers()` for Vitest, `FakeTimeProvider` for .NET) to advance simulated time deterministically.
- **Virtual scheduler / fake timers / `FakeTimeProvider`** — async tests must use the framework's fake-timer facility (`jest.useFakeTimers()` for Jest, `FakeTimeProvider` for .NET) to advance simulated time deterministically.
2 changes: 1 addition & 1 deletion .codex/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ default_permissions = ":danger-full-access"

[mcp_servers.drm-copilot]
command = "npx"
args = ["-y", "@danmoisan/drm-copilot-mcp@1.0.18"]
args = ["-y", "@danmoisan/drm-copilot-mcp@1.0.20"]
required = true
enabled_tools = [
"collect_commit_context",
Expand Down
Loading
Loading