@dzhechkov/harness-core 0.4.4 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/.dz-manifest.json +283 -103
  2. package/LICENSE +21 -0
  3. package/README.md +73 -5
  4. package/dist/agents-policy.d.ts +67 -0
  5. package/dist/agents-policy.d.ts.map +1 -0
  6. package/dist/agents-policy.js +258 -0
  7. package/dist/agents-policy.js.map +1 -0
  8. package/dist/codex-hooks-assets.d.ts +47 -0
  9. package/dist/codex-hooks-assets.d.ts.map +1 -0
  10. package/dist/codex-hooks-assets.js +287 -0
  11. package/dist/codex-hooks-assets.js.map +1 -0
  12. package/dist/codex-hooks-verify.d.ts +74 -0
  13. package/dist/codex-hooks-verify.d.ts.map +1 -0
  14. package/dist/codex-hooks-verify.js +140 -0
  15. package/dist/codex-hooks-verify.js.map +1 -0
  16. package/dist/codex-hooks.d.ts +258 -0
  17. package/dist/codex-hooks.d.ts.map +1 -0
  18. package/dist/codex-hooks.js +391 -0
  19. package/dist/codex-hooks.js.map +1 -0
  20. package/dist/discrimination-gate.d.ts +88 -15
  21. package/dist/discrimination-gate.d.ts.map +1 -1
  22. package/dist/discrimination-gate.js +343 -51
  23. package/dist/discrimination-gate.js.map +1 -1
  24. package/dist/feature-adr-checkpoints.d.ts +22 -0
  25. package/dist/feature-adr-checkpoints.d.ts.map +1 -1
  26. package/dist/feature-adr-checkpoints.js +42 -0
  27. package/dist/feature-adr-checkpoints.js.map +1 -1
  28. package/dist/feature-adr-routing.d.ts +196 -5
  29. package/dist/feature-adr-routing.d.ts.map +1 -1
  30. package/dist/feature-adr-routing.js +538 -54
  31. package/dist/feature-adr-routing.js.map +1 -1
  32. package/dist/guard.d.ts +13 -0
  33. package/dist/guard.d.ts.map +1 -1
  34. package/dist/guard.js +25 -1
  35. package/dist/guard.js.map +1 -1
  36. package/dist/index.d.ts +17 -7
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +23 -4
  39. package/dist/index.js.map +1 -1
  40. package/dist/loop-blobs.generated.js +2 -2
  41. package/dist/loop-blobs.generated.js.map +1 -1
  42. package/dist/managed-hooks.d.ts +76 -0
  43. package/dist/managed-hooks.d.ts.map +1 -0
  44. package/dist/managed-hooks.js +89 -0
  45. package/dist/managed-hooks.js.map +1 -0
  46. package/dist/mutation-gate.d.ts +14 -0
  47. package/dist/mutation-gate.d.ts.map +1 -1
  48. package/dist/mutation-gate.js +25 -2
  49. package/dist/mutation-gate.js.map +1 -1
  50. package/dist/operations.d.ts +153 -0
  51. package/dist/operations.d.ts.map +1 -1
  52. package/dist/operations.js +560 -24
  53. package/dist/operations.js.map +1 -1
  54. package/dist/parity.d.ts +38 -1
  55. package/dist/parity.d.ts.map +1 -1
  56. package/dist/parity.js +78 -5
  57. package/dist/parity.js.map +1 -1
  58. package/dist/recall-usage.d.ts +53 -0
  59. package/dist/recall-usage.d.ts.map +1 -1
  60. package/dist/recall-usage.js +125 -2
  61. package/dist/recall-usage.js.map +1 -1
  62. package/dist/setup.d.ts.map +1 -1
  63. package/dist/setup.js +14 -26
  64. package/dist/setup.js.map +1 -1
  65. package/dist/shell-veto-policy.d.ts +53 -0
  66. package/dist/shell-veto-policy.d.ts.map +1 -0
  67. package/dist/shell-veto-policy.js +103 -0
  68. package/dist/shell-veto-policy.js.map +1 -0
  69. package/dist/skills.d.ts +86 -1
  70. package/dist/skills.d.ts.map +1 -1
  71. package/dist/skills.js +116 -1
  72. package/dist/skills.js.map +1 -1
  73. package/dist/targets.d.ts +75 -0
  74. package/dist/targets.d.ts.map +1 -1
  75. package/dist/targets.js +160 -0
  76. package/dist/targets.js.map +1 -1
  77. package/package.json +20 -19
  78. package/sbom.json +552 -102
  79. package/src/agents-policy.ts +338 -0
  80. package/src/codex-hooks-assets.ts +291 -0
  81. package/src/codex-hooks-verify.ts +184 -0
  82. package/src/codex-hooks.ts +571 -0
  83. package/src/discrimination-gate.ts +456 -58
  84. package/src/feature-adr-checkpoints.ts +38 -0
  85. package/src/feature-adr-routing.ts +642 -75
  86. package/src/guard.ts +36 -1
  87. package/src/index.ts +118 -2
  88. package/src/loop-blobs.generated.ts +2 -2
  89. package/src/managed-hooks.ts +129 -0
  90. package/src/mutation-gate.ts +24 -2
  91. package/src/operations.ts +719 -28
  92. package/src/parity.ts +120 -6
  93. package/src/recall-usage.ts +184 -1
  94. package/src/setup.ts +26 -27
  95. package/src/shell-veto-policy.ts +119 -0
  96. package/src/skills.ts +174 -1
  97. package/src/targets.ts +189 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 dzhechko
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -7,9 +7,10 @@ and any other consumer.
7
7
 
8
8
  | Module | Exports | Purpose |
9
9
  |---|---|---|
10
- | `skills` | `loadSkillFromDir`, `listSkills`, `discoverSkillIds` | Read skill directories into `CanonicalSkill` objects |
10
+ | `skills` | `loadSkillFromDir`, `listSkills`, `listSkillsDetailed`, `describeSkillLoadFailure`, `formatSkillLoadFailures`, `formatSkillApplyFailures`, `discoverSkillIds` | Read skill directories into `CanonicalSkill` objects. **Two listing functions, deliberately:** `listSkills` THROWS on the first unloadable skill and always will — it is a published export, and silently turning it into a skip-and-collect function would downgrade every unknown third-party consumer from fail-closed to fail-silent without their consent (an incomplete catalogue reported as complete); a pinned regression test asserts it still throws. `listSkillsDetailed` is the total variant callers ask for BY NAME: it returns `{skills, failures}` with a per-id `try/catch`, so one unparseable `SKILL.md` never hides the ones after it (order-independence is the tested property — the offender first, middle or last yields the same counts). Every failure is NAMED — `describeSkillLoadFailure` is the single place a pathless parser throw becomes `{id, absolute path, verbatim reason, first line}`, because the parser is handed only TEXT and can never supply a path. `formatSkillLoadFailures` renders that list for stderr in one of two modes chosen by the CALLER (absolute paths for `dz list`/`dz sync`; relative-to-package for `dz install`, where a `node_modules/**` path is not actionable) |
11
11
  | `apply` | `applyEmitResult` | Write an adapter `EmitResult` to disk — **additively** |
