devlyn-cli 2.9.3 → 2.10.1

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 (40) hide show
  1. package/AGENTS.md +3 -3
  2. package/CLAUDE.md +5 -5
  3. package/README.md +5 -5
  4. package/benchmark/auto-resolve/fixtures/SCHEMA.md +1 -1
  5. package/benchmark/auto-resolve/scripts/pair_evidence_contract.py +1 -0
  6. package/benchmark/auto-resolve/scripts/run-fixture.sh +11 -1
  7. package/benchmark/auto-resolve/scripts/test-benchmark-arg-parsing.sh +83 -1
  8. package/bin/devlyn.js +108 -15
  9. package/config/skills/_shared/adapters/README.md +2 -2
  10. package/config/skills/_shared/adapters/grok.md +99 -0
  11. package/config/skills/_shared/archive_run.py +35 -1
  12. package/config/skills/_shared/collect-codex-findings.py +231 -50
  13. package/config/skills/_shared/engine-doctor.sh +6 -3
  14. package/config/skills/_shared/engine-preflight.md +12 -5
  15. package/config/skills/_shared/expected.schema.json +7 -0
  16. package/config/skills/_shared/grok-anchor-guard.py +61 -0
  17. package/config/skills/_shared/phase-prompt-render.py +181 -0
  18. package/config/skills/_shared/resolve-bootstrap.py +630 -0
  19. package/config/skills/_shared/resolve-stop-hook.py +325 -0
  20. package/config/skills/_shared/spec-verify-check.py +308 -31
  21. package/config/skills/_shared/state-phase-write.py +2966 -27
  22. package/config/skills/_shared/terminal-claim-check.py +537 -0
  23. package/config/skills/_shared/verify-merge-findings.py +533 -16
  24. package/config/skills/devlyn:engines/SKILL.md +2 -2
  25. package/config/skills/devlyn:ideate/SKILL.md +1 -1
  26. package/config/skills/devlyn:queue/SKILL.md +12 -0
  27. package/config/skills/devlyn:resolve/SKILL.md +60 -78
  28. package/config/skills/devlyn:resolve/references/phases/build-gate.md +2 -2
  29. package/config/skills/devlyn:resolve/references/phases/implement.md +1 -1
  30. package/config/skills/devlyn:resolve/references/phases/plan.md +1 -5
  31. package/config/skills/devlyn:resolve/references/phases/probe-derive.md +4 -2
  32. package/config/skills/devlyn:resolve/references/phases/surface-close.md +34 -0
  33. package/config/skills/devlyn:resolve/references/phases/verify.md +70 -97
  34. package/config/skills/devlyn:resolve/references/state-schema.md +26 -19
  35. package/optional-skills/devlyn:reap/SKILL.md +1 -1
  36. package/optional-skills/devlyn:reap/scripts/reap.sh +25 -13
  37. package/optional-skills/devlyn:reap/scripts/scan.sh +15 -1
  38. package/package.json +1 -1
  39. package/scripts/lint-fixtures.sh +4 -1
  40. package/scripts/lint-skills.sh +269 -77
@@ -60,17 +60,23 @@ _shared/spec-verify-check.py
60
60
  _shared/collect-codex-findings.py
61
61
  _shared/verify-merge-findings.py
62
62
  _shared/state-phase-write.py
63
+ _shared/terminal-claim-check.py
64
+ _shared/resolve-stop-hook.py
65
+ _shared/resolve-bootstrap.py
63
66
  devlyn:ideate/SKILL.md
64
67
  devlyn:ideate/references/spec-template.md
65
68
  devlyn:ideate/references/elicitation.md
66
69
  devlyn:ideate/references/project-mode.md
67
70
  devlyn:ideate/references/from-spec-mode.md
68
71
  devlyn:resolve/SKILL.md
72
+ devlyn:queue/SKILL.md
73
+ devlyn:engines/SKILL.md
69
74
  devlyn:resolve/references/state-schema.md
70
75
  devlyn:resolve/references/free-form-mode.md
71
76
  devlyn:resolve/references/phases/plan.md
72
77
  devlyn:resolve/references/phases/probe-derive.md
73
78
  devlyn:resolve/references/phases/implement.md
79
+ devlyn:resolve/references/phases/surface-close.md
74
80
  devlyn:resolve/references/phases/build-gate.md
75
81
  devlyn:resolve/references/phases/cleanup.md
76
82
  devlyn:resolve/references/phases/verify.md
@@ -78,6 +84,7 @@ _shared/expected.schema.json
78
84
  _shared/adapters/README.md
79
85
  _shared/adapters/claude.md
80
86
  _shared/adapters/codex.md
87
+ _shared/adapters/grok.md
81
88
  _shared/adapters/omp.md
82
89
  _shared/codex-config.md
83
90
  _shared/codex-monitored.sh
@@ -146,6 +153,7 @@ offenders=$(git grep -Il -- 'mcp__codex-cli__' -- \
146
153
  ':!config/skills/preflight-workspace/**' \
147
154
  ':!benchmark/auto-resolve/external/**' \
148
155
  ':!benchmark/auto-resolve/results/**' \
156
+ ':!benchmark/ceiling/results/**' \
149
157
  ':!benchmark/auto-resolve/PILOT-RESULTS*' \
150
158
  2>/dev/null || true)
151
159
  if [ -z "$offenders" ]; then
@@ -170,6 +178,7 @@ offenders=$(git grep -Il -- 'Requires Codex MCP\|Codex MCP server\|Codex MCP ava
170
178
  ':!config/skills/preflight-workspace/**' \
171
179
  ':!benchmark/auto-resolve/external/**' \
172
180
  ':!benchmark/auto-resolve/results/**' \
181
+ ':!benchmark/ceiling/results/**' \
173
182
  ':!benchmark/auto-resolve/PILOT-RESULTS*' \
174
183
  2>/dev/null || true)
175
184
  if [ -z "$offenders" ]; then
@@ -198,16 +207,19 @@ else
198
207
  fi
199
208
 
200
209
  # ---------------------------------------------------------------------------
201
- # 4. No model-pinned Claude references, any generation. Engine-scope docs say
202
- # "Claude"; exact model ids belong in run metadata only.
210
+ # 4. No model-pinned Claude references, any generation, except the adjudicated
211
+ # SURFACE_CLOSE envelope whose requested id must equal CLI arg and modelUsage.
203
212
  # ---------------------------------------------------------------------------
204
213
  section "Check 4: No model-pinned Claude references"
