@dzhechkov/harness-core 0.8.11 → 0.8.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dz-manifest.json +399 -139
- package/LICENSE +13 -0
- package/README.md +420 -6
- package/dist/agents-policy.d.ts +15 -1
- package/dist/agents-policy.d.ts.map +1 -1
- package/dist/agents-policy.js +27 -1
- package/dist/agents-policy.js.map +1 -1
- package/dist/amendment-trace.d.ts +72 -0
- package/dist/amendment-trace.d.ts.map +1 -1
- package/dist/amendment-trace.js +385 -17
- package/dist/amendment-trace.js.map +1 -1
- package/dist/backlog-public.d.ts +153 -0
- package/dist/backlog-public.d.ts.map +1 -0
- package/dist/backlog-public.js +415 -0
- package/dist/backlog-public.js.map +1 -0
- package/dist/backlog-transitions.d.ts +48 -0
- package/dist/backlog-transitions.d.ts.map +1 -0
- package/dist/backlog-transitions.js +64 -0
- package/dist/backlog-transitions.js.map +1 -0
- package/dist/backlog.d.ts.map +1 -1
- package/dist/backlog.js +13 -0
- package/dist/backlog.js.map +1 -1
- package/dist/claim-check.d.ts.map +1 -1
- package/dist/claim-check.js +24 -2
- package/dist/claim-check.js.map +1 -1
- package/dist/claude-hooks-assets.d.ts +93 -0
- package/dist/claude-hooks-assets.d.ts.map +1 -0
- package/dist/claude-hooks-assets.js +208 -0
- package/dist/claude-hooks-assets.js.map +1 -0
- package/dist/clean-room-smoke.d.ts +83 -0
- package/dist/clean-room-smoke.d.ts.map +1 -0
- package/dist/clean-room-smoke.js +138 -0
- package/dist/clean-room-smoke.js.map +1 -0
- package/dist/cmd-usage.d.ts.map +1 -1
- package/dist/cmd-usage.js +36 -6
- package/dist/cmd-usage.js.map +1 -1
- package/dist/codex-hooks-assets.d.ts +25 -7
- package/dist/codex-hooks-assets.d.ts.map +1 -1
- package/dist/codex-hooks-assets.js +138 -30
- package/dist/codex-hooks-assets.js.map +1 -1
- package/dist/codex-hooks.d.ts +21 -1
- package/dist/codex-hooks.d.ts.map +1 -1
- package/dist/codex-hooks.js +21 -1
- package/dist/codex-hooks.js.map +1 -1
- package/dist/course-staleness.d.ts +19 -0
- package/dist/course-staleness.d.ts.map +1 -0
- package/dist/course-staleness.js +95 -0
- package/dist/course-staleness.js.map +1 -0
- package/dist/destructive-guard-hook.d.ts +40 -0
- package/dist/destructive-guard-hook.d.ts.map +1 -0
- package/dist/destructive-guard-hook.js +109 -0
- package/dist/destructive-guard-hook.js.map +1 -0
- package/dist/destructive-guard.d.ts +27 -0
- package/dist/destructive-guard.d.ts.map +1 -0
- package/dist/destructive-guard.js +2808 -0
- package/dist/destructive-guard.js.map +1 -0
- package/dist/discrimination-gate.d.ts +28 -3
- package/dist/discrimination-gate.d.ts.map +1 -1
- package/dist/discrimination-gate.js +76 -16
- package/dist/discrimination-gate.js.map +1 -1
- package/dist/feature-adr-routing.d.ts +95 -1
- package/dist/feature-adr-routing.d.ts.map +1 -1
- package/dist/feature-adr-routing.js +193 -27
- package/dist/feature-adr-routing.js.map +1 -1
- package/dist/guard.d.ts +90 -0
- package/dist/guard.d.ts.map +1 -1
- package/dist/guard.js +271 -0
- package/dist/guard.js.map +1 -1
- package/dist/harness-core-location.d.ts +18 -0
- package/dist/harness-core-location.d.ts.map +1 -0
- package/dist/harness-core-location.js +42 -0
- package/dist/harness-core-location.js.map +1 -0
- package/dist/index.d.ts +24 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +24 -3
- package/dist/index.js.map +1 -1
- package/dist/lead-shift-gate.d.ts +95 -0
- package/dist/lead-shift-gate.d.ts.map +1 -0
- package/dist/lead-shift-gate.js +100 -0
- package/dist/lead-shift-gate.js.map +1 -0
- package/dist/ledger-backfill.d.ts +11 -1
- package/dist/ledger-backfill.d.ts.map +1 -1
- package/dist/ledger-backfill.js +19 -0
- package/dist/ledger-backfill.js.map +1 -1
- package/dist/loop-blobs.generated.d.ts +1 -1
- package/dist/loop-blobs.generated.d.ts.map +1 -1
- package/dist/loop-blobs.generated.js +12 -3
- package/dist/loop-blobs.generated.js.map +1 -1
- package/dist/loop-lint.d.ts.map +1 -1
- package/dist/loop-lint.js +56 -7
- package/dist/loop-lint.js.map +1 -1
- package/dist/loop-plan-graph.d.ts +1 -3
- package/dist/loop-plan-graph.d.ts.map +1 -1
- package/dist/loop-plan-graph.js +70 -1
- package/dist/loop-plan-graph.js.map +1 -1
- package/dist/loop-trace.d.ts.map +1 -1
- package/dist/loop-trace.js +16 -2
- package/dist/loop-trace.js.map +1 -1
- package/dist/managed-hooks.d.ts +5 -6
- package/dist/managed-hooks.d.ts.map +1 -1
- package/dist/managed-hooks.js +2 -2
- package/dist/managed-hooks.js.map +1 -1
- package/dist/model-recommender.d.ts.map +1 -1
- package/dist/model-recommender.js +14 -3
- package/dist/model-recommender.js.map +1 -1
- package/dist/operations.d.ts.map +1 -1
- package/dist/operations.js +82 -0
- package/dist/operations.js.map +1 -1
- package/dist/patterns.d.ts +23 -0
- package/dist/patterns.d.ts.map +1 -1
- package/dist/patterns.js +10 -0
- package/dist/patterns.js.map +1 -1
- package/dist/publish.d.ts +11 -0
- package/dist/publish.d.ts.map +1 -1
- package/dist/publish.js +16 -2
- package/dist/publish.js.map +1 -1
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +3 -2
- package/dist/registry.js.map +1 -1
- package/dist/score.d.ts.map +1 -1
- package/dist/score.js +15 -4
- package/dist/score.js.map +1 -1
- package/dist/session-retro.d.ts +121 -2
- package/dist/session-retro.d.ts.map +1 -1
- package/dist/session-retro.js +454 -11
- package/dist/session-retro.js.map +1 -1
- package/dist/setup.d.ts +28 -0
- package/dist/setup.d.ts.map +1 -1
- package/dist/setup.js +223 -6
- package/dist/setup.js.map +1 -1
- package/dist/sign.d.ts.map +1 -1
- package/dist/sign.js +18 -1
- package/dist/sign.js.map +1 -1
- package/dist/skills-verify.d.ts +34 -1
- package/dist/skills-verify.d.ts.map +1 -1
- package/dist/skills-verify.js +82 -0
- package/dist/skills-verify.js.map +1 -1
- package/dist/stage-line.d.ts +68 -0
- package/dist/stage-line.d.ts.map +1 -0
- package/dist/stage-line.js +129 -0
- package/dist/stage-line.js.map +1 -0
- package/dist/statusline.d.ts +99 -0
- package/dist/statusline.d.ts.map +1 -1
- package/dist/statusline.js +310 -44
- package/dist/statusline.js.map +1 -1
- package/dist/store-counts.d.ts +26 -0
- package/dist/store-counts.d.ts.map +1 -0
- package/dist/store-counts.js +125 -0
- package/dist/store-counts.js.map +1 -0
- package/dist/store-guard.d.ts +106 -0
- package/dist/store-guard.d.ts.map +1 -0
- package/dist/store-guard.js +294 -0
- package/dist/store-guard.js.map +1 -0
- package/dist/swarm-brief.d.ts +95 -0
- package/dist/swarm-brief.d.ts.map +1 -0
- package/dist/swarm-brief.js +660 -0
- package/dist/swarm-brief.js.map +1 -0
- package/dist/trace-bundle.d.ts +8 -0
- package/dist/trace-bundle.d.ts.map +1 -1
- package/dist/trace-bundle.js +11 -0
- package/dist/trace-bundle.js.map +1 -1
- package/package.json +12 -11
- package/sbom.json +804 -154
- package/src/agents-policy.ts +46 -2
- package/src/amendment-trace.ts +441 -19
- package/src/backlog-public.ts +503 -0
- package/src/backlog-transitions.ts +77 -0
- package/src/backlog.ts +12 -0
- package/src/claim-check.ts +25 -2
- package/src/claude-hooks-assets.ts +227 -0
- package/src/clean-room-smoke.ts +195 -0
- package/src/cmd-usage.ts +29 -5
- package/src/codex-hooks-assets.ts +140 -30
- package/src/codex-hooks.ts +21 -1
- package/src/course-staleness.ts +125 -0
- package/src/destructive-guard-hook.ts +151 -0
- package/src/destructive-guard.ts +3027 -0
- package/src/discrimination-gate.ts +98 -19
- package/src/feature-adr-routing.ts +220 -22
- package/src/guard.ts +318 -0
- package/src/harness-core-location.ts +44 -0
- package/src/index.ts +111 -3
- package/src/lead-shift-gate.ts +145 -0
- package/src/ledger-backfill.ts +20 -1
- package/src/loop-blobs.generated.ts +12 -3
- package/src/loop-lint.ts +52 -7
- package/src/loop-plan-graph.ts +66 -1
- package/src/loop-trace.ts +13 -1
- package/src/managed-hooks.ts +5 -6
- package/src/model-recommender.ts +14 -3
- package/src/operations.ts +75 -0
- package/src/patterns.ts +33 -0
- package/src/publish.ts +27 -2
- package/src/registry.ts +3 -2
- package/src/score.ts +16 -4
- package/src/session-retro.ts +466 -11
- package/src/setup.ts +246 -9
- package/src/sign.ts +18 -1
- package/src/skills-verify.ts +99 -1
- package/src/stage-line.ts +151 -0
- package/src/statusline.ts +396 -47
- package/src/store-counts.ts +154 -0
- package/src/store-guard.ts +388 -0
- package/src/swarm-brief.ts +661 -0
- package/src/trace-bundle.ts +10 -0
package/LICENSE
CHANGED
|
@@ -19,3 +19,16 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
19
19
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
20
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
21
|
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
Third-party licenses
|
|
26
|
+
|
|
27
|
+
This package bundles the frontend-design skill template, which is licensed
|
|
28
|
+
under the Apache License, Version 2.0 (not MIT). The full Apache-2.0 license
|
|
29
|
+
text ships alongside the skill at:
|
|
30
|
+
|
|
31
|
+
templates/.claude/skills/frontend-design/LICENSE.txt
|
|
32
|
+
|
|
33
|
+
All other contents of this package are covered by the MIT license above.
|
|
34
|
+
The package's SPDX license expression is: MIT AND Apache-2.0
|
package/README.md
CHANGED
|
@@ -3,6 +3,45 @@
|
|
|
3
3
|
Shared logic for the DZ harness — the engine behind `@dzhechkov/harness-cli`
|
|
4
4
|
and any other consumer.
|
|
5
5
|
|
|
6
|
+
## Per-turn admission debt (`session-retro.ts`)
|
|
7
|
+
|
|
8
|
+
The engine behind `dz retro` and `dz retro --scan-tail`: it turns a session transcript into events,
|
|
9
|
+
detects recurring PROCESS rakes, and folds an **admission debt** — an error narrated in chat with no
|
|
10
|
+
`dz teach` behind it.
|
|
11
|
+
|
|
12
|
+
Public surface used by the CLI: `streamSessionEvents`, `parseSessionJsonl`, `detectProcessRakes`,
|
|
13
|
+
`buildRetro`, `renderRetro`, `renderDrill`, `retroLessonText`, `foldAdmissionDebt`, `runRetroTailScan`,
|
|
14
|
+
`retroSentinelIsFresh`, `renderRetroDebtDirective`, `findLatestTranscript`, **`resolveScanTailTranscript`**
|
|
15
|
+
(new), plus the types `SessionEvent`, `RetroPendingSentinel`, `TailScanOutcome` and **`ScanTailSource`**
|
|
16
|
+
(new), and the constants `RETRO_DEBT_MARKER`, `RETRO_PENDING_FILE`, `RETRO_SCAN_STATE_FILE`,
|
|
17
|
+
`RETRO_SCAN_LOCK_NAME`, `PROCESS_SIGNATURES`, `DEFAULT_DRILL_THRESHOLD`.
|
|
18
|
+
|
|
19
|
+
`SessionEvent` carries an optional `toolUseId` — `tool_use.id` on a call, `tool_result.tool_use_id` on
|
|
20
|
+
its result — which is the pairing key the debt fold needs to tell WHICH command a result belongs to.
|
|
21
|
+
|
|
22
|
+
Load-bearing properties, each pinned by a test that goes RED when the property is mutated out
|
|
23
|
+
(`test/session-retro.test.ts`, `test/retro-scan-tail-source.test.ts`, registry ids in
|
|
24
|
+
`test/mutation-registry.json`):
|
|
25
|
+
- **Block order survives parsing.** An admission text block is emitted BEFORE the `tool_use` of the same
|
|
26
|
+
message, so admitting and teaching in one turn reads as settled (`retro-p1-1-block-order`).
|
|
27
|
+
- **Only an executed Bash teach can pay.** A `tool_result` echoing the phrase, an `echo`/`grep` decoy, or
|
|
28
|
+
a non-Bash tool call never settles anything (`retro-p1-2-bash-only-teach`).
|
|
29
|
+
- **A newline is a command boundary.** The dominant field form (`DZ=…\n$DZ teach … --project $B`) pays;
|
|
30
|
+
every decoy still stays armed (`retro-adr5-newline-boundary`).
|
|
31
|
+
- **The RECEIPT settles, not the command text.** A teach with a `tool_use_id` is registered by its call
|
|
32
|
+
and cleared only by that call's own result carrying a line `dz teach` prints on a real write — so
|
|
33
|
+
`exit 0\ndz teach "never runs"` pays nothing (`retro-r1-teach-receipt-required`).
|
|
34
|
+
- **Every awaiting teach is retained.** Two parallel teach calls cannot cancel each other out; if both
|
|
35
|
+
results come back receipt-less the debt stays armed (`retro-r3-retain-awaiting-teaches`).
|
|
36
|
+
- **An admission is a confession, not a bug-fix report.** The Russian branch is an allowlist of verb and
|
|
37
|
+
adverb forms, so «Я ошибку валидации исправил» — a NOUN in a completion report — arms nothing
|
|
38
|
+
(`retro-r3-admission-verb-only`).
|
|
39
|
+
- **The scan never guesses its transcript.** `resolveScanTailTranscript` takes an explicit flag, then a
|
|
40
|
+
positional path, then the Stop hook's stdin `transcript_path`; with none it returns
|
|
41
|
+
`{path: null, reason}` rather than the newest file on disk.
|
|
42
|
+
- **No lost update.** The whole read→fold→write tail-scan transaction runs under the `retro-scan` named
|
|
43
|
+
lock beside the store it guards; contention advances nothing (`retro-p1-3-unlocked-scan`).
|
|
44
|
+
|
|
6
45
|
## Lesson payoff (bandit re-rank)
|
|
7
46
|
|
|
8
47
|
`lesson-bandit.ts` / `lesson-payoff.ts` add a **payoff axis** to lesson recall: a Beta posterior per
|
|
@@ -54,6 +93,8 @@ explicit skills-only short circuit. `--no-verify` cannot authorize emission. A C
|
|
|
54
93
|
| `sign` | `listPackFiles`, `listSignablePackFiles`, `verifyManifest`, `verifySbomAgainstManifest` | Shared node_modules/.git exclusions; verify sees MORE than sign (smuggled symlinks still fail). After authenticating the Ed25519 manifest, verification derives the canonical CycloneDX document from those signed entries and requires the no-follow `sbom.json` read to match it exactly. Current/v3 signing refuses malformed, duplicate-key, or precision-losing root `package.json` JSON and preserves object order throughout `exports`, `imports`, and `typesVersions`, so condition-order entry-point changes cannot hide behind packer-noise canonicalisation. Readers retain v1/v2 compatibility |
|
|
55
94
|
| `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 |
|
|
56
95
|
| `slop-lint` | `slopLint`, `parseSlopRegistry`, `validateSlopLintConfig`, `DEFAULT_SLOP_CONFIG`, `BUNDLED_SLOP_REGISTRY_URL` | Pure deterministic EN/RU lexical-density and structural-style analysis behind advisory `dz lint`. It excludes protected Markdown, requires at least two distinct registered marker IDs in one paragraph, divides marker hits by `max(visibleWords, wordFloor)`, and reports bullet walls or registered three-adjective stacks independently. Under the default `4`/`2`/`25` policy, the distinct-ID floor owns paragraphs through 50 words and density is the dilution cap from 51 words onward. The core performs no file, network, clock, locale, or process I/O; policy/config failures are typed diagnostics rather than empty clean results. |
|
|
96
|
+
| `stem` | `tokenize`, `stemToken`, `stems` | Zero-dependency EN/RU word-form normalisation (light suffix stripping applied to BOTH sides of a match) behind registry search and `recommend`, so «анализы» finds «анализ»; a RU topic dictionary maps Russian queries onto catalogue topics, and an unmapped topic is reported as a miss rather than silently widened. |
|
|
97
|
+
| `course-staleness` | `classifyCourseStaleness`, `CourseStalenessState`, `CourseStalenessInput`, `CourseStalenessResult` | Pure tutorial/package parity classifier. It distinguishes `S0 SHIPPED`, `S3 TUTORIAL_STALE`, `S4 PACKAGE_BEHIND`, malformed/mismatched/unknown registry inputs, and—load-bearing—`E2 UNSTAMPED`; an absent source stamp can never collapse into shipped. The caller supplies registry facts, so classification performs no file, process, clock, or network I/O. |
|
|
57
98
|
| `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 |
|
|
58
99
|
| `no-stubs` | `scanStubs`, `checkNoStubs`, `scannableStubPath`, `STUB_MARKERS`, `STUB_PHRASES`, `STUB_SCAN_EXTENSIONS` | Pure unfinished-stub scanner behind the SOFT `no-stubs` publish rule (backlog 0b403a0106103901, Karpathy-Michaels rule XI): bare markers (`TODO`/`FIXME`/`HACK`/`XXX`/`PLACEHOLDER`) case-SENSITIVE with hard word boundaries (`hackathon`/`todos`/a marker inside a hash never fire; MEASURED: relaxing case doubles this repo's hits and adds only prose) + the `implement later` phrase case-insensitive. SCOPE = the CHANGE-SET (the working-tree `git status --porcelain -uall` diff — `-uall` so a brand-new untracked DIRECTORY is scanned file-by-file instead of collapsing to one invisible `?? newdir/` line; `.gitignore` semantics unchanged), never the whole tree — MEASURED: a tree-wide scan is 32+25 hits of mostly ancient legitimate markers, i.e. noise that gets a gate switched off. Markdown gets PROSE scoping (fenced blocks + backticked spans are QUOTES, not stubs). Waiver-with-REASON only, per line (`no-stubs: <reason>`) or per path (`.dz/guard.json` `stubWaivers`, the feature-adr-setup --guards shape); a reasonless waiver is REFUSED as its own finding and exempts nothing. Self-exemption is STRUCTURAL: every marker in the module and its tests is assembled from string fragments, so the gate's own source scans clean — a tested property, not a path skip. Fail-open on missing evidence (no change fact / ungathered contents ⇒ nothing reported) but never fail-SILENT: skipped scannable files (deleted/oversize/unreadable/beyond the file cap) surface as ONE aggregate `notes` entry in the `GuardResult` + audit record — information that can never move the verdict. KNOWN LIMITS are documented at the top of `no-stubs.ts` instead of implied away (whole-line inline waiver token = layer-4 auditability defence; reason QUALITY not judged; boolean fence model, not CommonMark; git-quoted paths undecoded; TS-monorepo extension allowlist; worktree-not-index reads; exact-string config-waiver paths). Mutation-defended (`no-stubs-bare-marker-fires` observed 10 red, `no-stubs-skipped-note-emitted` observed 2 red) |
|
|
59
100
|
| `feature-adr-setup` (P3) | `renderGuardsConfig`, `renderGuardsRunner` | Scaffolds deterministic guard tests into a TARGET project: `guards.config.json` + a zero-dependency `check.mjs` runner (loc-cap, secret-scan, frozen-file sha256 pins, waivers-with-reasons) — `dz feature-adr-setup --guards` |
|
|
@@ -69,7 +110,7 @@ explicit skills-only short circuit. `--no-verify` cannot authorize emission. A C
|
|
|
69
110
|
| `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 — сырьё для будущей локальной модели |
|
|
70
111
|
| `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: снятый под лимитом гард «кодер не ревьюит сам себя» становится долгом на диске, а не памяткой |
|
|
71
112
|
| `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 |
|
|
72
|
-
| `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 |
|
|
113
|
+
| `statusline` | `statuslineData`, `readFeatureAdrState`, `writeFeatureAdrState`, `writeFeatureAdrStateDetailed`, `renderFeatureAdrPhaseLine`, `featureAdrStateDir`, `featureAdrStatePath`, `FeatureAdrState`, `WriteFeatureAdrStateInput`, `WriteFeatureAdrStateResult` | 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 |
|
|
73
114
|
| `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 |
|
|
74
115
|
| `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 |
|
|
75
116
|
| `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 |
|
|
@@ -232,6 +273,50 @@ inconclusive until re-probed). Both are load-bearing:
|
|
|
232
273
|
`runtime: 'claude-code' | 'codex'` (absent ⇒ `claude-code`), and the compaction aggregate carries a
|
|
233
274
|
`runtimes` set union so provenance survives the lossy path.
|
|
234
275
|
|
|
276
|
+
## Destructive-command guard (`classifyDestructive` / `decideDestructiveHook`)
|
|
277
|
+
|
|
278
|
+
A pre-execution veto on the ONE class of loss the harness has actually suffered: a literal shell
|
|
279
|
+
deletion aimed at its own stores. `classifyDestructive(command)` is pure and returns one of three
|
|
280
|
+
verdicts — `refuse` (a deletion verb with a literal operand inside `.dz/`, `.agentic-qe/`, or a
|
|
281
|
+
database file such as `*.db` / `*.sqlite`), `allow`, or `undecidable` (the operand is built by the
|
|
282
|
+
shell: `$var`, `$(…)`, globs, a `bash -c` string that itself expands something). Every refusal names
|
|
283
|
+
the path AND the rule id from `DESTRUCTIVE_RULES`; `undecidable` is printed, never silently mapped
|
|
284
|
+
to either side.
|
|
285
|
+
|
|
286
|
+
The scope is narrow by DECIDABILITY, not by taste — MEASURED by
|
|
287
|
+
`bash scratchpad/corpus2.sh r17` on a 20 938-command corpus of real session commands: 35 refusals
|
|
288
|
+
(0.167 %), 45 undecidable, the rest allowed; both verdict lists are byte-identical to R16. Four
|
|
289
|
+
limits are printed with every verdict
|
|
290
|
+
so nobody reads more into it than it does: (1) it sees the command text, never the filesystem;
|
|
291
|
+
(2) an operand assembled at runtime is `undecidable`; (3) only the four deletion verbs `rm`, `rmdir`, `unlink`, `shred` are heads — `git rm`, `find -delete`
|
|
292
|
+
and a `>` truncation are deliberately outside the scope;
|
|
293
|
+
(4) quotes are decoded ONLY under a deletion verb or a table-declared shell command carrier:
|
|
294
|
+
shell `-c`, npm/npx `-c`/`--call`, or pnpm's global `-c`/`--shell-mode` before `exec`. Under any
|
|
295
|
+
other head the quoted text is text ABOUT a command, and heredoc / comment bodies are never read.
|
|
296
|
+
|
|
297
|
+
Wrapper command location is declared once in `COMMAND_WRAPPER_STRATEGIES`: first positional argv,
|
|
298
|
+
option value, shell `-c` string, or named external script, together with `execution: argv|shell`.
|
|
299
|
+
R17 removed the two parallel wrapper registries that let npm/npx/pnpm shell carriers fall between
|
|
300
|
+
branches. The same round restores function bodies after brace-expanding call words and respects
|
|
301
|
+
`POSIXLY_CORRECT` when deciding whether a late `--help` is a mode or an operand.
|
|
302
|
+
|
|
303
|
+
`decideDestructiveHook(payload, host)` in `destructive-guard-hook.ts` is the host adapter. Both
|
|
304
|
+
hosts call the SAME function: Claude Code through `.claude/hooks/destructive-guard.cjs`
|
|
305
|
+
(`PreToolUse` on `Bash`, exit 2 + `DZ-DESTRUCTIVE-REFUSE:` on stderr), and Codex through the
|
|
306
|
+
emitted veto helper (`hooks-sync --target codex`, helper version 6). The helper loads the decider
|
|
307
|
+
with `import()` — on Node < 20.19 a CommonJS `require()` of this ESM package throws
|
|
308
|
+
`ERR_REQUIRE_ESM`, and the previous body turned that into a silent exit 0 (MEASURED: exit 0, empty
|
|
309
|
+
stderr). A decider that fails to load now prints ONE `DZ-DESTRUCTIVE-WARN: guard not loaded —
|
|
310
|
+
<reason>` line and records `destructive-not-loaded` in `helper-errors.jsonl`; it still fails open,
|
|
311
|
+
but never quietly.
|
|
312
|
+
|
|
313
|
+
The registry currently contains 121 `guard-*` entries. R17's exhaustive anchor census identifies
|
|
314
|
+
the anchors displaced by the source refactor; new/repointed entries are staged separately under
|
|
315
|
+
`scratchpad/` until the owner lands the registry mutation. `dz mutation-gate` proves each accepted
|
|
316
|
+
protection by making at least one named test red; each
|
|
317
|
+
protection, when deleted from the source, turns at least one named test red. Design record:
|
|
318
|
+
`features/destructive-command-guard/03_adr/001-narrow-by-decidability.md`.
|
|
319
|
+
|
|
235
320
|
## Run a plan without the Claude host
|
|
236
321
|
|
|
237
322
|
`runWorkflow` (`workflow-run.ts`) is the PURE scheduler behind `dz workflow run`: it INTERPRETS a
|
|
@@ -385,14 +470,185 @@ decision domain: justification is neither scored nor offered as a trimming targe
|
|
|
385
470
|
|
|
386
471
|
## Status
|
|
387
472
|
|
|
388
|
-
`0.8.
|
|
473
|
+
`0.8.12` — **staged, not published.** The `/feature-adr` phase panel + per-phase ledger telemetry,
|
|
474
|
+
with the four cross-family review findings of the feature's first landing closed with proof: a
|
|
475
|
+
monotone step guard on the write path, the `fa-phase-slot` named lock around the whole slot
|
|
476
|
+
transition (plus a refusal REASON the CLI can print), and `kind`-carrying ledger rows made invisible
|
|
477
|
+
to every cost reader (`planLedgerBackfill`, `selectLedgerRows`, `assembleTimeline`). See the phase
|
|
478
|
+
telemetry paragraph above. Seven named guards in this package are `DEFENDED` under `dz mutation-gate`.
|
|
479
|
+
|
|
480
|
+
Unreleased — adds the pure `course-staleness` classifier. Missing provenance is explicitly
|
|
481
|
+
`E2 UNSTAMPED` and is tested not to equal `S0 SHIPPED`; version ordering uses the existing semver
|
|
482
|
+
comparator while all registry and filesystem I/O remains outside the classifier.
|
|
483
|
+
|
|
484
|
+
`0.8.15` — **staged, not published.** `amendment-trace.ts` now keeps the `CP-` prefix in the
|
|
485
|
+
amendment id: `AM-CP-N` and `AM-N` are DISTINCT ids, so a challenge-panel row appended by the
|
|
486
|
+
feature-adr workflow can no longer collide with the ideation's `AM-N`. Until this release the prefix
|
|
487
|
+
was matched by a non-capturing group and thrown away at id construction, and the subject guard —
|
|
488
|
+
which exists because comparing ids alone let a plan swap one change for another under the same id —
|
|
489
|
+
fired on plans whose subject WAS carried verbatim. MEASURED TWICE on 2026-09-05, on two independent
|
|
490
|
+
worktrees: the authors of `run-registry-liveness` and `core-boundary-guard` each re-numbered or
|
|
491
|
+
refused the `AM-CP-N` form to get past the instrument. K2 (`check-plan-completeness.mjs`) has always
|
|
492
|
+
kept the whole token and REQUIRES those rows inside `## Amendments`, so the pipeline's own gate was
|
|
493
|
+
forcing rows into the position this module misread — two tools, one text, two contracts. The
|
|
494
|
+
`amendmentSubject` furniture stripper learned the prefix too, so a CP row's subject is its text
|
|
495
|
+
rather than its own id. **JSON surface (`dz amendment-check --json`):** no field is renamed, but a
|
|
496
|
+
consumer that keyed on `AM-\d+` will now see the id VALUE `AM-CP-<n>` where a colliding `AM-<n>`
|
|
497
|
+
used to appear. Backlog `a7d0aece023774a0`; the prefix-drop mutant is registered as
|
|
498
|
+
`amendment-trace-cp-prefix-is-identity` and reported PROVEN with 7 failing tests under
|
|
499
|
+
`dz mutation-gate --only amendment-trace-cp-prefix-is-identity --test-cmd "npx vitest run
|
|
500
|
+
test/amendment-trace.test.ts test/amendment-grammar-agreement.test.ts test/score.test.ts"`. The
|
|
501
|
+
registry-wide `testCommand` reports the SAME failing count and answers INCONCLUSIVE on a loaded
|
|
502
|
+
machine for this entry AND for an untouched control entry, both for the same reason —
|
|
503
|
+
`test/eta.test.ts` times out at 5 s in the re-baseline — so that verdict is a property of the
|
|
504
|
+
runner, not of this protection.
|
|
505
|
+
|
|
506
|
+
**QE fix round (same `0.8.15`, cross-family review 2026-09-06).** The release note above claimed to
|
|
507
|
+
end the two-reader disagreement; the review found a THIRD reader with a THIRD contract, and it is
|
|
508
|
+
closed here. (1) `score.ts` scanned plans and QE reports with its own `/AM-\d+/g`, which does not
|
|
509
|
+
match `AM-CP-1` at all (MEASURED — `'AM-CP-1'.match(/AM-\d+/g)` → `null`), so a plan whose
|
|
510
|
+
amendments were all challenge-panel rows scored an EMPTY planned set and the whole
|
|
511
|
+
`amendment-confirmation` discipline was skipped with no `absent` and no `partial` — a check that
|
|
512
|
+
silently checked nothing. Coverage one line below was `qeText.includes(id)`, so a planned `AM-1` read
|
|
513
|
+
as covered by a report that only ever mentions `AM-10` (MEASURED — `'AM-10'.includes('AM-1')` →
|
|
514
|
+
`true`). Both are closed by ONE exported reader, `amendmentIdsIn` / `mentionsAmendmentId`, living
|
|
515
|
+
beside the row grammar in `amendment-trace.ts` and pinned against K2's token by
|
|
516
|
+
`test/amendment-grammar-agreement.test.ts`; narrowing it back is the registered mutant
|
|
517
|
+
`amendment-token-cp-prefix-shared-reader` (PROVEN, 4 failing). (2) `decideAmendmentOutcome` now tells
|
|
518
|
+
an explicit "None" from a silent grammar failure: a `## Amendments` section that declares
|
|
519
|
+
`None`/`нет`/`n/a` and parses zero rows is a **skip** (exit 0, stated reason), where it used to
|
|
520
|
+
return NOT-ESTABLISHED (exit 3) on a plan that was complete — MEASURED on this feature's own plan
|
|
521
|
+
(backlog `ce2da797e17a7a7f`). The skip is GUARDED: it never fires while an ideation amendment the
|
|
522
|
+
plan dropped is outstanding, so an absence can still never silence a real gap. Blast radius over the
|
|
523
|
+
363-feature census: exactly 2 features move `not-established` → `skip`
|
|
524
|
+
(`amendment-trace-cp-prefix`, `doctor-insight-flow-check`), both plans declaring None in prose. (3)
|
|
525
|
+
**Cross-family round 2** found the explicit-None contract half-honoured: the pipeline's own Step-8
|
|
526
|
+
module documents the INLINE form `## Amendments: None`
|
|
527
|
+
(`.claude/skills/feature-adr/modules/08-qe.md:166`), and there `amendmentSection` ate `: None` as
|
|
528
|
+
part of the heading and returned an EMPTY body, so the skip branch never ran and the gate still
|
|
529
|
+
answered NOT-ESTABLISHED (exit 3) — MEASURED on the built module: section `""`, `saysNone false`,
|
|
530
|
+
exit 3. The declaration is now read from BOTH homes, the section body and the heading suffix, and it
|
|
531
|
+
must BE a declaration: a separator (`:` or a dash), then a WHOLE remainder from the closed set
|
|
532
|
+
`None`/`N/A`/`нет`, optionally with a full stop. Before that exact comparison, an optional
|
|
533
|
+
whitespace-separated CommonMark closing hash sequence is removed as heading furniture. That rule
|
|
534
|
+
was chosen from the corpus, not invented — the 363 features carry
|
|
535
|
+
`## Amendments (carried verbatim into the plan)` (8×), `## Amendments — conditions before
|
|
536
|
+
plan/code`, `## Amendments applied before Step 6`, `## Amendments and confirmation obligations`, and
|
|
537
|
+
none of them may read as "none". Registered mutant `amendment-explicit-none-on-the-heading`
|
|
538
|
+
(refreshed against the current return path; PROVEN, 5 failing). Census: **zero** features use the inline form today, so the tally is unchanged
|
|
539
|
+
at `fail 48 · pass 82 · skip 200 · not-established 33` over 363 — this half of the fix honours a
|
|
540
|
+
DOCUMENTED contract rather than moving a live verdict. (7) **Cross-family round 6** replaced the mask
|
|
541
|
+
with a BLOCK READER, and the reason is the shape of the previous five rounds rather than any single
|
|
542
|
+
defect: `<!--` and `-->` written inside INLINE CODE SPANS in ordinary prose were read as a comment
|
|
543
|
+
spanning a real section, so `amendmentSection` returned null and the gate answered skip/exit 0
|
|
544
|
+
(MEASURED). Each mask had been born to close the previous one's hole — the signature of reading at
|
|
545
|
+
the wrong level. `maskNonRendered` is now one length-preserving block scan implementing CommonMark
|
|
546
|
+
§4.5 (fenced code), §4.6 type 2 (`<!-- … -->`, whose START CONDITION is line-start after at most
|
|
547
|
+
three spaces — which is what makes a code-spanned delimiter a non-event by construction) and §4.2
|
|
548
|
+
(headings matched only in what survives); §6.1 code spans are deliberately not parsed, because no
|
|
549
|
+
inline construct may open a block. NOT implemented, so the gap is auditable: indented code blocks,
|
|
550
|
+
HTML block types 1 and 3–7, block quotes and list containers, tabs as indentation, link reference
|
|
551
|
+
definitions. One deliberate deviation: an UNCLOSED block is reverted rather than run to end of
|
|
552
|
+
document, because hiding a real section would turn the gate into exit 0. Also in this round: a
|
|
553
|
+
document that opens more than one RENDERED `## Amendments` section at the same depth or shallower is
|
|
554
|
+
NOT-ESTABLISHED with a named reason, never a skip — a stale `## Amendments: None` above the real
|
|
555
|
+
section used to answer for it. The depth rule is corpus-derived: counting a `###` subsection as a
|
|
556
|
+
rival moved `p16-non-js-portability` from an honest `fail` to `not-established`, which is a
|
|
557
|
+
regression dressed as caution. Registered mutants `amendment-comment-start-is-a-block-condition` and
|
|
558
|
+
`amendment-duplicate-sections-are-not-established` (PROVEN, 1 failing each); fourteen `amendment-*`
|
|
559
|
+
entries are 12/12 proven with 0 drops among 12/12 anchored. STILL OPEN and named: K2
|
|
560
|
+
(`check-plan-completeness.mjs`) keeps the old any-run-of-three fence closer, so on a `````md` fence
|
|
561
|
+
containing a ```` ``` ```` line K2 now FAILS a plan this checker reads correctly — one reader for both
|
|
562
|
+
consumers is a feature, not a fix round (six copies, three published packages, standalone
|
|
563
|
+
execution). (4) **Cross-family round 3** found the skip
|
|
564
|
+
FORGEABLE and one of its spellings unreachable, both in the same reader. A plan may SHOW the form it
|
|
565
|
+
is allowed to write, and a fenced `## Amendments: None` above the real section won the raw heading
|
|
566
|
+
search: the fence's own body parsed as zero rows and the gate exited 0 WITHOUT LOOKING at the real
|
|
567
|
+
section, which carried an unresolvable `AM-1` — the new false pass NFR-3 forbids. Headings, section
|
|
568
|
+
boundaries and rows are now found in a length-preserving FENCE MASK and sliced from the original, so
|
|
569
|
+
an example illustrates the form and never answers for the document; an UNCLOSED fence is reverted
|
|
570
|
+
rather than trusted, because losing an example is cheap and hiding a real section would manufacture
|
|
571
|
+
the very skip the mask exists to prevent. In the same reader, JavaScript's `\b` was wrong in BOTH
|
|
572
|
+
directions at once: Cyrillic is not `\w`, so `нет` was REJECTED against the contract these READMEs
|
|
573
|
+
print, while a hyphen IS a word boundary, so a body opening `none-blocking follow-ups` was ACCEPTED
|
|
574
|
+
and exited 0. The current reader is stronger: the whole heading remainder or first paragraph must
|
|
575
|
+
match the closed set, so both longer-word and qualified-sentence forms are refused.
|
|
576
|
+
Registered mutants `amendment-explicit-none-not-forgeable-by-a-fence` (PROVEN, 2 failing) and
|
|
577
|
+
the stable registry id `amendment-none-declaration-unicode-guard` (retargeted from the deleted
|
|
578
|
+
constant to the current exact reader; PROVEN, 6 failing). Census after: 363 features,
|
|
579
|
+
`fail 48 · pass 82 · skip 200 · not-established 33` — and this time not one feature moved even in
|
|
580
|
+
its per-verdict COUNTS, so the masking cost the corpus nothing. (5) **Cross-family round 4** found two defects
|
|
581
|
+
in that fence mask itself, both again exit-0 answers over unresolved work. The mask was used for
|
|
582
|
+
FINDING and not for READING: row starts were located in the mask while each row's text was sliced
|
|
583
|
+
from the original, so a testless `AM-1` followed by a fenced example carrying a complete
|
|
584
|
+
`→ test … in …` pointer borrowed that pointer, resolved, and the gate answered **pass** (MEASURED —
|
|
585
|
+
`{"ids":["AM-1"],"testIds":[["a_long_enough_test_id"]],"verdicts":["AM-1:resolved"],"outcome":"pass","exit":0}`).
|
|
586
|
+
Every semantic read of a row — pointer, file, retraction, subject — now goes through a masked slice
|
|
587
|
+
of the SAME length carried on the row itself (`AmendmentRow.scan`, a new required field). And the
|
|
588
|
+
mask closed a fence on any run of three or more, while CommonMark §4.5 closes only on a run of the
|
|
589
|
+
SAME character at least as long as the opener: a standalone ` ``` ` line inside a ` ```` ` fence
|
|
590
|
+
re-opened the document mid-fence and a fenced `## Amendments: None` was selected again (MEASURED —
|
|
591
|
+
`{"saysNone":true,"outcome":"skip","exit":0}` with the real section's amendment never parsed). Both
|
|
592
|
+
reproducers now answer `fail`, exit 1. Registered mutants
|
|
593
|
+
`amendment-row-pointers-read-the-masked-slice`, `amendment-retraction-reads-the-masked-slice` and
|
|
594
|
+
`amendment-fence-closer-must-match-the-opener` (PROVEN, 1 failing each); the eight `amendment-*`
|
|
595
|
+
entries are 8/8 proven with 0 coverage drops among 8/8 anchored. Census unchanged again: 363
|
|
596
|
+
features, `fail 48 · pass 82 · skip 200 · not-established 33`, zero features moved even in their
|
|
597
|
+
per-verdict counts. HONEST LIMIT: the subject read shares the fix but has no discriminating test —
|
|
598
|
+
a fenced example can only APPEND to a row, and subject comparison is containment-tolerant in both
|
|
599
|
+
directions, so no fixture flips a verdict; the read is corrected, not proven. (6) **Cross-family round 5** raised the first two
|
|
600
|
+
P1s of this feature. An HTML-commented template — `<!-- … ## Amendments: None … -->` kept above the
|
|
601
|
+
real section, as plans legitimately do — was still read as the document's own declaration, so the
|
|
602
|
+
gate answered skip/exit 0 with the real section's unresolved `AM-1` never parsed (MEASURED —
|
|
603
|
+
`{"ids":[],"saysNone":true,"outcome":"skip","exit":0}`). The mask now blanks fences AND HTML
|
|
604
|
+
comments, both length-preserving, fences first; an unclosed comment is left visible for the same
|
|
605
|
+
reason an unclosed fence is reverted — masking may lose a comment, never hide a section. Fourth
|
|
606
|
+
instance of one class, after the fenced heading, the fenced row and the fenced pointer. Second P1:
|
|
607
|
+
round 4 made `AmendmentRow.scan` REQUIRED, which breaks any downstream constructor of a row the
|
|
608
|
+
previous release accepted — a legacy row reaching `amendmentsMissingFromPlan` threw
|
|
609
|
+
`TypeError: Cannot read properties of undefined (reading 'split')`. The field is **optional** again
|
|
610
|
+
and every semantic read goes through a fallback to `raw`, so a hand-built or deserialised row is
|
|
611
|
+
read slightly more generously instead of crashing. Registered mutants
|
|
612
|
+
`amendment-html-comment-is-not-a-declaration` (PROVEN, 1) and `amendment-row-scan-falls-back-to-raw`
|
|
613
|
+
(PROVEN, 3); the ten `amendment-*` entries are 10/10 proven, 0 drops among 10/10 anchored. Census
|
|
614
|
+
unchanged again — 363 features, zero moved, counts included — and zero of the 397 plan/ideation
|
|
615
|
+
documents on disk hold a commented `## Amendments` heading today, so this too closes a forgery route
|
|
616
|
+
rather than moving a live verdict.
|
|
617
|
+
|
|
618
|
+
**Cross-family rounds 7–8 (same staged `0.8.15`).** The exported declaration predicate now accepts
|
|
619
|
+
only a whole first paragraph or heading remainder from the closed set `None`/`N/A`/`нет`, optionally
|
|
620
|
+
with a full stop; qualified text such as `None of the required rows has been written yet.` is not an
|
|
621
|
+
absence declaration. The companion exported ambiguity predicate scans rendered text below the
|
|
622
|
+
section heading: when a declaration exists, zero rows parse, and AM-like content remains, the
|
|
623
|
+
decision is NOT-ESTABLISHED rather than skip. Round 8 also removes an optional whitespace-separated
|
|
624
|
+
CommonMark closing hash sequence before the exact heading comparison, so
|
|
625
|
+
`## Amendments: None ##` is accepted while `## Amendments: None##` remains text. The focused mutation
|
|
626
|
+
reproducer for the two refreshed declaration entries plus the closing-hash and ambiguity entries
|
|
627
|
+
reported 4/4 PROVEN (5, 6, 1 and 1 failing tests respectively) with no NOT_APPLIED or UNDEFENDED:
|
|
628
|
+
`dz mutation-gate --package . --only amendment-explicit-none-on-the-heading,amendment-none-declaration-unicode-guard,amendment-explicit-none-closing-hashes,amendment-explicit-none-ambiguity-fails-closed --test-cmd "npx vitest run test/amendment-trace.test.ts"`.
|
|
629
|
+
|
|
630
|
+
`0.8.11` — **published 2026-09-02.** Russian catalogue: `stem.ts` word-form normalisation on both sides of
|
|
631
|
+
registry search plus a RU topic dictionary with an observable miss in `recommend` (feature
|
|
632
|
+
`ru-catalog-discovery`, ADR 001/002, cross-family QE grade B). Honesty instruments: the
|
|
633
|
+
`dz discrimination-check` / `dz mutation-gate` seams are fixed — the CLI now passes the package test
|
|
634
|
+
script, a root-relative `packageDir` is accepted, and the vitest filter is no longer dropped by a stray
|
|
635
|
+
`--` (MEASURED: that ran all 5269 repo tests instead of one file). Also in this version: `dz chain`
|
|
636
|
+
(verify every chained journal in one command), `dz score --all` (a chained scorecard aggregate that
|
|
637
|
+
says `INSUFFICIENT_DATA` instead of a fake zero), mutation verdicts counted rather than read from
|
|
638
|
+
prose, `runnerId` stamped on the run ledger, a closed `RunOutcome` set in the telemetry vocabulary
|
|
639
|
+
wired into all five pipeline returns and the cost ledger, the catalogue counting invocable skills
|
|
640
|
+
(202 → 249) instead of prefix-named packs, `--select` refusing closed and installing a skill exactly
|
|
641
|
+
once, and one Codex invocation path that cannot return silence with a layer-1 guard against the stub
|
|
642
|
+
wrapper.
|
|
643
|
+
|
|
644
|
+
`0.8.10` — published 2026-08-31. Adds the four bounded `volume-shadow/v1` observations and their
|
|
389
645
|
immutable SOFT belt; incomplete evidence remains visible as unknown without changing the verdict.
|
|
390
646
|
|
|
391
|
-
`0.8.9` —
|
|
647
|
+
`0.8.9` — never published as-is; shipped inside 0.8.10 (2026-08-31). Adds the pure decision-point micro-recall contract, strict
|
|
392
648
|
fail-open transport/receipt reducers, post-hoc numerator/denominator metrics, and mutation-defended
|
|
393
649
|
advisory prompt isolation. This is experiment instrumentation, not evidence of local effectiveness.
|
|
394
650
|
|
|
395
|
-
`0.8.8` —
|
|
651
|
+
`0.8.8` — never published as-is; shipped inside 0.8.10 (2026-08-31). Adds evidence-gated `INTEGRATIONS.json` orchestration, one
|
|
396
652
|
receipt-qualified Claude project-MCP emitter, explicit refusal outcomes, and the ownership journal.
|
|
397
653
|
|
|
398
654
|
`0.8.7` — **pure same-tier feature-adr ETA calibration.** `eta.ts` parses timestamped checkpoint
|
|
@@ -419,6 +675,30 @@ closed. `CheckpointEntry` gains an optional `ts` and `stampCheckpointLine` appli
|
|
|
419
675
|
deliberately OUTSIDE the blob-mirrored serializer, because the sandboxed workflow has no `Date` and a
|
|
420
676
|
clock has no business in a function the clockless copy also runs. An absent stamp reads as UNKNOWN,
|
|
421
677
|
never zero; a malformed one is dropped, because a wrong instant is worse than an absent one.
|
|
678
|
+
**Phase telemetry, and why `planLedgerBackfill` now skips rows (v0.8.12).** `writeFeatureAdrState`
|
|
679
|
+
stores a `tier` and a `phaseStartTs` on the slot, and on a step-label CHANGE appends ONE
|
|
680
|
+
`{"kind":"phase",…,"wallSec","ts"}` row to the EXISTING `.dz/feature-adr/run-cost-ledger.jsonl`
|
|
681
|
+
(never creating one). `renderFeatureAdrPhaseLine` turns the slot alone into the panel's second line.
|
|
682
|
+
Three properties are load-bearing and each has a `dz mutation-gate` entry:
|
|
683
|
+
- **Monotone.** A plain `Step <n>` label going BACKWARDS against a slot younger than 90 minutes is
|
|
684
|
+
absorbed — counters land, the step and phase clock stand, no phase row. `⛔`/`⏸` labels and a
|
|
685
|
+
stale slot are the two escape hatches for a legitimate regression.
|
|
686
|
+
- **Serialized.** The whole read → compare → append → write transaction runs inside
|
|
687
|
+
`withNamedLockSync(root, 'fa-phase-slot', …)`; pattern counting and directory housekeeping stay
|
|
688
|
+
outside it. A lock timeout REFUSES the write. `writeFeatureAdrStateDetailed` returns
|
|
689
|
+
`{state}` or `{refused:'<reason>'}` so a caller can be LOUD about it (`writeFeatureAdrState` is the
|
|
690
|
+
back-compatible façade returning `state | undefined`). `WriteFeatureAdrStateInput._unsafeSkipLock`
|
|
691
|
+
and `._unsafeHoldMs` are **TEST-ONLY seams** for that lock's own RED half — no shipped caller sets
|
|
692
|
+
them, and `statusline-phase-lock.test.ts` asserts neither is spellable from the CLI.
|
|
693
|
+
- **Never a cost claim.** `kind`-carrying rows are telemetry. `isNonRunRow` is exported from
|
|
694
|
+
`ledger-backfill` and used by all three cost readers, so one definition covers them:
|
|
695
|
+
`planLedgerBackfill` skips such rows in claimant counting AND in filling (reporting
|
|
696
|
+
`skipped:'non-run-row'`), `selectLedgerRows` no longer pulls them into a run's evidence bundle
|
|
697
|
+
through its slug fallback, and `assembleTimeline` no longer labels them `cost` in a run timeline.
|
|
698
|
+
The last two were MEASURED leaking on 2026-09-06 — running each consumer with and without a phase
|
|
699
|
+
row changed its output — not assumed clean. `cadence` is untouched: it needs a string `date`,
|
|
700
|
+
which a phase row never carries.
|
|
701
|
+
|
|
422
702
|
`planLedgerBackfill` + `resolveLedgerRunId` let the run-cost ledger fill itself from the host's own
|
|
423
703
|
workflow record: a run id is resolved at WRITE time (the only moment it is unambiguous), a slug is
|
|
424
704
|
the fallback only when it names exactly ONE run, and a run claimed by more than one row fills
|
|
@@ -497,8 +777,9 @@ manifest was stale against its own shipped files. No behaviour changes.
|
|
|
497
777
|
lexical hit under `--semantic`, `HybridRecall` gains `semanticCandidates` / `semanticRanked`, and
|
|
498
778
|
`VectorTierStatus` gains `mirrorWriterEnabled`, `unmirrored`, `mirroredOther` and `orphaned`.
|
|
499
779
|
**`mirrored` CHANGES MEANING** to the pattern scope only — a consumer comparing it against a
|
|
500
|
-
full-store count must be updated. `0.6.1` — two new pure modules behind two new commands. `amendment-trace.ts` resolves every `AM-N`
|
|
501
|
-
amendment row
|
|
780
|
+
full-store count must be updated. `0.6.1` — two new pure modules behind two new commands. `amendment-trace.ts` resolves every `AM-N` and
|
|
781
|
+
`AM-CP-N` amendment row (two DISTINCT ids since `0.8.15`: a challenge-panel `AM-CP-1` never collides
|
|
782
|
+
with the ideation's `AM-1`) to a test found INSIDE the file the row names — matched against the file's parsed TEST
|
|
502
783
|
TITLES, because whole-file matching was forgeable by two comment lines whose letters spell the id, and
|
|
503
784
|
because an existing FILE never stands in for an existing TEST. `run-records.ts` decides whether a
|
|
504
785
|
run-cost row or a training pair may be written: it refuses bad JSON, a wrong-kind payload, an EMPTY
|
|
@@ -556,3 +837,136 @@ normaliser rather than keeping a copy of the rule). Four `TARGET_ALIASES` rows w
|
|
|
556
837
|
UNREACHABLE — `claude_code`, `claudecode`, `agentsmd`, `agents.md` all normalise onto a canonical name
|
|
557
838
|
and were resolved by precedence step 2 before the table was ever consulted; every one of those inputs
|
|
558
839
|
still resolves, so the deletion is observably a no-op.
|
|
840
|
+
|
|
841
|
+
## Публичный снимок бэклога — `buildPublicSnapshot` / `assertPublicSafe`
|
|
842
|
+
|
|
843
|
+
Две функции, отдающие наружу агрегаты очереди задач так, чтобы тексты задач не покидали машину.
|
|
844
|
+
|
|
845
|
+
```ts
|
|
846
|
+
import { buildPublicSnapshot, assertPublicSafe } from '@dzhechkov/harness-core';
|
|
847
|
+
|
|
848
|
+
const built = buildPublicSnapshot(records, '2026-09-03'); // чистая: записи + дата, без файлов и часов
|
|
849
|
+
if (!built.ok) throw new Error(`${built.code}: ${built.reason}`);
|
|
850
|
+
|
|
851
|
+
const verdict = assertPublicSafe(JSON.stringify(built.snapshot)); // независимая застава на выходе
|
|
852
|
+
if (!verdict.ok) throw new Error(`${verdict.code}: ${verdict.reason}`);
|
|
853
|
+
```
|
|
854
|
+
|
|
855
|
+
**Порождение, а не фильтрация.** Публичный объект не проверяется после сборки — он собирается
|
|
856
|
+
перечислением разрешённых агрегатов, и приватное поле ни разу не читается на пути к выходу.
|
|
857
|
+
Проверяющий поверх готового файла ловит то, о чём подумали, и пропускает поле, которое добавят
|
|
858
|
+
завтра.
|
|
859
|
+
|
|
860
|
+
**Застава несёт СВОЮ копию перечня** и не импортирует схему у порождения: иначе одна ошибка
|
|
861
|
+
проходила бы обе проверки. Она проверяет БАЙТЫ, а не разобранный объект — измерено, что
|
|
862
|
+
`{"receipt":{"text":"…"},"receipt":{…}}` проходит разбор чистым, а наружу уезжают оба ключа.
|
|
863
|
+
|
|
864
|
+
**Чего в схеме нет намеренно:** максимума, минимума и процентилей. Крайнее значение по определению
|
|
865
|
+
принадлежит ровно одной записи, то есть указывает на неё. Вместо них медиана и гистограмма, и порог
|
|
866
|
+
малых групп применяется к каждой корзине.
|
|
867
|
+
|
|
868
|
+
Пять каналов утечки закрыты по итогу трёх проходов кросс-семейного ревью: вложенное поле
|
|
869
|
+
разрешённого ключа, пустой объект как чистый снимок, дубликат ключа в байтах, свободная строка в
|
|
870
|
+
массиве правил, приватный текст в имени ключа словаря. Каждый воспроизведён прогоном до починки.
|
|
871
|
+
|
|
872
|
+
## Журнал переходов статуса — `appendTransition` / `readTransitions`
|
|
873
|
+
|
|
874
|
+
Запись бэклога хранит ровно один переход, последний. Журнал `.dz/backlog/status-log.jsonl` копит
|
|
875
|
+
все: строка на переход, дозапись без чтения файла целиком.
|
|
876
|
+
|
|
877
|
+
```ts
|
|
878
|
+
import { appendTransition, readTransitions } from '@dzhechkov/harness-core';
|
|
879
|
+
appendTransition(root, { id, from: 'new', to: 'shipped', ts, by: 'backlog ship' });
|
|
880
|
+
```
|
|
881
|
+
|
|
882
|
+
Никогда не бросает: журнал — наблюдение, а не гейт, и его поломка не должна ронять команду, которая
|
|
883
|
+
меняла статус. Битая строка при чтении пропускается — файл дозаписывается конкурентно.
|
|
884
|
+
|
|
885
|
+
## Контракт вывода роя — `checkSwarmBrief` / `SWARM_BRIEF_CONTRACT`
|
|
886
|
+
|
|
887
|
+
Разбирает бриф роя агентов и отвечает, объявлен ли в нём контракт вывода: куда писать
|
|
888
|
+
(`OUTPUT_DIR`), какие единицы работы (`UNITS`) и какая из них сборочная (`ASSEMBLY_UNIT`).
|
|
889
|
+
Заведён после инцидента, где рой получил бриф «пришли один отчёт в конце», умер посреди работы,
|
|
890
|
+
и восстанавливать оказалось нечего.
|
|
891
|
+
|
|
892
|
+
Вся ценность модуля в одном свойстве: **вердикт нельзя подделать текстом, который он же и судит.**
|
|
893
|
+
Отсюда форма проверок — не «есть ли такие слова», а «объявлено ли это ОДНОЗНАЧНО»:
|
|
894
|
+
|
|
895
|
+
- объявление внутри забора кода или HTML-комментария не считается объявлением; закрывающий забор
|
|
896
|
+
обязан быть не короче открывающего, а комментарий, пытающийся вложиться, даёт отказ, а не тихое
|
|
897
|
+
открытие после внутреннего `-->`;
|
|
898
|
+
- украшенный ключ (жирный, цитата, обратные кавычки) **считается** в счётчик повторов: спрятать
|
|
899
|
+
одно объявление за оформлением и получить тихий выбор другого нельзя — будет отказ по
|
|
900
|
+
неоднозначности;
|
|
901
|
+
- перечень единиц никогда не усекается молча. Пустая строка или любая строка-не-пункт, за которой в
|
|
902
|
+
том же блоке ещё есть пункты, — отказ, **называющий эту строку**; так же считается заслонённая
|
|
903
|
+
строка. Причина: `units` — тот самый машинный перечень, с которым потом сверяют каталог, и тихая
|
|
904
|
+
потеря его хвоста делает сверку ложно-успешной;
|
|
905
|
+
- строка-не-пункт ПОСЛЕ последнего пункта — нормальное окончание списка, а не нарушение: проверка,
|
|
906
|
+
изобретающая нарушения, хуже отсутствующей;
|
|
907
|
+
- `OUTPUT_DIR` проверяется как ПУТЬ: абсолютный, переход вверх, обратная косая, управляющие байты и
|
|
908
|
+
сегменты, не являющиеся именами, отвергаются;
|
|
909
|
+
- имя `plan` зарезервировано — его файл есть файл плана, который рой пишет первым;
|
|
910
|
+
- длина имени единицы ограничена, число единиц ограничено, проверка дубликатов линейная;
|
|
911
|
+
- любое значение из брифа, доходящее до терминала, обезврежено: байт `ESC` не перерисует строку
|
|
912
|
+
отказа в «OK».
|
|
913
|
+
|
|
914
|
+
Все перечисленные защиты имеют записи в реестре мутаций и доказаны прогоном
|
|
915
|
+
(`dz mutation-gate … → 7/7 proven, verdict PASS`).
|
|
916
|
+
|
|
917
|
+
Честный предел, который печатается вместе с зелёным ответом: проверка удостоверяет, что бриф
|
|
918
|
+
ОБЪЯВИЛ контракт, а не что рой ему последует.
|
|
919
|
+
|
|
920
|
+
## Двухфазная строка стадии — intent до модели, outcome после
|
|
921
|
+
|
|
922
|
+
Резолвер моделей теперь возвращает не только `StageOpts`, но и **причину**: `resolveStageDecision(stage, env)
|
|
923
|
+
→ {opts, spec, reason}`, где `reason` — закрытое перечисление из **двадцати** (20) значений:
|
|
924
|
+
восемь веток резолвера, две деградации спецификации и десять причин уровня диспатча, которых чистый
|
|
925
|
+
резолвер знать не может. Канонический порядок совпадает с экспортом `STAGE_DECISION_REASONS`:
|
|
926
|
+
|
|
927
|
+
<!-- stage-decision-reasons:start -->
|
|
928
|
+
- `usage-override`
|
|
929
|
+
- `explicit-models`
|
|
930
|
+
- `routing-not-requested`
|
|
931
|
+
- `coder-knob-codex`
|
|
932
|
+
- `planner-knob-codex`
|
|
933
|
+
- `qe-cross-family`
|
|
934
|
+
- `budget-table-cell`
|
|
935
|
+
- `default-models`
|
|
936
|
+
- `codex-id-substituted`
|
|
937
|
+
- `spec-unrecognised`
|
|
938
|
+
- `coder-fallback`
|
|
939
|
+
- `codex-unsupported-at-dispatch`
|
|
940
|
+
- `fallback-after-no-deliverable`
|
|
941
|
+
- `precision-second-pass`
|
|
942
|
+
- `auto-cost`
|
|
943
|
+
- `qe-same-family-degraded`
|
|
944
|
+
- `challenge-panel`
|
|
945
|
+
- `codex-probe-failed`
|
|
946
|
+
- `codex-refused-before-dispatch`
|
|
947
|
+
- `fallback-rung`
|
|
948
|
+
<!-- stage-decision-reasons:end -->
|
|
949
|
+
|
|
950
|
+
Причины первых двух групп вычисляются резолвером; причины последней группы описывают выбор текущей
|
|
951
|
+
ступени, включая откаты, второй precision-проход, challenge panel и learned-cost. `fallback-rung`
|
|
952
|
+
намеренно не пересказывает исход предыдущей ступени: тот уже принадлежит её собственной строке outcome.
|
|
953
|
+
Отдельно стоит `codex-refused-before-dispatch`: id ответил на пробу, но ступень так и не построила
|
|
954
|
+
диспатч (непригодный ref области ревью, небезопасный id, отклонённый exec-план). Ни один агент не
|
|
955
|
+
запускался, поэтому следующая ступень — НЕ `fallback-after-no-deliverable`: та причина утверждает, что
|
|
956
|
+
ступень отработала и ничего не отдала. Исход ступени трёхзначен (`dispatched` / `probe-failed` /
|
|
957
|
+
`refused-before-dispatch`) и остаётся на holder этой конкретной попытки; булев флаг сваливал всякий
|
|
958
|
+
не-пробный отказ в ветку «оно запускалось».
|
|
959
|
+
Исход принадлежит КОНКРЕТНОМУ вызову, а не модулю: дизайн-стадии идут конкурентно
|
|
960
|
+
(`await parallel(designThunks)`), и общая переменная, прочитанная после `await`, содержала бы то,
|
|
961
|
+
что записал последний сосед — измерено: стадия, чей собственный диспатч Codex ОТРАБОТАЛ, объявляла
|
|
962
|
+
свой откат как `codex-probe-failed`, позаимствовав факт у соседки. Отдельный случай — рантайм не
|
|
963
|
+
знает типа агента `codex:codex-rescue`: это тоже отказ ДО запуска, и он докладывается уже имевшейся
|
|
964
|
+
причиной `codex-unsupported-at-dispatch`, а не «ступень отработала и ничего не отдала».
|
|
965
|
+
`resolveStageDecision` не эмитит ни одну из десяти — это закреплено тестом. `resolveStageModel`
|
|
966
|
+
остался тонкой обёрткой с байт-идентичным результатом. Чистые `renderStageIntentLine` и
|
|
967
|
+
`renderStageOutcomeLine` проецируются генератором в оба зеркальных скрипта. Единственный шов
|
|
968
|
+
`dispatchAgent` печатает `▸ code · opus · budget table cell · intent` непосредственно перед
|
|
969
|
+
`agent(...)`, а после окончательного исхода той же попытки — `◆ code · opus · outcome: dispatched`
|
|
970
|
+
либо точный отказ/провал пробы. Только outcome добавляется в `dispatchOutcomes`, авторитетный отчёт
|
|
971
|
+
«кто-что-делал»; intent остаётся живым предупреждением. Свёрнутые стадии (`research`, `ddd`) отдельных
|
|
972
|
+
строк не получают — у них нет отдельного диспатча.
|
package/dist/agents-policy.d.ts
CHANGED
|
@@ -28,7 +28,21 @@ export interface ExtractPolicyBlocksResult {
|
|
|
28
28
|
/** Registry ids whose source is absent/null or whose begin/end anchor is malformed. */
|
|
29
29
|
readonly missing: readonly string[];
|
|
30
30
|
}
|
|
31
|
-
export type PolicyDriftStatus = 'ok' | 'stale' | 'missing-stamp' | 'missing-anchor' | 'orphan-stamp'
|
|
31
|
+
export type PolicyDriftStatus = 'ok' | 'stale' | 'missing-stamp' | 'missing-anchor' | 'orphan-stamp'
|
|
32
|
+
/**
|
|
33
|
+
* Секция `<!-- dz:policy id=X -->` найдена в ФАЙЛЕ-ИСТОЧНИКЕ, но не объявлена в `POLICY_SOURCES`.
|
|
34
|
+
*
|
|
35
|
+
* ЗАЧЕМ ОТДЕЛЬНЫЙ СТАТУС. `orphan-stamp` смотрит в другую сторону — штамп в проекции без
|
|
36
|
+
* источника. Обратный случай не покрывался ничем, и это давало ЛОЖНОЕ ЗЕЛЁНОЕ: реестр
|
|
37
|
+
* `POLICY_SOURCES` ведётся руками, поэтому секция, дописанная в CLAUDE.md и не вписанная в него,
|
|
38
|
+
* молча не попадала в AGENTS.md, а `dz agents-sync --check` отвечал «in sync».
|
|
39
|
+
*
|
|
40
|
+
* ИЗМЕРЕНО 2026-09-03 на живом случае: добавил в канон секцию `backlog-coverage`, прогнал
|
|
41
|
+
* `agents-sync` — «in sync — 9 policy section(s), 9250 bytes», ровно те же число секций и байт,
|
|
42
|
+
* что до правки, ни на единицу не изменившиеся. Правило, ради которого всё делалось, до агентов
|
|
43
|
+
* не доехало, а прибор доложил успех.
|
|
44
|
+
*/
|
|
45
|
+
| 'unregistered-section';
|
|
32
46
|
export interface PolicyDriftFinding {
|
|
33
47
|
readonly id: string;
|
|
34
48
|
readonly file: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agents-policy.d.ts","sourceRoot":"","sources":["../src/agents-policy.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,gFAAgF;IAChF,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;CAClC;AAED,oFAAoF;AACpF,eAAO,MAAM,cAAc,EAAE,SAAS,YAAY,
|
|
1
|
+
{"version":3,"file":"agents-policy.d.ts","sourceRoot":"","sources":["../src/agents-policy.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,gFAAgF;IAChF,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;CAClC;AAED,oFAAoF;AACpF,eAAO,MAAM,cAAc,EAAE,SAAS,YAAY,EAyExC,CAAC;AAEX,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,kFAAkF;IAClF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,MAAM,EAAE,SAAS,WAAW,EAAE,CAAC;IACxC,uFAAuF;IACvF,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAED,MAAM,MAAM,iBAAiB,GACzB,IAAI,GACJ,OAAO,GACP,eAAe,GACf,gBAAgB,GAChB,cAAc;AAChB;;;;;;;;;;;;GAYG;GACD,sBAAsB,CAAC;AAE3B,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAC;IACnC,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,SAAS,kBAAkB,EAAE,CAAC;CAClD;AAED,mMAAmM;AACnM,eAAO,MAAM,2BAA2B,QAAS,CAAC;AAClD,eAAO,MAAM,8BAA8B,MAAM,CAAC;AAElD,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,sEAAsE;IACtE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,oFAAoF;IACpF,QAAQ,CAAC,qBAAqB,EAAE,MAAM,CAAC;CACxC;AAID,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOxD;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAElD;AAMD,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,EACzC,OAAO,GAAE,SAAS,YAAY,EAAmB,GAChD,yBAAyB,CA0B3B;AAED,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,SAAS,WAAW,EAAE,EAC9B,OAAO,GAAE,SAAS,YAAY,EAAmB,GAChD,SAAS,MAAM,EAAE,CAMnB;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAE3E;AAED,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,cAAc,CAatE;AA8DD,wBAAgB,iBAAiB,CAC/B,WAAW,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,EAC/C,YAAY,EAAE,MAAM,GAAG,IAAI,EAC3B,OAAO,GAAE,SAAS,YAAY,EAAmB,GAChD,iBAAiB,CAwEnB"}
|
package/dist/agents-policy.js
CHANGED
|
@@ -28,7 +28,16 @@ export const POLICY_SOURCES = [
|
|
|
28
28
|
file: 'CLAUDE.md',
|
|
29
29
|
heading: 'Test execution: never watch',
|
|
30
30
|
why: 'A watch-mode test run hangs an unattended coding session.',
|
|
31
|
-
|
|
31
|
+
// Была `npm test -- --run` — эта команда в корне НЕ СУЩЕСТВУЕТ (Missing script: test), и
|
|
32
|
+
// 2026-09-03 канон исправлен. Несущей стала команда, которая есть.
|
|
33
|
+
operativeClause: 'npm run test:all',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: 'backlog-coverage',
|
|
37
|
+
file: 'CLAUDE.md',
|
|
38
|
+
heading: 'Backlog coverage',
|
|
39
|
+
why: 'Работа, не закрытая в этом же ходу, иначе не оставляет следа нигде, кроме переписки, которая прокручивается.',
|
|
40
|
+
operativeClause: 'ALWAYS say out loud which records you filed',
|
|
32
41
|
},
|
|
33
42
|
{
|
|
34
43
|
id: 'data-protection',
|
|
@@ -242,6 +251,23 @@ export function detectPolicyDrift(sourceFiles, agentsMdText, sources = POLICY_SO
|
|
|
242
251
|
for (const stamp of [...stamps.filter((entry) => !sourceIds.has(entry.id)), ...duplicateStamps]) {
|
|
243
252
|
findings.push({ id: stamp.id, file: stamp.file, status: 'orphan-stamp', expectedSha: null, actualSha: stamp.sha });
|
|
244
253
|
}
|
|
254
|
+
// Секции канона, о которых реестр не знает. Ищем во ВСЕХ переданных файлах-источниках, а не
|
|
255
|
+
// только в тех, что уже объявлены: файл может целиком отсутствовать в реестре.
|
|
256
|
+
{
|
|
257
|
+
const declared = new Set(sources.map((source) => source.id));
|
|
258
|
+
const seen = new Set();
|
|
259
|
+
for (const [file, text] of sourceFiles) {
|
|
260
|
+
if (typeof text !== 'string')
|
|
261
|
+
continue;
|
|
262
|
+
for (const m of text.matchAll(/<!-- dz:policy id=([a-z0-9-]+) -->/g)) {
|
|
263
|
+
const id = m[1];
|
|
264
|
+
if (declared.has(id) || seen.has(id))
|
|
265
|
+
continue;
|
|
266
|
+
seen.add(id);
|
|
267
|
+
findings.push({ id, file, status: 'unregistered-section', expectedSha: null, actualSha: null });
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
245
271
|
if (typeof agentsMdText === 'string') {
|
|
246
272
|
const beginCount = markerCount(agentsMdText, POLICY_BLOCK_BEGIN);
|
|
247
273
|
const endCount = markerCount(agentsMdText, POLICY_BLOCK_END);
|