12
- | `targets` | `TARGETS`, `TargetName`, `isTargetName` | `--target` name → platform adapter |
12
+ | `targets` | `TARGETS`, `TargetName`, `isTargetName`, `resolveTargetName`, `TARGET_ALIASES`, `TARGET_NAMES_SORTED`, `formatTargetProblem`, `formatTargetAliasNote`, `normalizeTargetToken` | `--target` name → platform adapter, plus the resolution layer in front of it. `isTargetName`/`TARGETS`/`TARGET_NAMES` are UNCHANGED: `boundaries.json` names `isTargetName` as the scanned `--target` validation boundary, and every resolution ends in exactly that guard — the boundary is routed THROUGH, never relocated. `resolveTargetName` is total and pure, with fixed precedence: exact canonical → normalised canonical (case/padding/separators: `Claude_Code`, `claudecode`) → an explicit `TARGET_ALIASES` row → unique normalised prefix → Levenshtein ≤ 3 strictly better than the runner-up → nothing. **Aliases ACCEPT; prefix and Levenshtein only SUGGEST** — an alias row is an owner decision recorded in DATA (adding one is one line and zero control flow), while a fuzzy match is a guess, and installing to the wrong target on a guess is worse than one round-trip. An ambiguous prefix (`co` → `codex`/`copilot`) is terminal with NO suggestion, for the same reason. `formatTargetProblem` renders the two-line refusal, keeping the literal `--target must be one of:` substring that shipped assertions pin |
13
+ | `agents-policy` | `POLICY_SOURCES`, `extractPolicyBlocks`, `renderPolicySections`, `detectPolicyDrift`, `measureAgentsMdBudget` | Pure anchored policy extraction, 12-hex source stamps, drift classification and Codex project-doc byte-budget measurement. The stamps prove source/target synchronization only; they do not prove that a runtime read or obeyed the text |
13
14
  | `sign` (walks) | `listPackFiles`, `listSignablePackFiles` | Shared node_modules/.git exclusions; verify sees MORE than sign (smuggled symlinks still fail) |
14
15
  | `guard` | `evaluateGuard`, `resolveRules`, `scanSecrets`, `DEFAULT_RULES`, `parsePnpmLockImporters` | Declarative HARD/SOFT constraint engine behind `dz guard` (publish/teach/consolidate pre-flight; fail-closed). The SOFT `lockfile-in-sync` rule compares each workspace package's `@dzhechkov/*` dep specs against the specifier `pnpm-lock.yaml` records for that importer — the `ERR_PNPM_OUTDATED_LOCKFILE` CI break, caught at publish. Its lockfile reader (`parsePnpmLockImporters`) is a pure RECOGNISE-OR-REFUSE parser (no YAML dependency): it reads only the `lockfileVersion: 9`+ importer layout and returns `undefined` for a legacy v5/v6 file, a truncated one, or any shape that leaves an importer with zero specifiers — because a half-parse reports every real dependency as "not recorded". The rule FAILS OPEN on that `undefined` (no violation) and is pinned SOFT-only via `SOFT_ONLY_RULES`, so no config can turn a parser that admits uncertainty into a publish blocker. The HARD `licence-hold` rule (+ `LICENCE_HOLD_PENDING_MARKER`) is the machine side of a declared licence precondition (`package.json.licenseHold`, ADR-001 hermes-claude-adaptation): silent while the pack stays `private:true` (the npm layer refuses it), it HARD-blocks publish the moment the pack becomes publishable with the hold unsatisfied — LICENSE absent/empty or still carrying the `<!-- PENDING:` grant placeholder, no `Grant-Confirmation: <url>` line, empty THIRD_PARTY_NOTICES, or a non-SPDX license field |