205
- offenders=$(grep -RIlnE 'Claude (Opus|Sonnet|Haiku|Fable)|claude-(opus|sonnet|haiku|fable)-[0-9]' \
214
+ offenders=$(grep -RInE 'Claude (Opus|Sonnet|Haiku|Fable)|claude-(opus|sonnet|haiku|fable)-[0-9]' \
206
215
  config/skills 2>/dev/null \
207
216
  | grep -v 'config/skills/roadmap-archival-workspace/' \
208
217
  | grep -v 'config/skills/devlyn:auto-resolve-workspace/' \
209
218
  | grep -v 'config/skills/devlyn:ideate-workspace/' \
210
219
  | grep -v 'config/skills/preflight-workspace/' \
220
+ | grep -vE '^config/skills/devlyn:resolve/SKILL\.md:[0-9]+:Freeze .*--tools "Read,Grep,Glob,Edit,Write" --dangerously-skip-permissions --model claude-sonnet-5 --output-format json --strict-mcp-config --mcp-config '\''\{"mcpServers":\{\}\}'\''.*, recording the same model at SPW spawn\.' \
221
+ | cut -d: -f1 \
222
+ | sort -u \
211
223
  || true)
212
224
  if [ -z "$offenders" ]; then
213
225
  ok "no model-pinned Claude references"
@@ -257,6 +269,84 @@ else
257
269
  bad "devlyn:design-ui must not be listed in OPTIONAL_ADDONS"
258
270
  fi
259
271
 
272
+ # ---------------------------------------------------------------------------
273
+ # 5b. Every completed managed-skills install writes its own version marker.
274
+ # ---------------------------------------------------------------------------
275
+ section "Check 5b: Installed skill roots carry exact version markers"
276
+ if make_temp_dir tmp_install_marker /tmp/devlyn-install-marker.XXXXXX; then
277
+ marker_home="$tmp_install_marker/home"
278
+ marker_project="$tmp_install_marker/project"
279
+ installer="$PWD/bin/devlyn.js"
280
+ mkdir -p "$marker_home" "$marker_project"
281
+
282
+ if (cd "$marker_project" \
283
+ && HOME="$marker_home" node "$installer" -y >"$tmp_install_marker/claude.log" 2>&1 \
284
+ && HOME="$marker_home" node "$installer" agents all >"$tmp_install_marker/agents.log" 2>&1); then
285
+ if python3 - "$installer" "$marker_home" "$marker_project" <<'PY'
286
+ import json
287
+ import pathlib
288
+ import stat
289
+ import sys
290
+
291
+ installer = pathlib.Path(sys.argv[1])
292
+ home = pathlib.Path(sys.argv[2])
293
+ project = pathlib.Path(sys.argv[3])
294
+ package = json.loads((installer.parent.parent / "package.json").read_text())
295
+ expected = {"schemaVersion": 1, "package": package["name"], "version": package["version"]}
296
+ roots = [
297
+ project / ".claude" / "skills",
298
+ home / ".codex" / "skills",
299
+ home / ".agents" / "skills",
300
+ home / ".grok" / "skills",
301
+ ]
302
+ for root in roots:
303
+ marker = root / ".devlyn-install.json"
304
+ actual = json.loads(marker.read_text())
305
+ if actual != expected:
306
+ raise SystemExit(f"{marker}: expected {expected}, got {actual}")
307
+ if stat.S_IMODE(marker.stat().st_mode) != 0o600:
308
+ raise SystemExit(f"{marker}: expected mode 0600")
309
+ if list(root.glob(".devlyn-install.json.*.tmp")):
310
+ raise SystemExit(f"{root}: stale marker temp file")
311
+ PY
312
+ then
313
+ if grep -Fxq '.claude/skills/.devlyn-install.json' "$marker_project/.gitignore"; then
314
+ ok "Claude, Codex, shared-agent, and Grok roots have exact 0600 markers; project marker is ignored"
315
+ else
316
+ bad "Claude project install must ignore its local marker"
317
+ fi
318
+ else
319
+ bad "completed installs must write the exact per-root marker"
320
+ fi
321
+ else
322
+ bad "isolated managed-skills install failed"
323
+ fi
324
+
325
+ incomplete="$tmp_install_marker/incomplete"
326
+ mkdir -p "$incomplete/package" "$incomplete/home/.codex/skills" "$incomplete/claude/.claude/skills" "$incomplete/codex"
327
+ cp -R bin config package.json "$incomplete/package/"
328
+ rm -rf "$incomplete/package/config/skills/devlyn:queue"
329
+ printf '{"version":"stale"}\n' > "$incomplete/home/.codex/skills/.devlyn-install.json"
330
+ printf '{"version":"stale"}\n' > "$incomplete/claude/.claude/skills/.devlyn-install.json"
331
+ if ! (cd "$incomplete/claude" \
332
+ && HOME="$incomplete/home" node "$incomplete/package/bin/devlyn.js" -y >"$incomplete/claude.log" 2>&1) \
333
+ && ! (cd "$incomplete/codex" \
334
+ && HOME="$incomplete/home" node "$incomplete/package/bin/devlyn.js" agents codex >"$incomplete/codex.log" 2>&1) \
335
+ && grep -Fq 'Incomplete devlyn skill install; missing: devlyn:queue' "$incomplete/claude.log" \
336
+ && grep -Fq 'Incomplete devlyn skill install; missing: devlyn:queue' "$incomplete/codex.log" \
337
+ && [ -d "$incomplete/claude/.claude/skills/devlyn:resolve" ] \
338
+ && [ -d "$incomplete/home/.codex/skills/devlyn:resolve" ] \
339
+ && [ ! -e "$incomplete/claude/.claude/skills/.devlyn-install.json" ] \
340
+ && [ ! -e "$incomplete/home/.codex/skills/.devlyn-install.json" ]; then
341
+ ok "incomplete Claude and Codex copies fail visibly without stale or replacement markers"
342
+ else
343
+ bad "incomplete Claude and Codex copies must fail visibly without an install marker"
344
+ fi
345
+ rm -rf "$tmp_install_marker"
346
+ else
347
+ bad "could not allocate install-marker smoke directory"
348
+ fi
349
+
260
350
  # ---------------------------------------------------------------------------
261
351
  # 6. Source ↔ installed mirror parity on critical path.
262
352
  # Only runs if .claude/skills exists (i.e. installer has been run).
@@ -332,23 +422,23 @@ if ! grep -Fq 'def pair_trigger_skip_contract_violation' config/skills/_shared/v
332
422
  || ! grep -Fq 'verify-pair-trigger-skipped-reason-unsupported' config/skills/_shared/verify-merge-findings.py \
333
423
  || ! grep -Fq 'verify-pair-trigger-mechanical-blocker-unsupported' config/skills/_shared/verify-merge-findings.py \
334
424
  || ! grep -Fq 'verify-pair-trigger-primary-judge-blocker-unsupported' config/skills/_shared/verify-merge-findings.py \
335
- || ! grep -Fq 'eligible:false` with no supported skip reason' config/skills/devlyn:resolve/references/state-schema.md \
336
- || ! grep -Fq 'Canonical eligible reasons are `mode.verify-only`' config/skills/devlyn:resolve/references/state-schema.md \
425
+ || ! grep -Fq 'Missing, contradictory, incomplete, or unknown trigger state BLOCKs VERIFY' config/skills/devlyn:resolve/references/state-schema.md \
426
+ || ! grep -Fq 'Canonical reasons are `pair.default`, `mode.verify-only`' config/skills/devlyn:resolve/references/state-schema.md \
337
427
  || ! grep -Fq '`mode.pair-verify`' config/skills/devlyn:resolve/references/state-schema.md \
338
- || ! grep -Fq 'Eligible triggers must contain only canonical' config/skills/devlyn:resolve/references/phases/verify.md \
339
- || ! grep -Fq 'include every applicable canonical reason' config/skills/devlyn:resolve/references/phases/verify.md \
428
+ || ! grep -Fq 'Eligible schema-v3 state must contain `pair.default` and every other applicable canonical' config/skills/devlyn:resolve/references/phases/verify.md \
429
+ || ! grep -Fq 'with `pair.default` plus every applicable outcome-independent canonical reason' config/skills/devlyn:resolve/references/phases/verify.md \
340
430
  || ! grep -Fq '`mode.pair-verify`' config/skills/devlyn:resolve/references/phases/verify.md \
341
431
  || ! grep -Fq '"pair_verify": false' config/skills/devlyn:resolve/references/state-schema.md \
342
- || ! grep -Fq 'requires a non-empty reasons list containing every applicable canonical eligible reason' config/skills/devlyn:resolve/references/state-schema.md \
343
- || ! grep -Fq 'containing every applicable canonical eligible reason' config/skills/devlyn:resolve/references/state-schema.md \
344
- || ! grep -Fq 'state.pair_verify == true' config/skills/devlyn:resolve/references/state-schema.md \
432
+ || ! grep -Fq 'Schema v3.0 eligible state requires `pair.default` plus every applicable telemetry reason' config/skills/devlyn:resolve/references/state-schema.md \
433
+ || ! grep -Fq 'Ineligible new-run state has empty reasons and only' config/skills/devlyn:resolve/references/state-schema.md \
434
+ || ! grep -Fq 'True only for `--pair-verify`' config/skills/devlyn:resolve/references/state-schema.md \
345
435
  || ! grep -Fq 'pair_verify: true` only when `--pair-verify` was passed' config/skills/devlyn:resolve/SKILL.md \
346
- || ! grep -Fq 'include every applicable canonical reason' config/skills/devlyn:resolve/SKILL.md \
436
+ || ! grep -Fq 'persist `pair_trigger` before spawn with `pair.default` plus every applicable outcome-independent reason' config/skills/devlyn:resolve/SKILL.md \
347
437
  || ! grep -Fq '`--pair-verify` and `--no-pair` are mutually exclusive' config/skills/devlyn:resolve/SKILL.md \
348
438
  || ! grep -Fq 'mutually exclusive with `risk_profile.pair_default_enabled == false`' config/skills/devlyn:resolve/references/state-schema.md \
349
439
  || ! grep -Fq 'if both are present, stop with `BLOCKED:invalid-flags`' config/skills/devlyn:resolve/references/phases/verify.md \
350
- || ! grep -Fq 'Contradictory, incomplete, or unknown trigger state is a VERIFY contract violation' config/skills/devlyn:resolve/SKILL.md \
351
- || ! grep -Fq 'user_no_pair` is valid only when `risk_profile.pair_default_enabled == false`' config/skills/devlyn:resolve/references/state-schema.md \
440
+ || ! grep -Fq 'Malformed risk or trigger state BLOCKs VERIFY' config/skills/devlyn:resolve/SKILL.md \
441
+ || ! grep -Fq 'pair_default_enabled` is false only for explicit `--no-pair`' config/skills/devlyn:resolve/references/state-schema.md \
352
442
  || ! grep -Fq 'def reject_json_constant' config/skills/_shared/verify-merge-findings.py \
353
443
  || ! grep -Fq 'loads_strict_json(raw)' config/skills/_shared/verify-merge-findings.py \
354
444
  || ! grep -Fq 'invalid JSON numeric constant: NaN' config/skills/_shared/verify-merge-findings.py; then
@@ -377,9 +467,17 @@ if python3 config/skills/_shared/collect-codex-findings.py --self-test >/dev/nul
377
467
  else
378
468
  bad "collect-codex-findings.py self-test failed"
379
469
  fi
470
+ # iter-0082: the frozen collector contract — every negative through every ingress
471
+ # path, plus real judge captures replayed against hand-verified expectations.
472
+ # The self-test above is unit-shaped; this is the integration bar against real bytes.
473
+ if python3 benchmark/ceiling/probes/r-weld-0082/test-collector-contract.py >/dev/null 2>&1; then
474
+ ok "collector contract (iter-0082 negatives + real-capture corpus) passed"
475
+ else
476
+ bad "collector contract failed — run benchmark/ceiling/probes/r-weld-0082/test-collector-contract.py"
477
+ fi
380
478
  if ! grep -Fq 'def reject_json_constant' config/skills/_shared/collect-codex-findings.py \
381
479
  || ! grep -Fq 'loads_strict_json(raw)' config/skills/_shared/collect-codex-findings.py \
382
- || ! grep -Fq 'NaN Codex stdout finding must not normalize' config/skills/_shared/collect-codex-findings.py; then
480
+ || ! grep -Fq 'NaN pair-JUDGE stdout finding' config/skills/_shared/collect-codex-findings.py; then
383
481
  bad "collect-codex-findings.py must reject non-standard JSON constants in pair-JUDGE stdout"
384
482
  fi
385
483
 
@@ -396,10 +494,56 @@ if ! grep -Fq 'SAFE_RUN_ID_RE' config/skills/_shared/archive_run.py \
396
494
  || ! grep -Fq 'invalid JSON numeric constant: NaN' config/skills/_shared/archive_run.py \
397
495
  || ! grep -Fq '"verify.pair.findings.jsonl"' config/skills/_shared/archive_run.py \
398
496
  || ! grep -Fq '"verify-merge.summary.json"' config/skills/_shared/archive_run.py \
497
+ || ! grep -Fq '"surface-close.output.json"' config/skills/_shared/archive_run.py \
399
498
  || ! grep -Fq '"*-judge.*"' config/skills/_shared/archive_run.py; then
400
499
  bad "archive_run.py must safely archive pair/risk-probe evidence and reject unsafe run ids"
401
500
  fi
402
501
 
502
+ section "Check 6c2: SURFACE_CLOSE v6 dispatch is complete and mirrored"
503
+ surface_close_missing=0
504
+ if python3 config/skills/_shared/state-phase-write.py --self-test >/dev/null 2>&1; then
505
+ ok "SURFACE_CLOSE state/envelope/adjudication/audit/rollback self-test passed"
506
+ else
507
+ bad "state-phase-write.py SURFACE_CLOSE self-test failed"
508
+ fi
509
+ if ! grep -Fq 'rollback_surface_delta' config/skills/_shared/state-phase-write.py \
510
+ || ! grep -Fq 'validate_surface_adjudication' config/skills/_shared/state-phase-write.py \
511
+ || ! grep -Fq 'validate_surface_execution' config/skills/_shared/state-phase-write.py \
512
+ || ! grep -Fq 'phases.surface_close spawn requires --engine claude' config/skills/_shared/state-phase-write.py \
513
+ || ! grep -Fq 'phases.surface_close spawn requires --model' config/skills/_shared/state-phase-write.py \
514
+ || ! grep -Fq 'BLOCKED:surface-close-input-mismatch' config/skills/_shared/state-phase-write.py \
515
+ || ! grep -Fq 'surface-check' config/skills/_shared/state-phase-write.py \
516
+ || ! grep -Fq 'surface-rollback' config/skills/_shared/state-phase-write.py; then
517
+ bad "state-phase-write.py must mechanically adjudicate, audit, guard, and roll back SURFACE_CLOSE"
518
+ fi
519
+ for tree in config/skills .claude/skills .agents/skills; do
520
+ skill="$tree/devlyn:resolve/SKILL.md"
521
+ phase="$tree/devlyn:resolve/references/phases/surface-close.md"
522
+ if ! grep -Fq '## PHASE 2.5: SURFACE_CLOSE' "$skill" \
523
+ || ! grep -Fq '`state.source.type == "generated"` and complexity is trivial/medium' "$skill" \
524
+ || ! grep -Fq 'Engine is Claude always' "$skill" \
525
+ || ! grep -Fq 'executor flag/pin' "$skill" \
526
+ || ! grep -Fq 'auto_surface_close_claude_unavailable' "$skill" \
527
+ || ! grep -Fq 'canonical body VERBATIM' "$skill" \
528
+ || ! grep -Fq 'run-bounded.py 600 -- claude -p' "$skill" \
529
+ || ! grep -Fq -- '--tools "Read,Grep,Glob,Edit,Write" --dangerously-skip-permissions --model claude-sonnet-5 --output-format json --strict-mcp-config --mcp-config '\''{"mcpServers":{}}'\''' "$skill" \
530
+ || ! grep -Fq '.devlyn/surface-close.output.json' "$skill" \
531
+ || ! grep -Fq '**Common post-fix checkpoint (BUILD_GATE and VERIFY):**' "$skill" \
532
+ || ! grep -Fq 'durability-enforce --round <n> --origin-phase <build_gate|verify>' "$skill" \
533
+ || ! grep -Fq '`surface-rollback`' "$skill" \
534
+ || grep -Fq 'Supplied digests' "$phase" \
535
+ || grep -Fq 'Hash both artifacts first' "$phase" \
536
+ || ! grep -Fq 'Never modify inputs or read state, PLAN, or IMPLEMENT transcript/reasoning.' "$phase" \
537
+ || ! grep -Fq 'optionally followed by ` — <one-line evidence>`' "$phase" \
538
+ || ! grep -Fq 'N/A <authorized-file>[:<line>] — <one-line evidence-based relationship judgment>' "$phase"; then
539
+ bad "$tree — SURFACE_CLOSE v6 dispatch/timeout/adjudication/rollback contract missing"
540
+ surface_close_missing=1
541
+ fi
542
+ done
543
+ if [ $surface_close_missing -eq 0 ]; then
544
+ ok "SURFACE_CLOSE v6 dispatch/timeout/adjudication/rollback contract is mirrored"
545
+ fi
546
+
403
547
  section "Check 6d: Spec verification executes hidden-blind risk probes"
404
548
  if python3 config/skills/_shared/spec-verify-check.py --self-test >/dev/null 2>&1; then
405
549
  ok "spec-verify-check.py risk-probe and expected-contract self-test passed"
@@ -647,13 +791,16 @@ fi
647
791
  if ! grep -Fq '"uses_visible_input_key_names"' config/skills/_shared/spec-verify-check.py \
648
792
  || ! grep -Fq '"asserts_visible_output_key_names"' config/skills/_shared/spec-verify-check.py \
649
793
  || ! grep -Fq '"asserts_no_unexpected_output_keys"' config/skills/_shared/spec-verify-check.py \
650
- || ! grep -Fq 'shape_contract co-occurring with error_contract without' config/skills/_shared/spec-verify-check.py \
794
+ || ! grep -Fq 'shape_contract claiming visible exact error object without' config/skills/_shared/spec-verify-check.py \
795
+ || ! grep -Fq 'visible_text_names_exact_json_error_object' config/skills/_shared/spec-verify-check.py \
651
796
  || ! grep -Fq 'JSON error object shape_contract with exact object evidence was rejected' config/skills/_shared/spec-verify-check.py \
652
797
  || ! grep -Fq 'shape_contract without any evidence was accepted' config/skills/_shared/spec-verify-check.py \
653
798
  || ! grep -Fq 'shape_contract with exact key evidence was rejected' config/skills/_shared/spec-verify-check.py \
654
799
  || ! grep -Fq '`shape_contract` when the visible text names exact keys' config/skills/devlyn:resolve/references/phases/probe-derive.md \
655
- || ! grep -Fq '`shape_contract` must' config/skills/devlyn:resolve/SKILL.md; then
656
- bad "risk-probe shape contracts must require exact visible input/output key evidence unconditionally, and asserts_exact_error_object when co-occurring with error_contract/http_error_contract"
800
+ || ! grep -Fq 'visible_text_names_exact_json_error_object' config/skills/devlyn:resolve/references/phases/probe-derive.md \
801
+ || ! grep -Fq '`shape_contract` must' config/skills/devlyn:resolve/SKILL.md \
802
+ || ! grep -Fq 'visible_text_names_exact_json_error_object' config/skills/devlyn:resolve/SKILL.md; then
803
+ bad "risk-probe shape contracts must require exact visible input/output key evidence unconditionally, and asserts_exact_error_object when the probe claims the visible text names an exact JSON error object"
657
804
  fi
658
805
  if ! grep -Fq '"auth_signature_contract"' config/skills/_shared/spec-verify-check.py \
659
806
  || ! grep -Fq '"idempotency_replay"' config/skills/_shared/spec-verify-check.py \
@@ -960,7 +1107,7 @@ fi
960
1107
  if ! grep -Fq 'per-feature Verification must include a solo-headroom' config/skills/devlyn:ideate/references/project-mode.md \
961
1108
  || ! grep -Fq 'per-feature Verification must include a solo-headroom' .claude/skills/devlyn:ideate/references/project-mode.md \
962
1109
  || ! grep -Fq 'per-feature Verification must include a solo-headroom' .agents/skills/devlyn:ideate/references/project-mode.md \
963
- || rg -q 'Context or Verification' config/skills/devlyn:ideate/references .claude/skills/devlyn:ideate/references .agents/skills/devlyn:ideate/references; then
1110
+ || grep -rq 'Context or Verification' config/skills/devlyn:ideate/references .claude/skills/devlyn:ideate/references .agents/skills/devlyn:ideate/references; then
964
1111
  bad "ideate project mode must keep solo-headroom hypothesis inside each pair-candidate feature spec"
965
1112
  else
966
1113
  ok "ideate project mode keeps solo-headroom hypothesis inside each pair-candidate feature spec"
@@ -1078,38 +1225,39 @@ if ! grep -Fq 'If the spec includes a solo-headroom hypothesis, one of the two t
1078
1225
  else
1079
1226
  ok "resolve pair-JUDGE prompts prioritize solo-headroom hypothesis"
1080
1227
  fi
1081
- if ! grep -Fq 'names the primary judge; if pair-mode triggers, the first available OTHER' config/skills/devlyn:resolve/references/phases/verify.md \
1082
- || ! grep -Fq 'names the primary judge; if pair-mode triggers, the first available OTHER' .claude/skills/devlyn:resolve/references/phases/verify.md \
1083
- || ! grep -Fq 'names the primary judge; if pair-mode triggers, the first available OTHER' .agents/skills/devlyn:resolve/references/phases/verify.md \
1084
- || ! grep -Fq 'do not run the OTHER engine as the' config/skills/devlyn:resolve/SKILL.md \
1085
- || ! grep -Fq 'do not run the OTHER engine as the' .claude/skills/devlyn:resolve/SKILL.md \
1086
- || ! grep -Fq 'do not run the OTHER engine as the' .agents/skills/devlyn:resolve/SKILL.md \
1087
- || ! grep -Fq '_shared/adapters/claude.md` `## Invocation' config/skills/devlyn:resolve/references/phases/verify.md \
1088
- || ! grep -Fq '_shared/adapters/claude.md` `## Invocation' config/skills/devlyn:resolve/SKILL.md \
1228
+ if ! grep -Fq 'The `--engine` flag does not disable default pairing' config/skills/devlyn:resolve/references/phases/verify.md \
1229
+ || ! grep -Fq 'The `--engine` flag does not disable default pairing' .claude/skills/devlyn:resolve/references/phases/verify.md \
1230
+ || ! grep -Fq 'The `--engine` flag does not disable default pairing' .agents/skills/devlyn:resolve/references/phases/verify.md \
1231
+ || ! grep -Fq 'the second judge uses the OTHER engine by default when available' config/skills/devlyn:resolve/SKILL.md \
1232
+ || ! grep -Fq 'the second judge uses the OTHER engine by default when available' .claude/skills/devlyn:resolve/SKILL.md \
1233
+ || ! grep -Fq 'the second judge uses the OTHER engine by default when available' .agents/skills/devlyn:resolve/SKILL.md \
1234
+ || ! grep -Fq '_shared/adapters/<name>.md' config/skills/devlyn:resolve/references/phases/verify.md \
1235
+ || ! grep -Fq '`## Invocation`' config/skills/devlyn:resolve/references/phases/verify.md \
1236
+ || ! grep -Fq '_shared/adapters/<name>.md` `## Invocation' config/skills/devlyn:resolve/SKILL.md \
1089
1237
  || ! grep -Fq '## Invocation' config/skills/_shared/adapters/claude.md \
1090
1238
  || ! grep -Fq '.devlyn/claude-judge.stdout' config/skills/_shared/adapters/claude.md; then
1091
1239
  bad "engine-neutral pair-judge dual declaration (iter-0060) out of sync"
1092
1240
  else
1093
1241
  ok "engine-neutral pair-judge dual declaration (iter-0060) in sync"
1094
1242
  fi
1095
- if ! grep -Fq 'current free-form `state.complexity` is `"large"`' config/skills/devlyn:resolve/SKILL.md \
1096
- || ! grep -Fq 'current free-form `state.complexity` is `"large"`' .claude/skills/devlyn:resolve/SKILL.md \
1097
- || ! grep -Fq 'current free-form `state.complexity` is `"large"`' .agents/skills/devlyn:resolve/SKILL.md \
1098
- || ! grep -Fq 'legacy/external spec `complexity: large` is accepted for compatibility' config/skills/devlyn:resolve/SKILL.md \
1099
- || ! grep -Fq 'legacy/external spec `complexity: large` is accepted for compatibility' .claude/skills/devlyn:resolve/SKILL.md \
1100
- || ! grep -Fq 'legacy/external spec `complexity: large` is accepted for compatibility' .agents/skills/devlyn:resolve/SKILL.md \
1101
- || ! grep -Fq 'current free-form `state.complexity` of `"large"`' config/skills/devlyn:resolve/references/state-schema.md \
1102
- || ! grep -Fq 'current free-form `state.complexity` of `"large"`' .claude/skills/devlyn:resolve/references/state-schema.md \
1103
- || ! grep -Fq 'current free-form `state.complexity` of `"large"`' .agents/skills/devlyn:resolve/references/state-schema.md \
1104
- || ! grep -Fq 'Legacy/external spec `complexity: large` remains accepted for compatibility' config/skills/devlyn:resolve/references/state-schema.md \
1105
- || ! grep -Fq 'Legacy/external spec `complexity: large` remains accepted for compatibility' .claude/skills/devlyn:resolve/references/state-schema.md \
1106
- || ! grep -Fq 'Legacy/external spec `complexity: large` remains accepted for compatibility' .agents/skills/devlyn:resolve/references/state-schema.md \
1107
- || ! grep -Fq 'Current free-form `state.complexity` is `"large"`' config/skills/devlyn:resolve/references/phases/verify.md \
1108
- || ! grep -Fq 'Current free-form `state.complexity` is `"large"`' .claude/skills/devlyn:resolve/references/phases/verify.md \
1109
- || ! grep -Fq 'Current free-form `state.complexity` is `"large"`' .agents/skills/devlyn:resolve/references/phases/verify.md \
1110
- || ! grep -Fq 'legacy/external spec' config/skills/devlyn:resolve/references/phases/verify.md \
1111
- || ! grep -Fq 'legacy/external spec' .claude/skills/devlyn:resolve/references/phases/verify.md \
1112
- || ! grep -Fq 'legacy/external spec' .agents/skills/devlyn:resolve/references/phases/verify.md; then
1243
+ if ! grep -Fq '`complexity.high`, `complexity.large`' config/skills/devlyn:resolve/SKILL.md \
1244
+ || ! grep -Fq '`complexity.high`, `complexity.large`' .claude/skills/devlyn:resolve/SKILL.md \
1245
+ || ! grep -Fq '`complexity.high`, `complexity.large`' .agents/skills/devlyn:resolve/SKILL.md \
1246
+ || ! grep -Fq '`spec.complexity.high`, `spec.complexity.large`' config/skills/devlyn:resolve/SKILL.md \
1247
+ || ! grep -Fq '`spec.complexity.high`, `spec.complexity.large`' .claude/skills/devlyn:resolve/SKILL.md \
1248
+ || ! grep -Fq '`spec.complexity.high`, `spec.complexity.large`' .agents/skills/devlyn:resolve/SKILL.md \
1249
+ || ! grep -Fq 'Legacy complexity values remain accepted only for archived compatibility' config/skills/devlyn:resolve/references/state-schema.md \
1250
+ || ! grep -Fq 'Legacy complexity values remain accepted only for archived compatibility' .claude/skills/devlyn:resolve/references/state-schema.md \
1251
+ || ! grep -Fq 'Legacy complexity values remain accepted only for archived compatibility' .agents/skills/devlyn:resolve/references/state-schema.md \
1252
+ || ! grep -Fq '`complexity.high`, `complexity.large`, `spec.complexity.high`, `spec.complexity.large`' config/skills/devlyn:resolve/references/state-schema.md \
1253
+ || ! grep -Fq '`complexity.high`, `complexity.large`, `spec.complexity.high`, `spec.complexity.large`' .claude/skills/devlyn:resolve/references/state-schema.md \
1254
+ || ! grep -Fq '`complexity.high`, `complexity.large`, `spec.complexity.high`, `spec.complexity.large`' .agents/skills/devlyn:resolve/references/state-schema.md \
1255
+ || ! grep -Fq '`complexity.large`, `spec.complexity.high`, `spec.complexity.large`,' config/skills/devlyn:resolve/references/phases/verify.md \
1256
+ || ! grep -Fq '`complexity.large`, `spec.complexity.high`, `spec.complexity.large`,' .claude/skills/devlyn:resolve/references/phases/verify.md \
1257
+ || ! grep -Fq '`complexity.large`, `spec.complexity.high`, `spec.complexity.large`,' .agents/skills/devlyn:resolve/references/phases/verify.md \
1258
+ || ! grep -Fq '`spec.solo_headroom_hypothesis`, `risk.high`, `risk_probes.enabled`,' config/skills/devlyn:resolve/references/phases/verify.md \
1259
+ || ! grep -Fq '`spec.solo_headroom_hypothesis`, `risk.high`, `risk_probes.enabled`,' .claude/skills/devlyn:resolve/references/phases/verify.md \
1260
+ || ! grep -Fq '`spec.solo_headroom_hypothesis`, `risk.high`, `risk_probes.enabled`,' .agents/skills/devlyn:resolve/references/phases/verify.md; then
1113
1261
  bad "resolve VERIFY docs must distinguish current large complexity, legacy high state, and legacy large spec compatibility"
1114
1262
  else
1115
1263
  ok "resolve VERIFY docs distinguish current large complexity, legacy high state, and legacy large spec compatibility"
@@ -1165,15 +1313,15 @@ if ! grep -Fq 'def spec_has_solo_headroom_hypothesis' config/skills/_shared/veri
1165
1313
  || ! grep -Fq 'spec_has_solo_headroom_hypothesis(' .agents/skills/_shared/verify-merge-findings.py \
1166
1314
  || ! grep -Fq ') is False' .agents/skills/_shared/verify-merge-findings.py \
1167
1315
  || ! grep -Fq ') is True' .agents/skills/_shared/verify-merge-findings.py \
1168
- || ! grep -Fq 'The spec includes an actionable solo-headroom hypothesis.' \
1316
+ || ! grep -Fq '`spec.solo_headroom_hypothesis`' \
1169
1317
  config/skills/devlyn:resolve/references/phases/verify.md \
1170
- || ! grep -Fq 'The spec includes an actionable solo-headroom hypothesis.' \
1318
+ || ! grep -Fq '`spec.solo_headroom_hypothesis`' \
1171
1319
  .claude/skills/devlyn:resolve/references/phases/verify.md \
1172
- || ! grep -Fq 'The spec includes an actionable solo-headroom hypothesis.' \
1320
+ || ! grep -Fq '`spec.solo_headroom_hypothesis`' \
1173
1321
  .agents/skills/devlyn:resolve/references/phases/verify.md \
1174
- || ! grep -Fq 'actionable solo-headroom hypotheses' config/skills/devlyn:resolve/references/state-schema.md \
1175
- || ! grep -Fq 'actionable solo-headroom hypotheses' .claude/skills/devlyn:resolve/references/state-schema.md \
1176
- || ! grep -Fq 'actionable solo-headroom hypotheses' .agents/skills/devlyn:resolve/references/state-schema.md \
1322
+ || ! grep -Fq '`spec.solo_headroom_hypothesis`' config/skills/devlyn:resolve/references/state-schema.md \
1323
+ || ! grep -Fq '`spec.solo_headroom_hypothesis`' .claude/skills/devlyn:resolve/references/state-schema.md \
1324
+ || ! grep -Fq '`spec.solo_headroom_hypothesis`' .agents/skills/devlyn:resolve/references/state-schema.md \
1177
1325
  || ! grep -Fq 'same actionable solo-headroom hypothesis is a VERIFY pair-trigger reason' config/skills/devlyn:resolve/SKILL.md \
1178
1326
  || ! grep -Fq 'same actionable solo-headroom hypothesis is a VERIFY pair-trigger reason' .claude/skills/devlyn:resolve/SKILL.md \
1179
1327
  || ! grep -Fq 'same actionable solo-headroom hypothesis is a VERIFY pair-trigger reason' .agents/skills/devlyn:resolve/SKILL.md; then
@@ -1199,9 +1347,9 @@ if ! grep -Fq 'pair_evidence_intent' config/skills/devlyn:resolve/references/fre
1199
1347
  || ! grep -Fq 'state.source.criteria_sha256` for generated free-form mode' config/skills/devlyn:resolve/references/phases/verify.md \
1200
1348
  || ! grep -Fq 'state.source.criteria_sha256` for generated free-form mode' .claude/skills/devlyn:resolve/references/phases/verify.md \
1201
1349
  || ! grep -Fq 'state.source.criteria_sha256` for generated free-form mode' .agents/skills/devlyn:resolve/references/phases/verify.md \
1202
- || ! grep -Fq 'Free-form mode sets `type: "generated"`' config/skills/devlyn:resolve/references/state-schema.md \
1203
- || ! grep -Fq 'Free-form mode sets `type: "generated"`' .claude/skills/devlyn:resolve/references/state-schema.md \
1204
- || ! grep -Fq 'Free-form mode sets `type: "generated"`' .agents/skills/devlyn:resolve/references/state-schema.md \
1350
+ || ! grep -Fq 'Free-form sets `type: "generated"`' config/skills/devlyn:resolve/references/state-schema.md \
1351
+ || ! grep -Fq 'Free-form sets `type: "generated"`' .claude/skills/devlyn:resolve/references/state-schema.md \
1352
+ || ! grep -Fq 'Free-form sets `type: "generated"`' .agents/skills/devlyn:resolve/references/state-schema.md \
1205
1353
  || ! grep -Fq 'backticked observable command line that itself contains `miss`' config/skills/devlyn:resolve/references/free-form-mode.md \
1206
1354
  || ! grep -Fq 'backticked observable command line that itself contains `miss`' .claude/skills/devlyn:resolve/references/free-form-mode.md \
1207
1355
  || ! grep -Fq 'backticked observable command line that itself contains `miss`' .agents/skills/devlyn:resolve/references/free-form-mode.md \
@@ -1311,19 +1459,21 @@ if [ $verify_mech_missing -eq 0 ]; then
1311
1459
  ok "spec-verify VERIFY output routes into verify-merge-findings.py"
1312
1460
  fi
1313
1461
 
1314
- section "Check 6j: VERIFY pair trigger runs after primary JUDGE"
1462
+ section "Check 6j: VERIFY default pair dispatches concurrently"
1315
1463
  pair_trigger_order_missing=0
1316
1464
  for file in \
1317
1465
  config/skills/devlyn:resolve/SKILL.md \
1318
1466
  .claude/skills/devlyn:resolve/SKILL.md \
1319
1467
  .agents/skills/devlyn:resolve/SKILL.md
1320
1468
  do
1321
- if ! grep -Fq 'Pair-mode (cross-model JUDGE) is eligible only after MECHANICAL and the primary JUDGE have no verdict-binding findings' "$file" \
1322
- || ! grep -Fq 'After MECHANICAL and the primary JUDGE finish, compute `pair_trigger' "$file" \
1323
- || ! grep -Fq '`risk_profile` is strict typed state' "$file" \
1324
- || ! grep -Fq 'malformed `risk_profile` is also a VERIFY contract violation' "$file" \
1325
- || ! grep -Fq 'If MECHANICAL or the primary JUDGE has a verdict-binding finding' "$file"; then
1326
- bad "$file — VERIFY pair trigger order must be after primary JUDGE"
1469
+ if ! grep -Fq 'Verify dual-judge is default-when-available' "$file" \
1470
+ || ! grep -Fq 'persist `pair_trigger` before spawn with `pair.default`' "$file" \
1471
+ || ! grep -Fq 'dispatch both judges concurrently against the same frozen diff' "$file" \
1472
+ || ! grep -Fq 'A primary blocker never cancels the pair judge' "$file" \
1473
+ || ! grep -Fq 'run the same two required judges sequentially' "$file" \
1474
+ || ! grep -Fq 'Eligible schema-v3 state must include `pair.default`' "$file" \
1475
+ || ! grep -Fq '`primary_judge_blocker` is parser-recognized only for archived v2.0 replay; new runs never write it' "$file"; then
1476
+ bad "$file — VERIFY default pair rule missing."
1327
1477
  pair_trigger_order_missing=1
1328
1478
  fi
1329
1479
  done
@@ -1332,11 +1482,14 @@ for file in \
1332
1482
  .claude/skills/devlyn:resolve/references/phases/verify.md \
1333
1483
  .agents/skills/devlyn:resolve/references/phases/verify.md
1334
1484
  do
1335
- if ! grep -Fq 'Pair-mode is eligible only after MECHANICAL and the primary JUDGE have no' "$file" \
1336
- || ! grep -Fq 'After MECHANICAL and the primary JUDGE finish, compute and persist this before' "$file" \
1337
- || ! grep -Fq 'Malformed `state.risk_profile` is a VERIFY contract violation' "$file" \
1338
- || ! grep -Fq 'primary_judge_blocker' "$file"; then
1339
- bad "$file — VERIFY phase body must compute pair_trigger after primary JUDGE"
1485
+ if ! grep -Fq 'Pair-mode (default when OTHER engine is available)' "$file" \
1486
+ || ! grep -Fq 'with `pair.default` plus every applicable' "$file" \
1487
+ || ! grep -Fq 'dispatch the primary and pair JUDGEs concurrently against the same frozen' "$file" \
1488
+ || ! grep -Fq 'A primary blocker does not cancel the pair-JUDGE' "$file" \
1489
+ || ! grep -Fq 'runs the same two required judges' "$file" \
1490
+ || ! grep -Fq 'Eligible schema-v3 state must contain `pair.default`' "$file" \
1491
+ || ! grep -Fq 'New runs never' "$file"; then
1492
+ bad "$file — VERIFY default pair dispatch missing"
1340
1493
  pair_trigger_order_missing=1
1341
1494
  fi
1342
1495
  done
@@ -1345,16 +1498,50 @@ for file in \
1345
1498
  .claude/skills/devlyn:resolve/references/state-schema.md \
1346
1499
  .agents/skills/devlyn:resolve/references/state-schema.md
1347
1500
  do
1348
- if ! grep -Fq 'MECHANICAL and the primary JUDGE have no verdict-binding blockers' "$file" \
1349
- || ! grep -Fq 'may set only `user_no_pair`, `mechanical_blocker`, `primary_judge_blocker`, `auto_pair_other_engine_unavailable`, or null' "$file" \
1350
- || ! grep -Fq '`risk_profile` must remain an object with boolean' "$file" \
1351
- || ! grep -Fq 'state implies a pair decision is required but `pair_trigger` is missing' "$file"; then
1352
- bad "$file — state schema must document pair_trigger blocker and missing-trigger enforcement"
1501
+ if ! grep -Fq '"version": "3.0"' "$file" \
1502
+ || ! grep -Fq 'pair.default` dispatches both judges concurrently' "$file" \
1503
+ || ! grep -Fq 'runs the same two required judges sequentially' "$file" \
1504
+ || ! grep -Fq 'a primary blocker never skips the pair' "$file" \
1505
+ || ! grep -Fq 'Schema v3.0 eligible state requires `pair.default`' "$file" \
1506
+ || ! grep -Fq 'archived v2.0 replay' "$file" \
1507
+ || ! grep -Fq 'Missing, contradictory, incomplete, or unknown trigger state BLOCKs VERIFY' "$file"; then
1508
+ bad "$file — schema-v3 default pair contract missing"
1353
1509
  pair_trigger_order_missing=1
1354
1510
  fi
1355
1511
  done
1356
1512
  if [ $pair_trigger_order_missing -eq 0 ]; then
1357
- ok "VERIFY pair trigger order waits for primary JUDGE evidence"
1513
+ ok "VERIFY default pair dispatches concurrently"
1514
+ fi
1515
+
1516
+ section "Check 6k: PLAN route is orchestrator-fixed"
1517
+ plan_route_ok=1
1518
+ for plan_route_file in "config/skills/devlyn:resolve/SKILL.md" CLAUDE.md AGENTS.md \
1519
+ "config/skills/devlyn:engines/SKILL.md" ".agents/skills/devlyn:resolve/SKILL.md" \
1520
+ ".agents/skills/devlyn:engines/SKILL.md"; do
1521
+ [ -f "$plan_route_file" ] || plan_route_ok=0
1522
+ done
1523
+ sed -n '/^<engine_routing>$/,/^<\/engine_routing>$/p' "config/skills/devlyn:resolve/SKILL.md" \
1524
+ | grep -Fq 'PLAN is orchestrator-fixed and never inherits `--engine`, an executor pin, or `state.engine`' || plan_route_ok=0
1525
+ sed -n '/^## PHASE 1: PLAN$/,/^## PHASE 1.5/p' "config/skills/devlyn:resolve/SKILL.md" \
1526
+ | grep -Fq 'PLAN is orchestrator-fixed and never inherits `--engine`, an executor pin, or `state.engine`' || plan_route_ok=0
1527
+ grep -Fq 'PLAN is orchestrator-fixed and never inherits `--engine` or an executor pin' CLAUDE.md || plan_route_ok=0
1528
+ grep -Fq 'PLAN is orchestrator-fixed and never inherits `--engine` or the executor pin' AGENTS.md || plan_route_ok=0
1529
+ grep -Fq 'PLAN is orchestrator-fixed and never follows the pin' config/skills/devlyn:engines/SKILL.md || plan_route_ok=0
1530
+ if grep -En '([Ee]xecutor|Default engine: Claude for)[^[:cntrl:]]{0,80}PLAN[[:space:]]*/[[:space:]]*IMPLEMENT([[:space:]]*/[[:space:]]*BUILD_GATE)?[[:space:]]*/[[:space:]]*CLEANUP' \
1531
+ config/skills/devlyn:resolve/SKILL.md \
1532
+ CLAUDE.md \
1533
+ AGENTS.md \
1534
+ README.md \
1535
+ config/skills/devlyn:engines/SKILL.md \
1536
+ .agents/skills/devlyn:resolve/SKILL.md \
1537
+ .agents/skills/devlyn:engines/SKILL.md \
1538
+ >/dev/null 2>&1; then
1539
+ plan_route_ok=0
1540
+ fi
1541
+ if [ $plan_route_ok -eq 1 ]; then
1542
+ ok "PLAN is orchestrator-fixed: exact declarations locked on all surfaces, no executor-role enumeration"
1543
+ else
1544
+ bad "PLAN route contract violated: a locked orchestrator-fixed declaration is missing or an executor-role enumeration re-teaches PLAN"
1358
1545
  fi
1359
1546
 
1360
1547
  section "Check 6h: No undocumented spec.expected.json.browser_flows field"
@@ -3444,8 +3631,9 @@ else
3444
3631
  offenders="${offenders}"$'\n'"packaged pair evidence subset could not allocate temporary audit workspace"
3445
3632
  fi
3446
3633
  if make_temp_file pack_json /tmp/devlyn-lint-pack.XXXXXX.json \
3447
- && npm pack --dry-run --json > "$pack_json" 2>/dev/null; then
3448
- if ! node - "$pack_json" <<'NODE'
3634
+ && make_temp_dir pack_cache /tmp/devlyn-lint-pack-cache.XXXXXX; then
3635
+ if npm_config_cache="$pack_cache" npm pack --dry-run --json > "$pack_json" 2>/dev/null; then
3636
+ if ! node - "$pack_json" <<'NODE'
3449
3637
  const fs = require("fs");
3450
3638
  const path = require("path");
3451
3639
  const packPath = process.argv[2];
@@ -3531,13 +3719,17 @@ if (forbidden.length > 0) {
3531
3719
  process.exit(2);
3532
3720
  }
3533
3721
  NODE
3534
- then
3535
- offenders="${offenders}"$'\n'"npm pack dry-run must include benchmark runner/gate regression tests, all shadow fixture files, retired fixture replay docs, and exclude pycache artifacts"
3722
+ then
3723
+ offenders="${offenders}"$'\n'"npm pack dry-run must include benchmark runner/gate regression tests, all shadow fixture files, retired fixture replay docs, and exclude pycache artifacts"
3724
+ fi
3725
+ else
3726
+ offenders="${offenders}"$'\n'"npm pack dry-run failed while checking benchmark runner/gate regression tests"
3536
3727
  fi
3537
3728
  else
3538
- offenders="${offenders}"$'\n'"npm pack dry-run failed while checking benchmark runner/gate regression tests"
3729
+ offenders="${offenders}"$'\n'"npm pack dry-run could not allocate temporary package workspace"
3539
3730
  fi
3540
3731
  [ -n "${pack_json:-}" ] && rm -f "$pack_json"
3732
+ [ -n "${pack_cache:-}" ] && rm -rf "$pack_cache"
3541
3733
  non_executable_shell_scripts=$(find benchmark/auto-resolve/scripts -maxdepth 1 -name '*.sh' -type f ! -perm -111 -print | sort)
3542
3734
  if [ -n "$non_executable_shell_scripts" ]; then
3543
3735
  while IFS= read -r f; do
@@ -3694,7 +3886,7 @@ if ! grep -Fq 'high-risk fixture must include a resolve risk-trigger term' scrip
3694
3886
  || ! grep -Fq 'expected.json must be an object' scripts/lint-fixtures.sh \
3695
3887
  || ! grep -Fq 'schema_ok=0' scripts/lint-fixtures.sh \
3696
3888
  || ! grep -Fq 'DEVLYN_LINT_FIXTURES_NO_JSONSCHEMA' scripts/lint-fixtures.sh \
3697
- || ! grep -Fq 'conditional pair/risk-probe triggers' benchmark/auto-resolve/fixtures/SCHEMA.md; then
3889
+ || ! grep -Fq 'conditional risk-probe triggers' benchmark/auto-resolve/fixtures/SCHEMA.md; then
3698
3890
  offenders="${offenders}"$'\n'"fixture lint must require high-risk fixtures to include resolve pair/risk trigger terms"
3699
3891
  fi
3700
3892
  unsafe_json_parser_refs=$(python3 - <<'PY'