@danmoisan/drm-copilot-mcp 1.0.18 → 1.0.20
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 +22 -2
- package/package.json +1 -1
- package/resources/claude-customizations/.claude/agents/atomic-executor.md +2 -2
- package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output.ps1 +14 -6
- package/resources/claude-customizations/.claude/lib/model-routing/ModelRouting.psm1 +34 -14
- package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorState.psm1 +30 -18
- package/resources/claude-customizations/.claude/rules/general-code-change.md +1 -1
- package/resources/claude-customizations/.claude/rules/general-unit-test.md +2 -2
- package/resources/claude-customizations/.claude/rules/typescript.md +5 -5
- package/resources/codex-and-agents-customizations/.agents/skills/general-code-change/SKILL.md +1 -1
- package/resources/codex-and-agents-customizations/.agents/skills/general-unit-test/SKILL.md +2 -2
- package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/check-powershell-test-purity.ps1 +9 -65
- package/resources/codex-and-agents-customizations/.codex/hooks/check-python-test-purity.ps1 +9 -65
- package/resources/codex-and-agents-customizations/.codex/hooks/codex-pretooluse-file-mapping.ps1 +474 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-checkpoint-monotonic.ps1 +20 -101
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-completion-consistency.ps1 +16 -3
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-child-worktree-binding.ps1 +14 -5
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-planning-only.ps1 +23 -5
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-evidence-locations.ps1 +30 -52
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate.ps1 +28 -27
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-powershell-batch-budget.ps1 +19 -49
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-python-batch-budget.ps1 +19 -49
- package/resources/codex-and-agents-customizations/pack-manifests/core.json +1 -0
- package/resources/powershell/PoshQC/PoshQC.Testing.psm1 +21 -1
- package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +19 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-pr-author-skill.ps1 +0 -500
package/out/mcp-server.js
CHANGED
|
@@ -25867,6 +25867,26 @@ var STEP_STATUS_KEYS = [
|
|
|
25867
25867
|
"step9_status",
|
|
25868
25868
|
"step10_status"
|
|
25869
25869
|
];
|
|
25870
|
+
var STEP_SPECIFIC_EXTRA_STATUS = /* @__PURE__ */ new Map([
|
|
25871
|
+
["step6_status", /* @__PURE__ */ new Set(["blocked_remediation_loop_limit"])],
|
|
25872
|
+
[
|
|
25873
|
+
"step9_status",
|
|
25874
|
+
/* @__PURE__ */ new Set(["passed", "failed_remediation_required", "blocked_ci_loop_limit"])
|
|
25875
|
+
]
|
|
25876
|
+
]);
|
|
25877
|
+
var COMPLETION_BLOCKING_STEP_STATUS = /* @__PURE__ */ new Set([
|
|
25878
|
+
"pending",
|
|
25879
|
+
"blocked",
|
|
25880
|
+
"failed_remediation_required",
|
|
25881
|
+
"blocked_ci_loop_limit",
|
|
25882
|
+
"blocked_remediation_loop_limit"
|
|
25883
|
+
]);
|
|
25884
|
+
function isValidStepStatus(key, value) {
|
|
25885
|
+
if (VALID_STEP_STATUS.has(value)) {
|
|
25886
|
+
return true;
|
|
25887
|
+
}
|
|
25888
|
+
return STEP_SPECIFIC_EXTRA_STATUS.get(key)?.has(value) === true;
|
|
25889
|
+
}
|
|
25870
25890
|
function isObject13(value) {
|
|
25871
25891
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
25872
25892
|
}
|
|
@@ -25951,7 +25971,7 @@ function validateOrchestratorStateText(text, options = {}) {
|
|
|
25951
25971
|
}
|
|
25952
25972
|
for (const key of STEP_STATUS_KEYS) {
|
|
25953
25973
|
const value = stateMap[key];
|
|
25954
|
-
if (value !== void 0 && value !== null && !(typeof value === "string" &&
|
|
25974
|
+
if (value !== void 0 && value !== null && !(typeof value === "string" && isValidStepStatus(key, value))) {
|
|
25955
25975
|
errors.push(`Checkpoint has invalid ${key}: ${String(value)}`);
|
|
25956
25976
|
}
|
|
25957
25977
|
}
|
|
@@ -25982,7 +26002,7 @@ function validateOrchestratorStateText(text, options = {}) {
|
|
|
25982
26002
|
if (options.requireComplete === true) {
|
|
25983
26003
|
for (const key of STEP_STATUS_KEYS) {
|
|
25984
26004
|
const value = stateMap[key];
|
|
25985
|
-
if (value
|
|
26005
|
+
if (COMPLETION_BLOCKING_STEP_STATUS.has(value)) {
|
|
25986
26006
|
errors.push(
|
|
25987
26007
|
`Checkpoint completion validation failed: ${key} is ${String(value)}.`
|
|
25988
26008
|
);
|
package/package.json
CHANGED
|
@@ -15,7 +15,7 @@ tools:
|
|
|
15
15
|
- "Bash(npx prettier *)"
|
|
16
16
|
- "Bash(npx eslint *)"
|
|
17
17
|
- "Bash(npx tsc *)"
|
|
18
|
-
- "Bash(npx
|
|
18
|
+
- "Bash(npx jest *)"
|
|
19
19
|
- "Bash(pwsh *)"
|
|
20
20
|
- "Bash(git *)"
|
|
21
21
|
- "mcp__drm-copilot__run_poshqc_format"
|
|
@@ -76,7 +76,7 @@ For each task:
|
|
|
76
76
|
Use the scoped tool patterns for quality gates:
|
|
77
77
|
|
|
78
78
|
- **Python**: `poetry run black`, `poetry run ruff`, `poetry run pyright`, `poetry run pytest`
|
|
79
|
-
- **TypeScript**: `npx prettier`, `npx eslint`, `npx tsc`, `npx
|
|
79
|
+
- **TypeScript**: `npx prettier`, `npx eslint`, `npx tsc`, `npx jest`
|
|
80
80
|
- **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`)
|
|
81
81
|
- **Git**: `git diff`, `git status`, `git log`
|
|
82
82
|
|
|
@@ -165,9 +165,15 @@ function Invoke-RoutingContractValidation {
|
|
|
165
165
|
string is unchanged for every existing caller of this hook.
|
|
166
166
|
|
|
167
167
|
Returns a hashtable with keys:
|
|
168
|
-
- HasErrors: $true when the validator reported a non-zero exit
|
|
169
|
-
|
|
170
|
-
|
|
168
|
+
- HasErrors: $true only when the validator reported a non-zero exit
|
|
169
|
+
code; $false when it exited 0. The exit code is the sole
|
|
170
|
+
discriminator, because the validator prints its success
|
|
171
|
+
line to stdout on a clean pass and the default Invoker
|
|
172
|
+
captures with 2>&1, so output text is present on success.
|
|
173
|
+
- ErrorText: the validator's combined captured output text, carried
|
|
174
|
+
through unchanged: the error lines on a failure, and the
|
|
175
|
+
success line (Python CLI) or empty (portable fallback)
|
|
176
|
+
on a clean pass.
|
|
171
177
|
#>
|
|
172
178
|
[CmdletBinding()]
|
|
173
179
|
[OutputType([hashtable])]
|
|
@@ -219,9 +225,11 @@ function Invoke-RoutingContractValidation {
|
|
|
219
225
|
$outputText = ([string]$result.Output).Trim()
|
|
220
226
|
}
|
|
221
227
|
|
|
222
|
-
# The
|
|
223
|
-
#
|
|
224
|
-
|
|
228
|
+
# The exit code is the complete failure discriminator: the validator prints every
|
|
229
|
+
# error to stderr and returns non-zero, and prints its success line to stdout and
|
|
230
|
+
# returns 0. Because the default invoker captures with 2>&1, the success line lands
|
|
231
|
+
# in $outputText on a clean pass, so output text must not influence this decision.
|
|
232
|
+
$hasErrors = ($exitCode -ne 0)
|
|
225
233
|
return @{ HasErrors = $hasErrors; ErrorText = $outputText }
|
|
226
234
|
}
|
|
227
235
|
|
|
@@ -11,8 +11,9 @@
|
|
|
11
11
|
- Resolve-DelegationModel port of scripts/dev_tools/resolve_delegation_model.py
|
|
12
12
|
|
|
13
13
|
Both functions are pure and deterministic: they read no file at runtime and
|
|
14
|
-
encode only the fixed band ordering, the
|
|
15
|
-
preferred overlay, and the disabled-mode clamp
|
|
14
|
+
encode only the fixed band ordering, the floor-signal name set, the base
|
|
15
|
+
complexity-to-model table, the preferred overlay, and the disabled-mode clamp
|
|
16
|
+
as module-scope constants.
|
|
16
17
|
Those literals are pinned to config/orchestration-routing.json (model_policy /
|
|
17
18
|
model_budget) by a static config-parity Pester test, and the Python modules
|
|
18
19
|
remain the validator's authoritative reference. This module is one half of a
|
|
@@ -29,6 +30,20 @@ $script:BAND_ORDER = @('C1', 'C2', 'C3', 'C4')
|
|
|
29
30
|
# The lowest band, returned when no floor signal is present (LOWEST_BAND).
|
|
30
31
|
$script:LOWEST_BAND = 'C1'
|
|
31
32
|
|
|
33
|
+
# The catalog signal names flagged "floor": true in model_policy.complexity. Only
|
|
34
|
+
# a signal named here contributes a floor candidate; a "floor": false name and an
|
|
35
|
+
# unknown name each contribute nothing. Hard-coded (never read from disk) because
|
|
36
|
+
# this module is pushed down to consumer repositories that do not ship
|
|
37
|
+
# config/orchestration-routing.json; a static parity Pester test pins this set to
|
|
38
|
+
# the config's "floor": true entries. Mirrors FLOOR_SIGNAL_NAMES in
|
|
39
|
+
# scripts/dev_tools/compute_complexity_floor.py.
|
|
40
|
+
$script:FLOOR_SIGNAL_NAMES = @(
|
|
41
|
+
'classifier_or_model_logic',
|
|
42
|
+
'auth_or_token_handling',
|
|
43
|
+
'concurrency_or_ordering',
|
|
44
|
+
'cross_module_contract_change'
|
|
45
|
+
)
|
|
46
|
+
|
|
32
47
|
# Every present floor signal contributes this uniform candidate band, per the
|
|
33
48
|
# model_policy.complexity contract (each [floor] signal contributes C3).
|
|
34
49
|
$script:FLOOR_CANDIDATE_BAND = 'C3'
|
|
@@ -78,21 +93,22 @@ function Get-ComplexityFloor {
|
|
|
78
93
|
.DESCRIPTION
|
|
79
94
|
Faithful PowerShell port of compute_complexity_floor
|
|
80
95
|
(scripts/dev_tools/compute_complexity_floor.py). Returns the deterministic
|
|
81
|
-
lower-bound complexity band implied by the
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
96
|
+
lower-bound complexity band implied by the recorded signal names: the
|
|
97
|
+
function intersects the input with FLOOR_SIGNAL_NAMES, each surviving
|
|
98
|
+
[floor] signal contributes a candidate band of C3, the floor is the maximum
|
|
99
|
+
triggered candidate band, and the floor never exceeds C3 (C4 is never
|
|
100
|
+
floor-forced). With no floor signal present the floor is the lowest band C1.
|
|
101
|
+
The function is pure: it reads no file and does not mutate its input, and
|
|
102
|
+
the result is independent of input ordering.
|
|
87
103
|
|
|
88
104
|
.PARAMETER SignalsPresent
|
|
89
|
-
The
|
|
90
|
-
|
|
91
|
-
floor
|
|
92
|
-
no floor signal is present.
|
|
105
|
+
The full set of recorded signal names, as written to the checkpoint's
|
|
106
|
+
signals_present[] array. The caller does not pre-filter: names outside
|
|
107
|
+
FLOOR_SIGNAL_NAMES ("floor": false catalog names and unknown names alike)
|
|
108
|
+
contribute nothing. An empty collection means no floor signal is present.
|
|
93
109
|
|
|
94
110
|
.OUTPUTS
|
|
95
|
-
System.String. The floor band: C1 when no
|
|
111
|
+
System.String. The floor band: C1 when no recorded name is a floor signal,
|
|
96
112
|
otherwise the maximum triggered candidate band clamped to at most C3.
|
|
97
113
|
C4 is never returned.
|
|
98
114
|
#>
|
|
@@ -104,9 +120,13 @@ function Get-ComplexityFloor {
|
|
|
104
120
|
[string[]] $SignalsPresent
|
|
105
121
|
)
|
|
106
122
|
|
|
123
|
+
# Keep only the recorded names that are floor signals. A "floor": false catalog
|
|
124
|
+
# name and an unknown name both drop out here and contribute no candidate band.
|
|
125
|
+
$triggered = @($SignalsPresent | Where-Object { $script:FLOOR_SIGNAL_NAMES -contains $_ })
|
|
126
|
+
|
|
107
127
|
# With no present floor signal there is no candidate band to raise the floor
|
|
108
128
|
# above the lowest band, so the floor is C1 (mirrors the empty-input guard).
|
|
109
|
-
if (
|
|
129
|
+
if ($triggered.Count -eq 0) {
|
|
110
130
|
return $script:LOWEST_BAND
|
|
111
131
|
}
|
|
112
132
|
|
package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorState.psm1
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
checkpoint-presence checks (required keys, step-status validity, blocked_reason
|
|
15
15
|
validity) from `scripts/dev_tools/validate_orchestrator_state.py`. The base
|
|
16
16
|
constants below are pinned to `REQUIRED_STATE_KEYS`, `STEP_STATUS_KEYS`,
|
|
17
|
-
`VALID_STEP_STATUS`,
|
|
17
|
+
`VALID_STEP_STATUS`, `VALID_BLOCKED_REASONS`, and `STEP_SPECIFIC_EXTRA_STATUS`.
|
|
18
18
|
|
|
19
19
|
Every public function FAILS CLOSED: a missing checkpoint file, invalid JSON, a
|
|
20
20
|
missing required key, an invalid step status, or an unmet readiness condition all
|
|
@@ -83,6 +83,15 @@ $script:VALID_STEP_STATUS = @(
|
|
|
83
83
|
'completed'
|
|
84
84
|
)
|
|
85
85
|
|
|
86
|
+
# Per-key additive step-status vocabulary layered on VALID_STEP_STATUS: each value
|
|
87
|
+
# below is valid only on its owning key and is still rejected on every other step
|
|
88
|
+
# key. Pinned to STEP_SPECIFIC_EXTRA_STATUS in
|
|
89
|
+
# scripts/dev_tools/_orchestrator_state_step_status.py.
|
|
90
|
+
$script:STEP_SPECIFIC_EXTRA_STATUS = @{
|
|
91
|
+
step6_status = @('blocked_remediation_loop_limit')
|
|
92
|
+
step9_status = @('passed', 'failed_remediation_required', 'blocked_ci_loop_limit')
|
|
93
|
+
}
|
|
94
|
+
|
|
86
95
|
# The allowed blocked_reason vocabulary. Pinned to VALID_BLOCKED_REASONS in the
|
|
87
96
|
# primary validator.
|
|
88
97
|
$script:VALID_BLOCKED_REASONS = @(
|
|
@@ -226,11 +235,11 @@ function Get-OrchestratorStateBasePresenceError {
|
|
|
226
235
|
Return the base checkpoint-presence errors, mirroring the primary validator.
|
|
227
236
|
.DESCRIPTION
|
|
228
237
|
Private base check. Emits one error string per missing required key, one per
|
|
229
|
-
step5_status..step10_status value outside VALID_STEP_STATUS
|
|
230
|
-
blocked_reason is present with a
|
|
231
|
-
mirrors the base block of
|
|
232
|
-
(required keys, step-status
|
|
233
|
-
before any mode-specific gate.
|
|
238
|
+
step5_status..step10_status value outside VALID_STEP_STATUS and that key's
|
|
239
|
+
STEP_SPECIFIC_EXTRA_STATUS set, and one when blocked_reason is present with a
|
|
240
|
+
value outside VALID_BLOCKED_REASONS. This mirrors the base block of
|
|
241
|
+
scripts/dev_tools/validate_orchestrator_state.py (required keys, step-status
|
|
242
|
+
validity, blocked_reason validity) that runs before any mode-specific gate.
|
|
234
243
|
.PARAMETER State
|
|
235
244
|
The parsed checkpoint PSCustomObject.
|
|
236
245
|
.OUTPUTS
|
|
@@ -254,12 +263,16 @@ function Get-OrchestratorStateBasePresenceError {
|
|
|
254
263
|
}
|
|
255
264
|
}
|
|
256
265
|
|
|
257
|
-
# Every present step status must be a member of the
|
|
258
|
-
# step key contributes no error (mirrors the
|
|
266
|
+
# Every present step status must be a member of the shared vocabulary or of that
|
|
267
|
+
# key's additive extra set; an absent step key contributes no error (mirrors the
|
|
268
|
+
# primary validator's None guard).
|
|
259
269
|
foreach ($key in $script:STEP_STATUS_KEYS) {
|
|
260
270
|
$field = Get-OrchestratorStateField -State $State -Name $key
|
|
271
|
+
$extra = @()
|
|
272
|
+
if ($script:STEP_SPECIFIC_EXTRA_STATUS.ContainsKey($key)) { $extra = @($script:STEP_SPECIFIC_EXTRA_STATUS[$key]) }
|
|
261
273
|
if ($field.Present -and $null -ne $field.Value -and
|
|
262
|
-
($script:VALID_STEP_STATUS -notcontains [string]$field.Value)
|
|
274
|
+
($script:VALID_STEP_STATUS -notcontains [string]$field.Value) -and
|
|
275
|
+
($extra -notcontains [string]$field.Value)) {
|
|
263
276
|
$errors.Add("Checkpoint has invalid $key`: $($field.Value)")
|
|
264
277
|
}
|
|
265
278
|
}
|
|
@@ -279,12 +292,11 @@ function Get-OrchestratorStatePrCreationReadinessError {
|
|
|
279
292
|
.SYNOPSIS
|
|
280
293
|
Return the PR-creation-readiness errors, parity with the Python reference.
|
|
281
294
|
.DESCRIPTION
|
|
282
|
-
Private readiness check mirroring
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
present. It does not enforce completion, CI, PR, or routing-contract gates.
|
|
295
|
+
Private readiness check mirroring validate_orchestrator_state_pr_creation_readiness in
|
|
296
|
+
_orchestrator_state_pr_creation_readiness.py: steps 5-8 must not be pending, blocked, or
|
|
297
|
+
blocked_remediation_loop_limit; blocked_reason must be `none` or absent; and the
|
|
298
|
+
local_execution_overrides / delegation_bypasses lists must be empty when present. It does
|
|
299
|
+
not enforce completion, CI, PR, or routing-contract gates.
|
|
288
300
|
.PARAMETER State
|
|
289
301
|
The parsed checkpoint PSCustomObject.
|
|
290
302
|
.OUTPUTS
|
|
@@ -299,11 +311,11 @@ function Get-OrchestratorStatePrCreationReadinessError {
|
|
|
299
311
|
|
|
300
312
|
$errors = [System.Collections.Generic.List[string]]::new()
|
|
301
313
|
|
|
302
|
-
# Reject an upstream step recorded as pending or
|
|
303
|
-
# finished before the first PR of a branch is created.
|
|
314
|
+
# Reject an upstream step recorded as pending, blocked, or blocked_remediation_loop_limit; steps
|
|
315
|
+
# 5-8 must have finished before the first PR of a branch is created.
|
|
304
316
|
foreach ($key in $script:PR_CREATION_READY_STEP_KEYS) {
|
|
305
317
|
$field = Get-OrchestratorStateField -State $State -Name $key
|
|
306
|
-
if ($field.Present -and (
|
|
318
|
+
if ($field.Present -and (@('pending', 'blocked', 'blocked_remediation_loop_limit') -contains $field.Value)) {
|
|
307
319
|
$errors.Add("Checkpoint PR-creation readiness validation failed: $key is $($field.Value).")
|
|
308
320
|
}
|
|
309
321
|
}
|
|
@@ -36,7 +36,7 @@ Run the full seven-stage toolchain in this exact order and repeat until all stag
|
|
|
36
36
|
2. **Linting** (e.g., Ruff, ESLint, PSScriptAnalyzer, .NET analyzers)
|
|
37
37
|
3. **Type checking** (e.g., Pyright, TSC, nullable analysis; skip for PowerShell)
|
|
38
38
|
4. **Architecture-boundary tests** (e.g., dependency-cruiser, NetArchTest.Rules)
|
|
39
|
-
5. **Unit tests** (e.g., Pytest,
|
|
39
|
+
5. **Unit tests** (e.g., Pytest, Jest, MSTest, Pester) including property-based tests where applicable per `quality-tiers.md`
|
|
40
40
|
6. **Contract / schema compatibility checks** (e.g., oasdiff, schema-snapshot diff)
|
|
41
41
|
7. **Integration tests**
|
|
42
42
|
|
|
@@ -37,7 +37,7 @@ The correct response to a file that contains untestable lines is to refactor it
|
|
|
37
37
|
**Permitted `exclude` entries** (non-production paths only):
|
|
38
38
|
- Build output directories: `dist/**`, `lib/**`, `lib-amd/**`.
|
|
39
39
|
- Test files and test infrastructure: `**/*.test.ts`, `tests/**`, `src/test-support/**`.
|
|
40
|
-
- Config files that are not production code: `
|
|
40
|
+
- Config files that are not production code: `jest.config.cjs`, `eslint.config.mjs`, `.dependency-cruiser.cjs`, `webpack.config.js`.
|
|
41
41
|
- `node_modules/**`.
|
|
42
42
|
|
|
43
43
|
**Prohibited `exclude` entries:**
|
|
@@ -102,4 +102,4 @@ All test code must be deterministic. The following infrastructure requirements a
|
|
|
102
102
|
- **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.
|
|
103
103
|
- **Seeded RNG** — randomness must be supplied via a seedable interface; on test failure the seed must be printed so the failure is reproducible.
|
|
104
104
|
- **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.
|
|
105
|
-
- **Virtual scheduler / fake timers / `FakeTimeProvider`** — async tests must use the framework's fake-timer facility (`
|
|
105
|
+
- **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.
|
|
@@ -13,7 +13,7 @@ This rule file summarizes the TypeScript-specific policies for this repository.
|
|
|
13
13
|
1. **Formatting — Prettier**: All TypeScript must be formatted with the repository Prettier configuration. Command: `npm run format`
|
|
14
14
|
2. **Linting — ESLint**: TypeScript must pass ESLint using the repository configuration. Command: `npm run lint`
|
|
15
15
|
3. **Type Checking — TSC**: TypeScript must pass the compiler type-check. Avoid `any`; prefer `unknown` plus narrowing. Command: `npm run typecheck`
|
|
16
|
-
4. **Testing —
|
|
16
|
+
4. **Testing — Jest**: All TypeScript unit tests must use Jest. Command: `npm run test:unit`
|
|
17
17
|
|
|
18
18
|
Run the toolchain in order: format → lint → type-check → test. Restart from step 1 if any step fails or changes files.
|
|
19
19
|
|
|
@@ -39,16 +39,16 @@ Run the toolchain in order: format → lint → type-check → test. Restart fro
|
|
|
39
39
|
|
|
40
40
|
## Testing Standards
|
|
41
41
|
|
|
42
|
-
- Use **
|
|
42
|
+
- Use **Jest** as the test framework.
|
|
43
43
|
- Name test files `*.test.ts`.
|
|
44
44
|
- Unit tests must not require the Outlook host runtime.
|
|
45
45
|
- Follow Arrange–Act–Assert structure.
|
|
46
46
|
- Each test targets one behavior.
|
|
47
|
-
- Use `
|
|
47
|
+
- Use `jest.spyOn` or `jest.mock` for targeted mocking; reset mocks with `afterEach(() => { jest.resetAllMocks(); })`.
|
|
48
48
|
- No external dependencies (network, filesystem temp files, external processes) in unit tests.
|
|
49
49
|
- Avoid snapshot tests unless stable and intentional.
|
|
50
50
|
- Coverage thresholds follow the uniform tier rule defined in `.claude/rules/quality-tiers.md`: line coverage >= 85% and branch coverage >= 75% across all tiers (T1–T4).
|
|
51
|
-
- Coverage command: `npm run test:coverage` (the
|
|
51
|
+
- Coverage command: `npm run test:unit:coverage` (the root `package.json` script runs `node run-jest.cjs --coverage`).
|
|
52
52
|
- Coverage regression on changed lines is a blocking finding.
|
|
53
53
|
- Interface/type-only files with no executable behavior — files consisting solely of `interface` or `type` declarations — may be omitted from coverage measurement. Such files legitimately report 0% executable coverage. This is a clarification only; it does not lower any coverage threshold.
|
|
54
54
|
|
|
@@ -70,5 +70,5 @@ Layer rules and the No-COM architecture assertions are defined in `.claude/rules
|
|
|
70
70
|
## Runtime Determinism
|
|
71
71
|
|
|
72
72
|
- `Date`, `Math.random`, and `setTimeout` access must flow through an injected `Clock` / `Random` interface.
|
|
73
|
-
- Tests use
|
|
73
|
+
- Tests use Jest fake timers (`jest.useFakeTimers()`).
|
|
74
74
|
- Prefer `await flushPromises()` over `setTimeout(0)` for awaiting micro-tasks.
|
package/resources/codex-and-agents-customizations/.agents/skills/general-code-change/SKILL.md
CHANGED
|
@@ -41,7 +41,7 @@ Run the full seven-stage toolchain in this exact order and repeat until all stag
|
|
|
41
41
|
2. **Linting** (e.g., Ruff, ESLint, PSScriptAnalyzer, .NET analyzers)
|
|
42
42
|
3. **Type checking** (e.g., Pyright, TSC, nullable analysis; skip for PowerShell)
|
|
43
43
|
4. **Architecture-boundary tests** (e.g., dependency-cruiser, NetArchTest.Rules)
|
|
44
|
-
5. **Unit tests** (e.g., Pytest,
|
|
44
|
+
5. **Unit tests** (e.g., Pytest, Jest, MSTest, Pester) including property-based tests where applicable per `quality-tiers.md`
|
|
45
45
|
6. **Contract / schema compatibility checks** (e.g., oasdiff, schema-snapshot diff)
|
|
46
46
|
7. **Integration tests**
|
|
47
47
|
|
|
@@ -42,7 +42,7 @@ The correct response to a file that contains untestable lines is to refactor it
|
|
|
42
42
|
**Permitted `exclude` entries** (non-production paths only):
|
|
43
43
|
- Build output directories: `dist/**`, `lib/**`, `lib-amd/**`.
|
|
44
44
|
- Test files and test infrastructure: `**/*.test.ts`, `tests/**`, `src/test-support/**`.
|
|
45
|
-
- Config files that are not production code: `
|
|
45
|
+
- Config files that are not production code: `jest.config.cjs`, `eslint.config.mjs`, `.dependency-cruiser.cjs`, `webpack.config.js`.
|
|
46
46
|
- `node_modules/**`.
|
|
47
47
|
|
|
48
48
|
**Prohibited `exclude` entries:**
|
|
@@ -107,4 +107,4 @@ All test code must be deterministic. The following infrastructure requirements a
|
|
|
107
107
|
- **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.
|
|
108
108
|
- **Seeded RNG** — randomness must be supplied via a seedable interface; on test failure the seed must be printed so the failure is reproducible.
|
|
109
109
|
- **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.
|
|
110
|
-
- **Virtual scheduler / fake timers / `FakeTimeProvider`** — async tests must use the framework's fake-timer facility (`
|
|
110
|
+
- **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.
|
package/resources/codex-and-agents-customizations/.codex/hooks/check-powershell-test-purity.ps1
CHANGED
|
@@ -33,6 +33,10 @@
|
|
|
33
33
|
[CmdletBinding()]
|
|
34
34
|
param()
|
|
35
35
|
|
|
36
|
+
# Shared Codex PreToolUse transport: stdin payload parsing and tool_input-to-file
|
|
37
|
+
# mapping for every tool name the ^(apply_patch|Edit|Write)$ matcher admits.
|
|
38
|
+
. (Join-Path $PSScriptRoot 'codex-pretooluse-file-mapping.ps1')
|
|
39
|
+
|
|
36
40
|
function Get-PowerShellTestPurityBlockDecision {
|
|
37
41
|
[CmdletBinding()]
|
|
38
42
|
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
@@ -138,76 +142,16 @@ function Invoke-PowerShellTestPurityDecision {
|
|
|
138
142
|
return Get-PowerShellTestPurityBlockDecision -Reason $reason
|
|
139
143
|
}
|
|
140
144
|
|
|
141
|
-
function ConvertFrom-CodexPowerShellPurityPayload {
|
|
142
|
-
[CmdletBinding()]
|
|
143
|
-
param([Parameter(Mandatory)][string] $PayloadRaw)
|
|
144
|
-
|
|
145
|
-
if ([string]::IsNullOrWhiteSpace($PayloadRaw)) {
|
|
146
|
-
throw 'check-powershell-test-purity hook input is empty.'
|
|
147
|
-
}
|
|
148
|
-
try {
|
|
149
|
-
$payload = $PayloadRaw | ConvertFrom-Json -ErrorAction Stop
|
|
150
|
-
} catch {
|
|
151
|
-
throw "check-powershell-test-purity hook input is malformed JSON: $_"
|
|
152
|
-
}
|
|
153
|
-
if ($payload.PSObject.Properties.Name -notcontains 'tool_input' -or $null -eq $payload.tool_input) {
|
|
154
|
-
throw 'check-powershell-test-purity hook input is missing tool_input.'
|
|
155
|
-
}
|
|
156
|
-
if ([string]$payload.hook_event_name -ne 'PreToolUse' -or [string]$payload.tool_name -ne 'apply_patch') {
|
|
157
|
-
throw 'check-powershell-test-purity requires a PreToolUse apply_patch payload.'
|
|
158
|
-
}
|
|
159
|
-
return $payload
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function ConvertTo-CodexPowerShellPurityInput {
|
|
163
|
-
[CmdletBinding()]
|
|
164
|
-
[OutputType([object[]])]
|
|
165
|
-
param([Parameter(Mandatory)] $Payload)
|
|
166
|
-
|
|
167
|
-
if ($Payload.tool_input.PSObject.Properties.Name -contains 'file_path') {
|
|
168
|
-
return , $Payload.tool_input
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
$command = [string]$Payload.tool_input.command
|
|
172
|
-
if ([string]::IsNullOrWhiteSpace($command)) {
|
|
173
|
-
throw 'check-powershell-test-purity cannot map tool_input to a file edit.'
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
$fileMatches = [regex]::Matches(
|
|
177
|
-
$command,
|
|
178
|
-
'(?ms)^\*\*\* (?:Add|Update|Delete) File:\s*(?<path>.+?)\r?\n(?<body>.*?)(?=^\*\*\* (?:(?:Add|Update|Delete) File:|End Patch)\s*|\z)'
|
|
179
|
-
)
|
|
180
|
-
if ($fileMatches.Count -eq 0) {
|
|
181
|
-
throw 'check-powershell-test-purity received an unrecognized apply_patch command.'
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
$inputs = [System.Collections.Generic.List[object]]::new()
|
|
185
|
-
foreach ($match in $fileMatches) {
|
|
186
|
-
$filePath = ([string]$match.Groups['path'].Value).Trim()
|
|
187
|
-
$moveMatch = [regex]::Match([string]$match.Groups['body'].Value, '(?m)^\*\*\* Move to:\s*(?<path>.+?)\s*$')
|
|
188
|
-
if ($moveMatch.Success) {
|
|
189
|
-
$filePath = ([string]$moveMatch.Groups['path'].Value).Trim()
|
|
190
|
-
}
|
|
191
|
-
$addedLines = foreach ($line in ([string]$match.Groups['body'].Value -split '\r?\n')) {
|
|
192
|
-
if ($line.StartsWith('+') -and -not $line.StartsWith('+++')) {
|
|
193
|
-
$line.Substring(1)
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
$inputs.Add([pscustomobject]@{
|
|
197
|
-
file_path = $filePath
|
|
198
|
-
content = $addedLines -join [Environment]::NewLine
|
|
199
|
-
})
|
|
200
|
-
}
|
|
201
|
-
return $inputs.ToArray()
|
|
202
|
-
}
|
|
203
|
-
|
|
204
145
|
if ($MyInvocation.InvocationName -eq '.') {
|
|
205
146
|
return
|
|
206
147
|
}
|
|
207
148
|
|
|
208
149
|
try {
|
|
209
|
-
|
|
210
|
-
|
|
150
|
+
# Transport and mapping come from the shared module. A well-formed payload
|
|
151
|
+
# that maps to no file edit produces an empty record set, so the loop body
|
|
152
|
+
# never runs and the hook allows silently.
|
|
153
|
+
$payload = ConvertFrom-CodexPreToolUsePayload -PayloadRaw ([Console]::In.ReadToEnd()) -HookName 'check-powershell-test-purity'
|
|
154
|
+
foreach ($toolInput in @(ConvertTo-CodexFileEditInput -Payload $payload)) {
|
|
211
155
|
$toolInputRaw = $toolInput | ConvertTo-Json -Compress -Depth 20
|
|
212
156
|
$decision = Invoke-PowerShellTestPurityDecision -ToolInputRaw $toolInputRaw
|
|
213
157
|
if ($null -ne $decision -and $decision.hookSpecificOutput.permissionDecision -eq 'deny') {
|
|
@@ -30,6 +30,10 @@
|
|
|
30
30
|
[CmdletBinding()]
|
|
31
31
|
param()
|
|
32
32
|
|
|
33
|
+
# Shared Codex PreToolUse transport: stdin payload parsing and tool_input-to-file
|
|
34
|
+
# mapping for every tool name the ^(apply_patch|Edit|Write)$ matcher admits.
|
|
35
|
+
. (Join-Path $PSScriptRoot 'codex-pretooluse-file-mapping.ps1')
|
|
36
|
+
|
|
33
37
|
function Get-PythonTestPurityBlockDecision {
|
|
34
38
|
[CmdletBinding()]
|
|
35
39
|
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
@@ -138,76 +142,16 @@ function Invoke-PythonTestPurityDecision {
|
|
|
138
142
|
return Get-PythonTestPurityBlockDecision -Reason $reason
|
|
139
143
|
}
|
|
140
144
|
|
|
141
|
-
function ConvertFrom-CodexPythonPurityPayload {
|
|
142
|
-
[CmdletBinding()]
|
|
143
|
-
param([Parameter(Mandatory)][string] $PayloadRaw)
|
|
144
|
-
|
|
145
|
-
if ([string]::IsNullOrWhiteSpace($PayloadRaw)) {
|
|
146
|
-
throw 'check-python-test-purity hook input is empty.'
|
|
147
|
-
}
|
|
148
|
-
try {
|
|
149
|
-
$payload = $PayloadRaw | ConvertFrom-Json -ErrorAction Stop
|
|
150
|
-
} catch {
|
|
151
|
-
throw "check-python-test-purity hook input is malformed JSON: $_"
|
|
152
|
-
}
|
|
153
|
-
if ($payload.PSObject.Properties.Name -notcontains 'tool_input' -or $null -eq $payload.tool_input) {
|
|
154
|
-
throw 'check-python-test-purity hook input is missing tool_input.'
|
|
155
|
-
}
|
|
156
|
-
if ([string]$payload.hook_event_name -ne 'PreToolUse' -or [string]$payload.tool_name -ne 'apply_patch') {
|
|
157
|
-
throw 'check-python-test-purity requires a PreToolUse apply_patch payload.'
|
|
158
|
-
}
|
|
159
|
-
return $payload
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function ConvertTo-CodexPythonPurityInput {
|
|
163
|
-
[CmdletBinding()]
|
|
164
|
-
[OutputType([object[]])]
|
|
165
|
-
param([Parameter(Mandatory)] $Payload)
|
|
166
|
-
|
|
167
|
-
if ($Payload.tool_input.PSObject.Properties.Name -contains 'file_path') {
|
|
168
|
-
return , $Payload.tool_input
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
$command = [string]$Payload.tool_input.command
|
|
172
|
-
if ([string]::IsNullOrWhiteSpace($command)) {
|
|
173
|
-
throw 'check-python-test-purity cannot map tool_input to a file edit.'
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
$fileMatches = [regex]::Matches(
|
|
177
|
-
$command,
|
|
178
|
-
'(?ms)^\*\*\* (?:Add|Update|Delete) File:\s*(?<path>.+?)\r?\n(?<body>.*?)(?=^\*\*\* (?:(?:Add|Update|Delete) File:|End Patch)\s*|\z)'
|
|
179
|
-
)
|
|
180
|
-
if ($fileMatches.Count -eq 0) {
|
|
181
|
-
throw 'check-python-test-purity received an unrecognized apply_patch command.'
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
$inputs = [System.Collections.Generic.List[object]]::new()
|
|
185
|
-
foreach ($match in $fileMatches) {
|
|
186
|
-
$filePath = ([string]$match.Groups['path'].Value).Trim()
|
|
187
|
-
$moveMatch = [regex]::Match([string]$match.Groups['body'].Value, '(?m)^\*\*\* Move to:\s*(?<path>.+?)\s*$')
|
|
188
|
-
if ($moveMatch.Success) {
|
|
189
|
-
$filePath = ([string]$moveMatch.Groups['path'].Value).Trim()
|
|
190
|
-
}
|
|
191
|
-
$addedLines = foreach ($line in ([string]$match.Groups['body'].Value -split '\r?\n')) {
|
|
192
|
-
if ($line.StartsWith('+') -and -not $line.StartsWith('+++')) {
|
|
193
|
-
$line.Substring(1)
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
$inputs.Add([pscustomobject]@{
|
|
197
|
-
file_path = $filePath
|
|
198
|
-
content = $addedLines -join [Environment]::NewLine
|
|
199
|
-
})
|
|
200
|
-
}
|
|
201
|
-
return $inputs.ToArray()
|
|
202
|
-
}
|
|
203
|
-
|
|
204
145
|
if ($MyInvocation.InvocationName -eq '.') {
|
|
205
146
|
return
|
|
206
147
|
}
|
|
207
148
|
|
|
208
149
|
try {
|
|
209
|
-
|
|
210
|
-
|
|
150
|
+
# Transport and mapping come from the shared module. A well-formed payload
|
|
151
|
+
# that maps to no file edit produces an empty record set, so the loop body
|
|
152
|
+
# never runs and the hook allows silently.
|
|
153
|
+
$payload = ConvertFrom-CodexPreToolUsePayload -PayloadRaw ([Console]::In.ReadToEnd()) -HookName 'check-python-test-purity'
|
|
154
|
+
foreach ($toolInput in @(ConvertTo-CodexFileEditInput -Payload $payload)) {
|
|
211
155
|
$toolInputRaw = $toolInput | ConvertTo-Json -Compress -Depth 20
|
|
212
156
|
$decision = Invoke-PythonTestPurityDecision -ToolInputRaw $toolInputRaw
|
|
213
157
|
if ($null -ne $decision -and $decision.hookSpecificOutput.permissionDecision -eq 'deny') {
|