15
16
  | `backlog` + `backlog-embed` | `dedupIdea`, `classifyDedup`, `dedupPairBand`, `dedupEmbedText`, `lexicalContainment`, `ensureBacklogEmbedForm`, `recordAbsorption`, `alignIdea`, `spinRoulette`, `readGoalMapDetailed`, `parseEffort`, `ensureBacklogGitignored`, `harmonizeBacklog`, `transitionIdeas`, `checkTransition`, `resolveIdPrefix`, `IDEA_TRANSITIONS` | The Smart Backlog engine behind `dz backlog`: status lifecycle via `transitionIdeas` (`ship`/`drop`/`reopen` against the `IDEA_TRANSITIONS` table — unique-short-prefix resolution, idempotent ship/drop no-ops, non-idempotent reopen, all-or-nothing fail-closed batches, line-preserving atomic JSONL rewrite that keeps every non-status byte of untouched records); content-addressed idea records in `.dz/backlog/ideas.jsonl`, semantic dedup over the REUSED agentdb vector namespace (`dz-backlog` — no second store), weighted-max GoalMap alignment, and a seeded weighted roulette. Dedup is TWO-SIGNAL since the register-inflation fix (MEASURED 2026-08-11 on the real 105-idea store: full-length embeds INVERTED the signal on long texts — genuine paraphrases 0.35–0.61 vs topically disjoint long-RU pairs up to 0.9195): `dedupEmbedText` embeds a bounded 400-char excerpt (`backlog-embed.ts`, one form shared by query/mirror/reindex so vectors can never split spaces; `ensureBacklogEmbedForm` re-mirrors v1 stores once, batched), and `dedupPairBand` requires a cosine-threshold DUPLICATE to also share subject vocabulary (`lexicalContainment` ≥ 0.3, else demoted to RELATED with the pair reported — the 0.941 register-only absorption) while promoting a same-idea re-capture at a different length (containment ≥ 0.95, cosine ≥ 0.75) to a subset duplicate; `recordAbsorption` keeps every absorbed text in `absorbed.jsonl` so a wrong verdict is reversible (mutation-defended: `backlog-dedup-demotion-corroboration`, hand-verified 6 red). Every band/weight decision is a PURE function. `classifyDedup` also reports the top-1 match id alongside the cosine (the calibration surface for the 0.92 duplicate band — observational, it never moves the verdict); `readGoalMapDetailed` returns the entries the defensive reader DROPPED with a reason AND the fields it REPAIRED with their raw values (a weight clamped before validation made the validator's out-of-range branch dead code), so `goals --validate` can never report a vacuous "valid (0 goals)" nor hide a `weight: 7`; `parseEffort` returns a printable note for every clamp; `ensureBacklogGitignored` gitignores the store on first write (raw ideas are private prompt-class content) — atomically, preserving the file's dominant EOL, recognising every plain spelling of an existing rule (`/.dz/`, `.dz/**`, …) via `backlogIgnoreStatus`, and obeying a `!` negation as an explicit user opt-out instead of overriding it |
@@ -19,12 +20,14 @@ and any other consumer.
19
20
  | `cost-ledger` | `deriveCostLedger`, `buildCostLedger`, `verifyCostLedgerReport`, `stageCostAggregates`, `renderCostLedger`, `writeCostLedgerJsonl`, `COST_LEDGER_SCOPE` | Per-stage cost ledger behind `dz usage --by-stage`. A feature-adr run reports ONE number; this joins the workflow's own `stageLabel()` strings to the per-agent transcripts the harness already writes, so a run becomes an itemized receipt. POST-HOC DERIVER, not a writer — no workflow edit, and a KILLED run is still derivable. The invariant: `accounted + unaccounted === runTotal` and `accounted + doubleAttributed === Σ stages`, RAW integer equality (rounding happens exactly once, per sample, at extraction), re-derived from the emitted report by `verifyCostLedgerReport` — the writer clamps, the verifier enforces. A mismatch is a NAMED defect (`Unaccounted`, `DoubleAttributed`, `ForeignSample`, `MissingStageTranscript`, `MalformedRecord`), never a rounding remainder, so `epsilon` defaults to 0. The run total comes from the run's transcript DIRECTORY LISTING, NOT the record's own `totalTokens` — that field is exactly `Σ workflowProgress[].tokens` in 29 of 29 recorded runs (MEASURED), so an invariant against it can never fail. Both sides share ONE estimator with `dz usage` (`weightedTokensOf`). `stageCostAggregates` is a pure feed-forward reader for auto-cost routing that EXCLUDES non-reconciling runs; wiring it into routing is deliberately out of scope. HONEST SCOPE, printed by every surface: local transcript ESTIMATES, not billed amounts — it catches ATTRIBUTION errors, NOT pricing errors; `hasKnownPricing` marks rows priced by the sonnet-class fallback. `INSUFFICIENT_DATA` is a distinct verdict, never collapsed into `BALANCED` |
20
21
  | `compounding` | `mulberry32`, `bootstrapDelta`, `decidePromotion`, `assembleCompoundingReport` | Pure learning-loop payoff engine behind `dz compounding`: seeded deterministic bootstrap (conservative nearest-rank lower-95), promotion that refuses non-finite/malformed input and anything under 5 samples per arm, and dz-native measurements (pool write-only ratio, guard trajectory by RATE, replay readiness over unique untruncated prompt events). A gate without data says INSUFFICIENT_DATA — never a verdict. Compaction keeps the newest query-bearing rows verbatim and aggregates ONLY the rest (read totals are invariant across compactions). Also reports EVENT-CHAIN health of the evidence logs it computed from (`evidenceLogs` in, `instrumentation.chains` out) — verified / defect kinds / uncovered pre-chain prefix, with no logs handed in producing no line at all rather than a vacuous "clean" |
21
22
  | `event-chain` | `fnv1a32`, `nextChainFields`, `appendChainedLines`, `chainRewrite`, `guardedRewrite`, `verifyEventChain`, `EVENT_CHAIN_SCOPE` | Pure hash-chain over the two learning-evidence logs (`.dz/recall-usage.jsonl`, `.dz/guard-audit.jsonl`): each appended record carries `seq` + `prevHash` (FNV-1a over the previous line AS WRITTEN, so key order cannot make writer and verifier disagree), derived from the LAST LINE ONLY so a per-prompt hook stays O(1). `verifyEventChain` names eight classes — `BrokenLink`, `DuplicateSeq`, `NonMonotonicSeq`, `TornTail`, `DoubleCounted`, `LedgerImbalance`, `MalformedLedger`, `ClaimInterrupted`. The last four exist because a rewriter must not be able to certify itself: the compaction ledger's arithmetic (`Σ weight + dropped === source`, `dropped ∈ [0, source]`) is enforced with NO clamps in the verifier (the clamp belongs to the writer), a damaged ledger line is a defect rather than a silently-disabled check, and a claim that never reached its `throughSeq` — because the segment restarted or the file ended — is reported instead of escaping through the discontinuity. `guardedRewrite` is the concurrency guard for any whole-file rewrite: exclusive lock, plus a re-read of the live file after computing the new text and BEFORE the rename, so a concurrent append aborts the attempt and is folded into a bounded retry rather than overwritten (it narrows the read→rename window; it cannot close it, and says so). Records written before chaining existed stay LEGAL and are counted as an uncovered `preChainPrefix`; an unreadable tail never blocks a write (fresh MARKED segment — an unreadable tail WINDOW is distinguished from an empty file — and the appender starts on a new line so one torn write cannot eat the next record); an unmarked restart is reported once and then re-anchored, so one incident is one defect instead of a cascade. HONEST SCOPE, carried in every result and printed by every surface: corruption detection for our own bugs — FNV-1a is not cryptography, its collisions are constructible, the threat model has no adversary, and a regression test fails if the module regrows tamper-proofing vocabulary |
22
- | `feature-adr-checkpoints` | `checkpointInputHash`, `decideCheckpointResume`, `parseCheckpointRead`, `serializeCheckpoint`, `fnv1a64`, `CKPT_SCHEMA_VERSION`, `STAGE_ARTIFACTS` | The PURE half of feature-adr's durable per-stage checkpoints (`features/<slug>/.fa-state/checkpoints.jsonl`): a dead L/XL run — or the standard stop-after-plan re-invoke — resumes completed stages instead of re-spending them. Resume = INPUT-identity (64-bit salted FNV over a schema-versioned JSON tuple incl. upstream stage results) + presence of EVERY tier-required artifact; a stale-input hash never resumes in ANY mode (`force` relaxes only the artifact probe — the tested load-bearing property). HONEST SCOPE: it does NOT fingerprint the working tree (a crash-resume legitimately sees the dead run's uncommitted writes) — after manual edits use `resume:'never'` and re-QE. Null results are never persisted or resumable; a stage-identifiable corrupt record ERASES its older entry (last-wins holds for corruption too); a codex code stage whose landing barrier said `genuinely-not-landed` is never checkpointed. The workflow mirrors this inline (wiring-guarded); RU: чекпоинт после каждой дорогой стадии — упавший ран возобновляется, а не пере-тратит завершённое |
23
+ | `feature-adr-checkpoints` | `checkpointInputHash`, `decideCheckpointResume`, `parseCheckpointRead`, `serializeCheckpoint`, `fnv1a64`, `CKPT_SCHEMA_VERSION`, `STAGE_ARTIFACTS` | The PURE half of feature-adr's durable per-stage checkpoints (`features/<slug>/.fa-state/checkpoints.jsonl`): a dead L/XL run — or the standard stop-after-plan re-invoke — resumes completed stages instead of re-spending them. Resume = INPUT-identity (64-bit salted FNV over a schema-versioned JSON tuple incl. upstream stage results) + presence of EVERY tier-required artifact; a stale-input hash never resumes in ANY mode (`force` relaxes only the artifact probe — the tested load-bearing property). HONEST SCOPE: it does NOT fingerprint the working tree (a crash-resume legitimately sees the dead run's uncommitted writes) — after manual edits use `resume:'never'` and re-QE. Null results are never persisted or resumable; a stage-identifiable corrupt record ERASES its older entry (last-wins holds for corruption too); the code stage's persist predicate is now an ALLOWLIST (`codeCheckpointPersistAllowed` + `codeStageResultShapeValid`, ADR-003 Condition 3): ONLY `landed` on a barrier-required run and `synchronous` on a non-barrier run may be checkpointed — inconclusive, not-landed, garbage and a mislabeled `synchronous` are all refused, and the `landing-v2` hash token makes every pre-protocol code checkpoint stale. The workflow mirrors this inline (wiring-guarded); RU: чекпоинт после каждой дорогой стадии — упавший ран возобновляется, а не пере-тратит завершённое |
24
+ | `discrimination-gate` | `planDiscriminationCheck`, `classifyExecutionEvidence`, `classifyDiscrimination`, `DiscriminationVerdict`, `BaseOutcome`, `TipOutcome`, `ExecutionEvidence`, `CannotIsolateReason`, `DiscriminationFinding`, `MeasurementValid`, `PrimaryAction` | The PURE §42 test-discrimination engine behind `dz discrimination-check`: it plans the pre-feature worktree check and classifies the observations the executor feeds back. **Every trust verdict is gated on execution evidence** (ADR-001, feature wave1-instrument-repair): `classifyExecutionEvidence` turns one captured run into `{exitCode, runner, failureKind, testsExecuted, targetSeen}` off the MEASURED vitest / node --test output shapes (reusing `mutation-gate`'s single regex family), and a row whose outcome VALUE is not backed by that evidence degrades to `CANNOT_ISOLATE` with a typed reason instead of minting trust. **Seven verdicts** (was four): `DISCRIMINATES` (assertion-red at base, evidenced) · `DISCRIMINATES_VIA_ERROR` (evidenced load error at base + evidenced pass at TIP) · `NON_DISCRIMINATING` (evidenced pass at base — a proven false green) · `TEST_FILE_ABSENT` (the named check is not a regular file — stat+isFile, before any worktree) · `LOAD_ERROR_AT_BOTH_REVS` (could not execute at EITHER rev — zero signal) · `FAILS_AT_TIP` (the feature's own test is red WITH the feature) · `CANNOT_ISOLATE` (no established observation; reason ∈ no-execution-evidence | unrecognised-runner-output | no-tests-executed | inconsistent-evidence | tip-control-missing | tip-evidence-missing | timeout). The result carries `findings[]` (one per distinct non-clean verdict — the scalar `aggregate` can only name the worst), plus two ORTHOGONAL axes: `measurementValid` (did the instrument measure at all: `true | false | 'partial'`) and `primaryAction` (the single most urgent operator repair). PARSE-NEVER-SYNTHESIZE: an outcome contradicted by its own evidence is rejected as `inconsistent-evidence`, never reinterpreted. HONEST SCOPE: the bar is "a recognized runner demonstrably executed the named test", NOT resistance to an output-imitating runner; recognising vitest/node --test shapes is in scope, CHOOSING the runner is not. The singular `finding` remains as a DEPRECATED one-release alias for `findings[0] ?? null`. RU: вердикт доверия теперь требует доказательства исполнения — «упало» без доказательства больше не считается доказательством |
25
+ | `feature-adr-routing` (landed barrier) | `sourceExpectedCodeTargets`, `validateExpectedTargetsBlock`, `codeLandedBarrierPlan`, `decideCodeLanding`, `codeLandedBarrierHasLanded`, `codeLandingProbeCmd`, `parseLandingSignal`, `verifyPreCodeBaseline`, `preCodeBaselineCaptureCmd`, `posixCksum`, `LANDING_PROTOCOL_VERSION`, `LANDING_HASH_TOKEN` | The PURE half of feature-adr's Step-7.5 Codex landing barrier (ADR-003, feature wave1-instrument-repair). Expected targets are SOURCED with an explicit precedence — a non-empty `args` override REPLACES the plan's `EXPECTED_CODE_TARGETS:` block (and narrowing to an all-unpollable set returns EMPTY with reason `override-unpollable`, never a silent fall-through), while Codex's own self-declared paths land in `scrapeDiagnostic` and can NEVER establish or match — the agent under test does not declare its own success criteria. An enabled barrier with no established target is `mode:'inconclusive'`, not the deleted `any-code-change` fallback that read an unrelated dirty file as landed. Landing is a DELTA, not dirtiness: `preCodeBaselineCaptureCmd` records `git hash-object` per pre-existing dirty path with a `count=/cksum=` trailer, `verifyPreCodeBaseline` refuses any truncated or edited baseline as `baseline-unverified` (a truncated baseline makes everything look landed), and a path counts only when it is absent from the baseline or its hash CHANGED. `parseLandingSignal` is the single normalization point — empty stdout is `probe-failure`, unparseable text is `malformed-signal`, and neither is ever a landing. RU: «файл грязный» ≠ «кодер его написал»; барьер теперь умеет сказать «не знаю» |
23
26
  | `feature-adr-training-pairs` (in `feature-adr-checkpoints`) | `buildTrainingPair`, `serializeTrainingPair`, `trainingPairPath`, `trainingPairAppendCmd`, `modelFamily`, `TRAINPAIR_SCHEMA_VERSION`, `TRAINPAIR_MAX_IO_CHARS`, `TRAINPAIR_PRIVACY_NOTE` | The PURE half of feature-adr TRAINING-PAIR capture (backlog 70e0f083): every checkpointed stage emits one SFT-ready JSONL record — STAGE INPUT (full prompt/context) → STAGE OUTPUT (artifact/result) → EVALUATION {QE grade, gradedBy, lessonsInjected} with provenance {model, FAMILY ∈ claude/codex, role} — to `.dz/fa-training/<slug>/<stage>.jsonl` (one file per stage), raw material for future local-model fine-tuning. FAMILY is load-bearing: the downstream dataset must honour the cross-model rule (QE pairs from a DIFFERENT family than the coder). Oversize guard: input+output over 48k chars is TRUNCATED with a named marker + full-text fnv1a64 — never silently dropped, never unbounded. A stage without a QE grade (router) records `grade:null` honestly. Deterministic: `ts` is passed in (the workflow fills it shell-side). Capture is default-ON in the workflow, opt-out `args.captureTrainingPairs:false`, non-blocking (a capture failure never fails the run). PRIVACY: pairs may contain target-repo code; the capture dir carries a README note; NOT gitignored by explicit owner decision. RU: тренировочные пары вход→выход→оценка с каждого прогона feature-adr — сырьё для будущей локальной модели |
24
27
  | `reqe` | `shouldEmitReqeDebt`, `buildReqeDebt`, `parseReqeDebt`, `buildReqeBrief`, `extractReportGrade`, `settleReqeDebt`, `renderReqeList`, `REQE_SCOPE` | The pure half of `dz reqe` — the re-QE debt ledger: when feature-adr's usage-adaptive override made Step-8 QE run on the coder's OWN family (the cross-model guard consciously suspended, FR-2.9), the run records a debt in `features/<slug>/.fa-state/reqe-due.json`. Emission is the NARROW case only (same-family + the ` (usage-switched)` label — never every switch, never the no-override Claude belt); settlement is FAIL-CLOSED: an existing, non-trivial report naming exactly ONE line-anchored grade (`GRADE A-F` boilerplate and `A through F` ranges refused, ambiguity refused), never the run's own 08_qe_report.md. Debts carry the emitting run's stamp so an old settlement never immunizes a fresh run. HONEST SCOPE printed everywhere: nothing re-runs QE automatically; the validator proves procedural soundness, not authorship. RU: снятый под лимитом гард «кодер не ревьюит сам себя» становится долгом на диске, а не памяткой |
25
28
  | `trace-bundle` | `buildBundle`, `serializeBundle`, `parseBundle`, `selectLedgerRows`, `resolveRunMeta`, `foldAttribution`, `planImport` | The PURE half of `dz workflow-trace export/import` — one run's telemetry as one movable file. No fs, no clock, no randomness: it DECIDES and the caller does the I/O, which is what makes the fail-closed import testable without ever pointing a test at a real project (`planImport` returns the refusals as a VALUE, not as a side effect). Run addressing is the existing one, reused rather than rebuilt. Carries EVENTS, not aggregates: the single derived value travels alongside the records it was folded from, marked derived and naming its rule, so deleting it loses nothing but convenience — last-writer-wins by timestamp is a stated CHOICE, not a truth. `resolveRunMeta` reads the harness's own workflow records and judges each RECORD, not the slug: one historical sibling must not poison a usable one (MEASURED: 1 slug of 32 was being thrown away whole). Its reason set is closed and exactly one value is ACTIONABLE — `layout-unrecognised` means the harness layout changed; `predates-model-routing` means history. That split exists because the actionable reason fired on 3 of 32 slugs of untouched data, and an alarm that sounds on normal operation is not an alarm. RECOGNISE-OR-REFUSE: a record whose fields are gone yields a reason and NO data, never a half-parse that would report a model-blind run as model-known |
26
29
  | `statusline` | `statuslineData`, `readFeatureAdrState`, `writeFeatureAdrState`, `featureAdrStateDir`, `featureAdrStatePath`, `FeatureAdrState` | The live self-learning panel behind `dz statusline`, plus the LIVE-RUN segment two producers share. Each producer owns a per-slug slot under `.dz/feature-adr/learning-state/` and stamps `kind: 'feature-adr' \| 'loop'` (absent ⇒ `feature-adr`, so legacy states keep their meaning); `readFeatureAdrState` arbitrates by `(kind rank, ts)` — a fresh `feature-adr` state OUTRANKS any `loop` state, because a generated loop writes zero recalled/stored counters far more often and plain freshest-wins would empty the panel of the very thing it exists to show. Candidates are stat'ed and ordered newest-first BEFORE the bounded slice, so truncation can only ever drop the least-recent slot — a cap over an unsorted listing could hide the live slot behind older ones (MEASURED: 81 slots, the live one invisible). The render path is strictly READ-ONLY (~300 ms budget); housekeeping — a 24 h prune — belongs to the write path alone. Hostile slugs are sanitized to one bounded filename component and cannot escape the directory. Nothing older than 30 minutes is surfaced |
27
- | `operations` | `runInit`, `runSync`, `runVerify`, `runDoctor` | The harness operations, returning structured reports |
30
+ | `operations` | `runInit`, `runSync`, `runVerify`, `runDoctor` | The harness operations, returning structured reports. `InitReport` and `SyncReport` carry an additive, always-present `failures: readonly SkillLoadFailure[]` (empty when nothing failed): a single unloadable `SKILL.md` used to throw out of the whole loop, so `dz init`/`dz install`/`dz sync` reported NOTHING at all. They now skip, collect and name — skipping without a record would only trade a loud failure for a silent one. `runDoctor` is deliberately untouched: it was never a throw site, and a negative test asserts it gained no `failures` field |
28
31
  | `release` | `collectPackageFacts`, `selectAffectedPackages`, `planReleaseGates`, `classifyGateExecutions`, `buildFailureIssue`, `firstOutputLine` | Pure verified-release engine behind `dz release`: plans 4 HARD gates (tests / `pnpm audit --prod` / `node --check` / bin smoke-boot) as DATA and classifies injected results fail-closed — an unbuilt package (declared `build` script, no dist JS) is a `MISSING_DIST` failure, a template-only pack is a named `SKIP_NO_ARTIFACTS` skip, `selectAffectedPackages` fail-opens to the full set when the changed-file list is unavailable |
29
32
  | `parity` | `TARGET_CAPABILITIES`, `PARITY_FEATURES`, `computeParity`, `buildParityMatrix` | Declarative target-parity model behind `dz parity`: verified capability flags per target × feature FORMS with requirements; the feature×target matrix is always COMPUTED (never hand-written), and the model must classify exactly `TARGET_NAMES` — an unclassified new target refuses to compile |
30
33
  | `delivery-check` | `PLANE_SPECS`, `collectDeliveryFacts`, `planDeliveryCheck`, `renderDeliveryBrief`, `classifyDelivery`, `renderDeliveryReview` | Pure portable Step-10 Delivery Gate engine behind `dz delivery-check`: the four review planes as shared DATA (prose-identical to the workflow's inline `planePrompts`, held by a drift-guard test), a deterministic plan/classify over injected facts+findings, and the FAIL-CLOSED hand-off verdict — `ready` only off complete, cross-validated, clean evidence; classification reads only numeric severity counts so injected instruction-like text cannot move the verdict. No `child_process`; the only fs is `existsSync` in `collectDeliveryFacts` |
@@ -122,6 +125,71 @@ templates are retired — `WorkflowTemplate`/`WORKFLOWS`/`getWorkflow` removed,
122
125
  now empty. Replacement: `dz workflow init/validate/render` + `dz workflow-lint`/`dz workflow-trace`
123
126
  (see the CHANGELOG).
124
127
 
128
+ ## Always-on policy sync for Codex
129
+
130
+ `runSyncAgentsPolicy` is the I/O shell around the pure `agents-policy` module. It reads the fixed
131
+ anchor registry from `CLAUDE.md` and `.claude/rules/*.md`, renders those clauses verbatim, and
132
+ updates only the independent `dz:policies` fence in the root `AGENTS.md`. The write path is the
133
+ same `writeManagedMarkdown` helper used by `runInitAgentsMd`; `--check` callers never write.
134
+
135
+ Use it after changing an anchored bearing rule, and in CI before publish. Missing sources are
136
+ `inconclusive` rather than a pass, oversized output is refused before any write, and hand-authored
137
+ content plus the existing `dz:skills` fence is preserved.
138
+
139
+ ## Codex hook carrier (`hooks-sync --target codex`)
140
+
141
+ Five modules deliver the dz veto + auto-recall hooks to Codex's user-global registry, and one of them
142
+ is shared with the Claude Code path rather than duplicated:
143
+
144
+ | Module | What it is |
145
+ |---|---|
146
+ | `managed-hooks.ts` | `mergeManagedHookEntries` — the **one** event-level hook merge, used by BOTH targets. Foreign entries are kept byte-for-byte; the attribution predicate is the only parameter that differs (substring for Claude, sha-over-manifest for Codex). |
147
+ | `codex-hooks.ts` | paths (all `CODEX_HOME`-relative), the two managed entries, sha attribution, manifest, drift, and the `config.toml` trust block. Pure. |
148
+ | `codex-hooks-verify.ts` | `classifyVetoProbe` — a fail-closed two-axis classifier (`verdict` × `trust`). No branch defaults to a pass; a `--dangerously-bypass-hook-trust` run can never yield one. |
149
+ | `shell-veto-policy.ts` | `vetoShellCommand` + `resolveVetoMode`. ONE rule, `ssh-explicit-auth-weakening`; **warn by default**, block only on explicit project opt-in. Pure, no I/O. |
150
+ | `codex-hooks-assets.ts` | the two emitted `.cjs` helper bodies (the `generateAgentdbWriter` pattern). |
151
+
152
+ `runSyncCodexHooks` (in `operations.ts`) is the I/O shell: refuse → read → merge → back up → atomic
153
+ write → helpers → **install-time liveness self-probe** → arm trust → manifest.
154
+
155
+ Two runtime facts it encodes, both MEASURED on `codex-cli 0.147.0` and both load-bearing:
156
+
157
+ - **Hooks are trust-gated.** A written entry is silently never run until trust is recorded per entry
158
+ in `$CODEX_HOME/config.toml` as `[hooks.state."<key>"] trusted_hash = "<currentHash>"`. Both values
159
+ are READ from codex's own `hooks/list` app-server RPC — never computed — so dz cannot arm an entry
160
+ it did not just emit, and editing a helper disarms it rather than inheriting its trust.
161
+ - **The runner spawns via `$SHELL -lc`.** A bare `node` is frequently absent from a non-interactive
162
+ login shell, and the helper then exits **127**, which the runtime reads as **ALLOW** — a blocking
163
+ guard silently dead in the fail-open direction. So the interpreter is an absolute `process.execPath`,
164
+ both paths are single-quoted, and install runs a liveness self-probe through the same shell. A hook
165
+ that cannot execute is reported **not armed**, never "installed".
166
+
167
+ `appendRecallUsage` in `recall-usage.ts` is the single chained writer both runtimes call; rows carry
168
+ `runtime: 'claude-code' | 'codex'` (absent ⇒ `claude-code`), and the compaction aggregate carries a
169
+ `runtimes` set union so provenance survives the lossy path.
170
+
125
171
  ## Status
126
172
 
127
- `0.4.3` — stable.
173
+ `0.4.8` — staged, not published. Adds the Codex hook carrier above, the shared
174
+ `mergeManagedHookEntries` extraction, the `runtime`/`runtimes` provenance on recall-usage, and the
175
+ granular `hooks-write` / `hooks-shell` / `hooks-prompt` parity capabilities with an evidence gate.
176
+
177
+ **New in 0.4.7:** the anchored policy emitter/drift detector, reusing the existing AGENTS.md
178
+ managed-write path.
179
+
180
+ **New in 0.4.6** (feature `dz-cli-defects`, slice A): `listSkillsDetailed` /
181
+ `describeSkillLoadFailure` / `formatSkillLoadFailures` (skip-and-collect skill listing —
182
+ `listSkills` still throws, on purpose) and `resolveTargetName` / `TARGET_ALIASES` /
183
+ `formatTargetProblem` / `formatTargetAliasNote` / `TARGET_NAMES_SORTED` (`--target` alias
184
+ resolution + did-you-mean). Both additive — no existing export changed shape or behaviour.
185
+ `InitReport` / `SyncReport` gained an always-present `failures` array.
186
+
187
+ **Fix round 1 (still 0.4.6, unpublished).** `SkillApplyFailure` / `formatSkillApplyFailures` and an
188
+ always-present `InitReport.applyFailures`: `runInit`'s per-id `try` is now scoped to
189
+ `loadSkillFromDir` alone (as ADR-001 decided), so a compile-or-write error is reported under its own
190
+ header naming the TARGET, instead of masquerading as an "unparseable `SKILL.md`" against a valid
191
+ source file. `normalizeTargetToken` is now exported (the alias-reachability check asks the production
192
+ normaliser rather than keeping a copy of the rule). Four `TARGET_ALIASES` rows were deleted as
193
+ UNREACHABLE — `claude_code`, `claudecode`, `agentsmd`, `agents.md` all normalise onto a canonical name
194
+ and were resolved by precedence step 2 before the table was ever consulted; every one of those inputs
195
+ still resolves, so the deletion is observably a no-op.
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Pure extraction, rendering, budget measurement, and drift detection for the
3
+ * always-on policy projection written to the root AGENTS.md.
4
+ *
5
+ * Callers own filesystem I/O. Keeping this module string-in/string-out makes
6
+ * the layer-1 drift guard deterministic and usable from both tests and CLI.
7
+ */
8
+ export interface PolicySource {
9
+ readonly id: string;
10
+ readonly file: string;
11
+ readonly heading: string;
12
+ readonly why: string;
13
+ /** Clause whose loss would turn the policy into a slogan rather than a rule. */
14
+ readonly operativeClause: string;
15
+ }
16
+ /** Ordered by cold-start value; this order is also the deterministic emit order. */
17
+ export declare const POLICY_SOURCES: readonly PolicySource[];
18
+ export interface PolicyBlock {
19
+ readonly id: string;
20
+ readonly file: string;
21
+ readonly heading: string;
22
+ /** Verbatim anchor body after the documented minimal whitespace normalization. */
23
+ readonly text: string;
24
+ readonly sha: string;
25
+ }
26
+ export interface ExtractPolicyBlocksResult {
27
+ readonly blocks: readonly PolicyBlock[];
28
+ /** Registry ids whose source is absent/null or whose begin/end anchor is malformed. */
29
+ readonly missing: readonly string[];
30
+ }
31
+ export type PolicyDriftStatus = 'ok' | 'stale' | 'missing-stamp' | 'missing-anchor' | 'orphan-stamp';
32
+ export interface PolicyDriftFinding {
33
+ readonly id: string;
34
+ readonly file: string;
35
+ readonly status: PolicyDriftStatus;
36
+ readonly expectedSha: string | null;
37
+ readonly actualSha: string | null;
38
+ }
39
+ export interface PolicyDriftResult {
40
+ readonly applicable: boolean;
41
+ readonly findings: readonly PolicyDriftFinding[];
42
+ }
43
+ /** MEASURED 2026-08-18, codex-cli 0.147.0, project_doc_max_bytes unset: codex debug prompt-input included exactly 32768 bytes of a 414013-byte AGENTS.md and truncated mid-line with no notice. */
44
+ export declare const CODEX_PROJECT_DOC_MAX_BYTES = 32768;
45
+ export declare const AGENTS_MD_BUDGET_WARN_FRACTION = 0.9;
46
+ export interface AgentsMdBudget {
47
+ readonly bytes: number;
48
+ readonly cap: number;
49
+ /** Percentage in the human convention, e.g. 50 means half the cap. */
50
+ readonly pct: number;
51
+ readonly overflow: boolean;
52
+ /** UTF-8 byte offset immediately after the policy END marker, or -1 when absent. */
53
+ readonly policyBlockEndsAtByte: number;
54
+ }
55
+ export declare function normalizePolicyText(text: string): string;
56
+ export declare function policyTextSha(text: string): string;
57
+ export declare function extractPolicyBlocks(files: ReadonlyMap<string, string | null>, sources?: readonly PolicySource[]): ExtractPolicyBlocksResult;
58
+ export declare function renderPolicySections(blocks: readonly PolicyBlock[], sources?: readonly PolicySource[]): readonly string[];
59
+ /**
60
+ * Has this repository OPTED IN to policy sync? The `dz:policies` fence in `AGENTS.md` is the only
61
+ * durable on-disk signal that `dz agents-sync` was ever run here. Keeping the marker knowledge in
62
+ * this module means no caller has to re-spell the literal (a second spelling is a second surface).
63
+ */
64
+ export declare function hasPolicyFence(fileText: string | null | undefined): boolean;
65
+ export declare function measureAgentsMdBudget(fileText: string): AgentsMdBudget;
66
+ export declare function detectPolicyDrift(sourceFiles: ReadonlyMap<string, string | null>, agentsMdText: string | null, sources?: readonly PolicySource[]): PolicyDriftResult;
67
+ //# sourceMappingURL=agents-policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agents-policy.d.ts","sourceRoot":"","sources":["../src/agents-policy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,258 @@
1
+ /**
2
+ * Pure extraction, rendering, budget measurement, and drift detection for the
3
+ * always-on policy projection written to the root AGENTS.md.
4
+ *
5
+ * Callers own filesystem I/O. Keeping this module string-in/string-out makes
6
+ * the layer-1 drift guard deterministic and usable from both tests and CLI.
7
+ */
8
+ import { createHash } from 'node:crypto';
9
+ import { POLICY_BLOCK_BEGIN, POLICY_BLOCK_END } from '@dzhechkov/core';
10
+ /** Ordered by cold-start value; this order is also the deterministic emit order. */
11
+ export const POLICY_SOURCES = [
12
+ {
13
+ id: 'integrity-rule',
14
+ file: 'CLAUDE.md',
15
+ heading: 'Integrity Rule (ABSOLUTE)',
16
+ why: 'Prevents fabricated evidence and unmeasured claims from a fresh runtime.',
17
+ operativeClause: 'measure before asserting',
18
+ },
19
+ {
20
+ id: 'verify-before-claiming',
21
+ file: 'CLAUDE.md',
22
+ heading: 'Verify before claiming',
23
+ why: 'Requires execution evidence instead of a prediction that work passes.',
24
+ operativeClause: 'verify before claiming success',
25
+ },
26
+ {
27
+ id: 'test-execution-no-watch',
28
+ file: 'CLAUDE.md',
29
+ heading: 'Test execution: never watch',
30
+ why: 'A watch-mode test run hangs an unattended coding session.',
31
+ operativeClause: 'npm test -- --run',
32
+ },
33
+ {
34
+ id: 'data-protection',
35
+ file: 'CLAUDE.md',
36
+ heading: 'Data protection',
37
+ why: 'Protects persistent QE and database state from destructive cleanup.',
38
+ operativeClause: 'without confirmation',
39
+ },
40
+ {
41
+ id: 'fixes-through-pipeline',
42
+ file: '.claude/rules/feature-adr-conventions.md',
43
+ heading: 'Fixes go through the pipeline',
44
+ why: 'Preserves the framing checkpoint for fixes and selects independent QE up front.',
45
+ operativeClause: 'ultracode or plain?',
46
+ },
47
+ {
48
+ id: 'publish-gate',
49
+ file: '.claude/rules/feature-adr-conventions.md',
50
+ heading: 'Publish gate',
51
+ why: 'Prevents publishing an unsettled disk version before independent verification lands.',
52
+ operativeClause: 'independent round with no blockers',
53
+ },
54
+ {
55
+ id: 'readme-first',
56
+ file: 'CLAUDE.md',
57
+ heading: 'README-first',
58
+ why: 'Keeps each changed package and the repository documentation current before a bump.',
59
+ operativeClause: 'in the SAME change',
60
+ },
61
+ {
62
+ id: 'cross-family-qe',
63
+ file: '.claude/rules/feature-adr-ultracode.md',
64
+ heading: 'Cross-family QE',
65
+ why: 'Prevents the model family that wrote code from certifying its own work.',
66
+ operativeClause: 'FAMILY, not host',
67
+ },
68
+ {
69
+ id: 'cost-of-detection-ladder',
70
+ file: '.claude/rules/feature-adr-conventions.md',
71
+ heading: 'Cost-of-detection ladder',
72
+ why: 'Places deterministic checks at the earliest reliable layer instead of reviewer memory.',
73
+ operativeClause: 'strongest layer that can express it',
74
+ },
75
+ ];
76
+ /** MEASURED 2026-08-18, codex-cli 0.147.0, project_doc_max_bytes unset: codex debug prompt-input included exactly 32768 bytes of a 414013-byte AGENTS.md and truncated mid-line with no notice. */
77
+ export const CODEX_PROJECT_DOC_MAX_BYTES = 32_768;
78
+ export const AGENTS_MD_BUDGET_WARN_FRACTION = 0.9;
79
+ const POLICY_STAMP = /^<!-- dz:policy id=([a-z0-9-]+) src=([^\s]+) sha=([a-f0-9]{12}) -->$/gm;
80
+ export function normalizePolicyText(text) {
81
+ return text
82
+ .replace(/\r\n?/g, '\n')
83
+ .split('\n')
84
+ .map((line) => line.replace(/[ \t]+$/g, ''))
85
+ .join('\n')
86
+ .trim();
87
+ }
88
+ export function policyTextSha(text) {
89
+ return createHash('sha256').update(normalizePolicyText(text), 'utf8').digest('hex').slice(0, 12);
90
+ }
91
+ function anchorPair(id) {
92
+ return { begin: `<!-- dz:policy id=${id} -->`, end: '<!-- dz:policy end -->' };
93
+ }
94
+ export function extractPolicyBlocks(files, sources = POLICY_SOURCES) {
95
+ const blocks = [];
96
+ const missing = [];
97
+ for (const source of sources) {
98
+ const raw = files.get(source.file);
99
+ if (typeof raw !== 'string') {
100
+ missing.push(source.id);
101
+ continue;
102
+ }
103
+ const { begin, end } = anchorPair(source.id);
104
+ const beginIndex = raw.indexOf(begin);
105
+ const secondBegin = beginIndex === -1 ? -1 : raw.indexOf(begin, beginIndex + begin.length);
106
+ const endIndex = beginIndex === -1 ? -1 : raw.indexOf(end, beginIndex + begin.length);
107
+ const nestedBegin = beginIndex === -1 ? -1 : raw.indexOf('<!-- dz:policy id=', beginIndex + begin.length);
108
+ if (beginIndex === -1 || secondBegin !== -1 || endIndex === -1 || (nestedBegin !== -1 && nestedBegin < endIndex)) {
109
+ missing.push(source.id);
110
+ continue;
111
+ }
112
+ const text = normalizePolicyText(raw.slice(beginIndex + begin.length, endIndex));
113
+ if (text.length === 0) {
114
+ missing.push(source.id);
115
+ continue;
116
+ }
117
+ blocks.push({ id: source.id, file: source.file, heading: source.heading, text, sha: policyTextSha(text) });
118
+ }
119
+ return { blocks, missing };
120
+ }
121
+ export function renderPolicySections(blocks, sources = POLICY_SOURCES) {
122
+ const headingById = new Map(sources.map((source) => [source.id, source.heading]));
123
+ return blocks.map((block) => {
124
+ const heading = headingById.get(block.id) ?? block.heading;
125
+ return `<!-- dz:policy id=${block.id} src=${block.file} sha=${block.sha} -->\n## ${heading}\n\n${block.text}\n\nCanonical: ${block.file}`;
126
+ });
127
+ }
128
+ /**
129
+ * Has this repository OPTED IN to policy sync? The `dz:policies` fence in `AGENTS.md` is the only
130
+ * durable on-disk signal that `dz agents-sync` was ever run here. Keeping the marker knowledge in
131
+ * this module means no caller has to re-spell the literal (a second spelling is a second surface).
132
+ */
133
+ export function hasPolicyFence(fileText) {
134
+ return typeof fileText === 'string' && fileText.includes(POLICY_BLOCK_BEGIN);
135
+ }
136
+ export function measureAgentsMdBudget(fileText) {
137
+ const bytes = Buffer.byteLength(fileText, 'utf8');
138
+ const endChar = fileText.indexOf(POLICY_BLOCK_END);
139
+ const policyBlockEndsAtByte = endChar === -1
140
+ ? -1
141
+ : Buffer.byteLength(fileText.slice(0, endChar + POLICY_BLOCK_END.length), 'utf8');
142
+ return {
143
+ bytes,
144
+ cap: CODEX_PROJECT_DOC_MAX_BYTES,
145
+ pct: Number(((bytes / CODEX_PROJECT_DOC_MAX_BYTES) * 100).toFixed(2)),
146
+ overflow: bytes > CODEX_PROJECT_DOC_MAX_BYTES,
147
+ policyBlockEndsAtByte,
148
+ };
149
+ }
150
+ function parsePolicyStamps(agentsMdText) {
151
+ if (typeof agentsMdText !== 'string')
152
+ return [];
153
+ const begin = agentsMdText.indexOf(POLICY_BLOCK_BEGIN);
154
+ const end = begin === -1 ? -1 : agentsMdText.indexOf(POLICY_BLOCK_END, begin + POLICY_BLOCK_BEGIN.length);
155
+ if (begin === -1 || end === -1)
156
+ return [];
157
+ const inner = agentsMdText.slice(begin + POLICY_BLOCK_BEGIN.length, end);
158
+ const matches = [...inner.matchAll(POLICY_STAMP)];
159
+ return matches.map((match, index) => ({
160
+ id: match[1],
161
+ file: match[2],
162
+ sha: match[3],
163
+ section: inner.slice((match.index ?? 0) + match[0].length, matches[index + 1]?.index ?? inner.length),
164
+ }));
165
+ }
166
+ function markerCount(text, marker) {
167
+ let count = 0;
168
+ let offset = 0;
169
+ while (true) {
170
+ const found = text.indexOf(marker, offset);
171
+ if (found === -1)
172
+ return count;
173
+ count += 1;
174
+ offset = found + marker.length;
175
+ }
176
+ }
177
+ function hasUnclaimedPolicyPrefix(text) {
178
+ const begin = text.indexOf(POLICY_BLOCK_BEGIN);
179
+ const end = begin === -1 ? -1 : text.indexOf(POLICY_BLOCK_END, begin + POLICY_BLOCK_BEGIN.length);
180
+ if (begin === -1 || end === -1)
181
+ return false;
182
+ const inner = text.slice(begin + POLICY_BLOCK_BEGIN.length, end);
183
+ const firstStamp = [...inner.matchAll(POLICY_STAMP)][0];
184
+ return inner.slice(0, firstStamp?.index ?? inner.length).trim().length > 0;
185
+ }
186
+ function hasPolicyStampOutsideFence(text) {
187
+ const begin = text.indexOf(POLICY_BLOCK_BEGIN);
188
+ const end = begin === -1 ? -1 : text.indexOf(POLICY_BLOCK_END, begin + POLICY_BLOCK_BEGIN.length);
189
+ if (begin === -1 || end === -1)
190
+ return [...text.matchAll(POLICY_STAMP)].length > 0;
191
+ const innerStart = begin + POLICY_BLOCK_BEGIN.length;
192
+ return [...text.matchAll(POLICY_STAMP)].some((match) => (match.index ?? -1) < innerStart || (match.index ?? -1) >= end);
193
+ }
194
+ function emittedBody(stamp, source) {
195
+ const normalized = stamp.section.replace(/^\n+/, '').replace(/\n+$/, '');
196
+ const heading = `## ${source.heading}`;
197
+ if (!normalized.startsWith(`${heading}\n`))
198
+ return null;
199
+ const afterHeading = normalized.slice(heading.length).replace(/^\n+/, '');
200
+ const canonical = `\n\nCanonical: ${source.file}`;
201
+ if (!afterHeading.endsWith(canonical))
202
+ return null;
203
+ return normalizePolicyText(afterHeading.slice(0, -canonical.length));
204
+ }
205
+ export function detectPolicyDrift(sourceFiles, agentsMdText, sources = POLICY_SOURCES) {
206
+ const sourceFilesPresent = new Set(sources.map((source) => source.file))
207
+ .size > 0 && sources.some((source) => typeof sourceFiles.get(source.file) === 'string');
208
+ const extracted = extractPolicyBlocks(sourceFiles, sources);
209
+ const blockById = new Map(extracted.blocks.map((block) => [block.id, block]));
210
+ const stamps = parsePolicyStamps(agentsMdText);
211
+ const stampsById = new Map();
212
+ const duplicateStamps = [];
213
+ for (const stamp of stamps) {
214
+ if (stampsById.has(stamp.id))
215
+ duplicateStamps.push(stamp);
216
+ else
217
+ stampsById.set(stamp.id, stamp);
218
+ }
219
+ const findings = [];
220
+ for (const source of sources) {
221
+ const block = blockById.get(source.id);
222
+ const stamp = stampsById.get(source.id);
223
+ if (!block) {
224
+ findings.push({ id: source.id, file: source.file, status: 'missing-anchor', expectedSha: null, actualSha: stamp?.sha ?? null });
225
+ continue;
226
+ }
227
+ if (!stamp) {
228
+ findings.push({ id: source.id, file: source.file, status: 'missing-stamp', expectedSha: block.sha, actualSha: null });
229
+ continue;
230
+ }
231
+ const body = emittedBody(stamp, source);
232
+ const stale = stamp.file !== source.file || stamp.sha !== block.sha || body === null || policyTextSha(body) !== block.sha;
233
+ findings.push({
234
+ id: source.id,
235
+ file: source.file,
236
+ status: stale ? 'stale' : 'ok',
237
+ expectedSha: block.sha,
238
+ actualSha: stamp.sha,
239
+ });
240
+ }
241
+ const sourceIds = new Set(sources.map((source) => source.id));
242
+ for (const stamp of [...stamps.filter((entry) => !sourceIds.has(entry.id)), ...duplicateStamps]) {
243
+ findings.push({ id: stamp.id, file: stamp.file, status: 'orphan-stamp', expectedSha: null, actualSha: stamp.sha });
244
+ }
245
+ if (typeof agentsMdText === 'string') {
246
+ const beginCount = markerCount(agentsMdText, POLICY_BLOCK_BEGIN);
247
+ const endCount = markerCount(agentsMdText, POLICY_BLOCK_END);
248
+ if (beginCount > 1 ||
249
+ endCount > 1 ||
250
+ beginCount !== endCount ||
251
+ hasUnclaimedPolicyPrefix(agentsMdText) ||
252
+ hasPolicyStampOutsideFence(agentsMdText)) {
253
+ findings.push({ id: 'dz:policies', file: 'AGENTS.md', status: 'orphan-stamp', expectedSha: null, actualSha: null });
254
+ }
255
+ }
256
+ return { applicable: sourceFilesPresent, findings };
257
+ }
258
+ //# sourceMappingURL=agents-policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agents-policy.js","sourceRoot":"","sources":["../src/agents-policy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * The emitted Codex helper bodies (`crossrt-2-codex-hooks`, ADR-003 / ADR-004).
3
+ *
4
+ * Generated-code-as-a-string, following the `generateAgentdbWriter` precedent (`setup.ts`): the
5
+ * helpers are THIN and the logic lives in the package, because a changed helper body changes
6
+ * codex's `currentHash` and disarms the entry until it is re-trusted (MEASURED — M0 spike §4).
7
+ * Every byte in here is therefore a liability; keep them boring.
8
+ *
9
+ * ## The contracts, side by side
10
+ *
11
+ * | | `dz-codex-veto.cjs` (PreToolUse) | `dz-codex-recall.cjs` (UserPromptSubmit) |
12
+ * |----------------|-----------------------------------------------------|------------------------------------------|
13
+ * | Polarity | mode-driven on POLICY (default `warn` ⇒ exit 0) | **never-block, always** |
14
+ * | Keys on | presence of `tool_input.command` (AM-8) | `payload.prompt` |
15
+ * | Activation | nearest `.dz` walking up from `payload.cwd` (AM-25) | same |
16
+ * | Our own failure| exit 0, silent, note to `helper-errors.jsonl` | exit 0, empty stdout, no write (AM-9) |
17
+ *
18
+ * `.git` is **not** an activation marker (AM-25): a user-global hook that treats "any git checkout"
19
+ * as opted-in reaches every repository on the machine, including trees nobody pointed at dz.
20
+ *
21
+ * The self-failure note goes to `$CODEX_HOME/dz-hooks/helper-errors.jsonl` (AM-33), never into a
22
+ * project — writing it into `<project>/.dz/` would CREATE a `.dz/` in a foreign repo, which is the
23
+ * exact thing the activation rule exists to prevent.
24
+ *
25
+ * A `UserPromptSubmit` hook that exits 2 **blocks the user's turn**, so the recall helper has no
26
+ * path to a non-zero exit at all.
27
+ *
28
+ * @packageDocumentation
29
+ */
30
+ /**
31
+ * The PreToolUse veto helper.
32
+ *
33
+ * Fail-OPEN on OUR failure, mode-driven on a POLICY hit. Exit 2 only when the project explicitly
34
+ * opted in — the shipped default warns and returns 0 (AM-24 / G-J).
35
+ */
36
+ export declare function generateCodexVetoHelper(): string;
37
+ /**
38
+ * The UserPromptSubmit recall helper.
39
+ *
40
+ * NEVER blocks and NEVER exits non-zero (AM-9). Silence is its correct output when nothing is
41
+ * relevant, which is exactly why the acceptance canary is a FORCED HIT plus a removed-hook twin
42
+ * (AM-4) — a dead hook and a correctly-silent one are indistinguishable from the outside.
43
+ */
44
+ export declare function generateCodexRecallHelper(): string;
45
+ /** Both helper bodies, keyed by the file they are written to. */
46
+ export declare function generateCodexHelpers(): Readonly<Record<'veto' | 'recall', string>>;
47
+ //# sourceMappingURL=codex-hooks-assets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codex-hooks-assets.d.ts","sourceRoot":"","sources":["../src/codex-hooks-assets.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAkGH;;;;;GAKG;AACH,wBAAgB,uBAAuB,IAAI,MAAM,CAoDhD;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,IAAI,MAAM,CA4FlD;AAED,iEAAiE;AACjE,wBAAgB,oBAAoB,IAAI,QAAQ,CAAC,MAAM,CAAC,MAAM,GAAG,QAAQ,EAAE,MAAM,CAAC,CAAC,CAElF"}