@deftai/directive-content 0.108.0 → 0.109.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 (100) hide show
  1. package/QUICK-START.md +4 -3
  2. package/SKILL.md +9 -10
  3. package/Taskfile.yml +18 -3
  4. package/UPGRADING.md +7 -6
  5. package/coding/build-output.md +4 -3
  6. package/coding/coding.md +6 -5
  7. package/coding/security.md +3 -3
  8. package/coding/testing.md +2 -1
  9. package/commands.md +13 -15
  10. package/contracts/deposit-required-paths.json +26 -0
  11. package/contracts/design-critique.md +64 -1
  12. package/contracts/deterministic-questions.md +2 -1
  13. package/contracts/host-lifecycle-duties.md +1 -1
  14. package/contracts/path-write-fence.md +67 -7
  15. package/conventions/content-manifest.json +1 -1
  16. package/conventions/references.md +10 -8
  17. package/conventions/task-caching.md +2 -1
  18. package/conventions/vbrief-filenames.md +5 -4
  19. package/docs/consumer-check-contract.md +35 -0
  20. package/docs/delivery-attempt.md +2 -0
  21. package/docs/gate-integrity.md +17 -2
  22. package/docs/host-surface-assumptions.md +2 -1
  23. package/docs/orphan-active-verdict-basis.md +33 -0
  24. package/docs/skill-pin-policy.md +1 -1
  25. package/events/README.md +12 -13
  26. package/glossary.md +2 -1
  27. package/incidents/README.md +2 -1
  28. package/interfaces/cli.md +2 -1
  29. package/languages/6502-DASM.md +2 -1
  30. package/languages/c.md +2 -1
  31. package/languages/cpp.md +2 -1
  32. package/languages/csharp.md +2 -1
  33. package/languages/dart.md +2 -1
  34. package/languages/delphi.md +2 -1
  35. package/languages/elixir.md +2 -1
  36. package/languages/go.md +2 -1
  37. package/languages/java.md +2 -1
  38. package/languages/javascript.md +2 -1
  39. package/languages/julia.md +2 -1
  40. package/languages/kotlin.md +2 -1
  41. package/languages/markdown.md +2 -1
  42. package/languages/mermaid.md +2 -1
  43. package/languages/officejs.md +2 -1
  44. package/languages/python.md +2 -1
  45. package/languages/r.md +2 -1
  46. package/languages/rust.md +2 -1
  47. package/languages/sql.md +2 -1
  48. package/languages/swift.md +2 -1
  49. package/languages/typescript.md +2 -1
  50. package/languages/vba.md +2 -1
  51. package/languages/vhdl.md +2 -1
  52. package/languages/visual-basic.md +2 -1
  53. package/languages/zig.md +2 -1
  54. package/main.md +47 -44
  55. package/meta/code-field.md +2 -1
  56. package/meta/morals.md +2 -1
  57. package/meta/philosophy.md +3 -2
  58. package/meta/project.md +4 -3
  59. package/meta/ralph.md +2 -1
  60. package/meta/security.md +3 -2
  61. package/meta/versioning.md +2 -1
  62. package/package.json +3 -3
  63. package/packs/patterns/patterns-pack-0.1.json +1 -1
  64. package/packs/rules/rules-pack-0.1.json +6 -6
  65. package/packs/skills/skills-pack-0.1.json +8 -8
  66. package/packs/strategies/strategies-pack-0.1.json +5 -5
  67. package/patterns/executor-layer-credentials.md +1 -1
  68. package/patterns/multi-agent.md +4 -4
  69. package/platforms/2600.md +2 -1
  70. package/platforms/unity.md +2 -1
  71. package/references/ip-risk.md +14 -19
  72. package/scm/changelog.md +1 -1
  73. package/scm/git.md +2 -1
  74. package/scm/github.md +10 -6
  75. package/skills/deft-directive-build/SKILL.md +7 -7
  76. package/skills/deft-directive-gh-slice/SKILL.md +1 -1
  77. package/skills/deft-directive-interview/SKILL.md +5 -5
  78. package/skills/deft-directive-pre-pr/SKILL.md +2 -2
  79. package/skills/deft-directive-refinement/SKILL.md +3 -3
  80. package/skills/deft-directive-release/SKILL.md +9 -9
  81. package/skills/deft-directive-setup/SKILL.md +3 -2
  82. package/skills/deft-directive-sync/SKILL.md +7 -7
  83. package/stage-pack.mjs +31 -0
  84. package/strategies/README.md +2 -1
  85. package/strategies/interview.md +1 -1
  86. package/strategies/research.md +1 -1
  87. package/strategies/speckit.md +2 -2
  88. package/strategies/v0-20-contract.md +2 -2
  89. package/swarm/swarm.md +2 -1
  90. package/tasks/vbrief.yml +6 -2
  91. package/tasks/verify.yml +40 -1
  92. package/templates/agent-prompt-preamble.md +2 -2
  93. package/templates/agents-entry.md +2 -2
  94. package/templates/make-spec.md +1 -1
  95. package/templates/swarm-greptile-poller-prompt.md +2 -2
  96. package/tools/package-manager-network.md +2 -1
  97. package/tools/taskfile-migration.md +2 -1
  98. package/tools/taskfile.md +2 -1
  99. package/tools/telemetry.md +2 -1
  100. package/vbrief/vbrief.md +1 -1
@@ -30,6 +30,13 @@ schema with its own matcher.
30
30
  When a fence is active, PreToolUse direct writes (Write / Edit / StrReplace / …) **fail closed**
31
31
  for out-of-fence paths after ritual / scope / read-only / human-origin authz gates.
32
32
 
33
+ ApplyPatch is a direct write. Every path `hookMutationTargetPaths` returns — the declared
34
+ path plus every `*** Add/Update/Delete/Move/Rename File:` header and every `*** Move to:`
35
+ destination — must pass the same fence as Write (#3614). A mixed patch is denied if any
36
+ target is denied. An ApplyPatch body that names no classifiable mutation target fails closed
37
+ while the fence is active. ⊗ Authorize only the declared path when the patch body names
38
+ other targets.
39
+
33
40
  Deny reasons are stable and name the fence source:
34
41
 
35
42
  - `write fence project allowPaths (source: project)` or `project+story`
@@ -101,10 +108,24 @@ output of running a program, so gating them by parsing the command string means
101
108
  a program will do without running it. Recognition of *destructive spellings* is decidable;
102
109
  prediction of *mutation* is not.
103
110
 
104
- What that means concretely all of these are **fail-open today**:
111
+ Tree-wide destructive git is **recognized and fail-closed**, always-on, independent of
112
+ `shellDestForms` (#3917). The forms are `git reset --hard`, `git clean -f` (including
113
+ combined `-fd` / `-fdx`), `git checkout -f` / `git switch --force` / `-B`, and
114
+ `git stash drop` / `git stash clear`. A simple command whose relocators (`-C`,
115
+ `--git-dir`, `--work-tree`, `GIT_DIR=`, `GIT_WORK_TREE=`) are all absolute paths
116
+ outside the project root is allowed as a throwaway fixture. Relative, in-project,
117
+ opaque (`GIT_CONFIG*`, `-c core.workTree`), and compound forms stay denied.
118
+
119
+ That close is a **guard**, not a root-cause claim. Every recognized form, deny or
120
+ fixture-allow, appends one JSONL line under the platform user-config dir
121
+ (`%APPDATA%\deft\logs\git-destructive.jsonl` / `~/.config/deft/logs/git-destructive.jsonl`,
122
+ overridable with `DEFT_GIT_DESTRUCTIVE_LOG`) so a later occurrence names host, actor,
123
+ command, project root, and disposition even if reflogs are gone.
105
124
 
106
- - Unrecognized mutators: `git reset --hard`, `git clean -fd`, `git stash drop`, `git checkout`
107
- without `--`, `mv`, `cp`, `sed -i`, `truncate`, `find -delete`, and `>` / `>>` redirection
125
+ What remains **fail-open today**:
126
+
127
+ - Unrecognized mutators: `git checkout` without `--` or `-f` (branch switch / ambiguous
128
+ path checkout), `mv`, `cp`, `sed -i`, `truncate`, `find -delete`, and `>` / `>>` redirection
108
129
  - Interpreters: `bash -c 'rm x'`, `python -c`, `node -e`, `cmd /c`
109
130
  - Non-literal verbs: `\rm x`, `rm${IFS}x` — the tokenizer cannot see the verb, so even the
110
131
  fail-closed branch does not fire
@@ -113,10 +134,49 @@ What that means concretely — all of these are **fail-open today**:
113
134
  cannot tell which shell will run the command (#3624)
114
135
  - Mutations by allowed programs: `npm run build`, `node scripts/clean.js`, `make` — inherent
115
136
  to any string recognizer, since writing files is what those commands are *for*
116
- - **Nothing on the allow path is audited**, so a bypass currently leaves no trace
117
-
118
- Do not describe this gate as closing the Bash bypass. It raises the floor on the four
119
- recognized verbs in simple commands. The bypass class remains open.
137
+ - **Nothing on the dest-form allow path is audited** except the tree-wide destructive-git
138
+ log above. A dest-form bypass still leaves no dest-form trace.
139
+
140
+ ### Shell file-write reissue (#3983 / #3987)
141
+
142
+ This gate is a **cooperative guardrail**. It raises the cost of an accidental or
143
+ careless reissue -- a Write that occupancy, ritual, or scope already denied,
144
+ sent again through the host shell. It is **not** a boundary against a determined
145
+ caller. An agent that wants out of the fence has unbounded exits, and parsing
146
+ the command string cannot close them.
147
+
148
+ Grok Build shell is `run_terminal_command`. That name is in `SHELL_TOOL_NAMES`,
149
+ so PreToolUse fires. Recognized in-repo dests (`Set-Content`, `Out-File`,
150
+ `Add-Content`, python pathlib `write_text`/`write_bytes`, IO.File WriteAllText /
151
+ WriteAllBytes) are injected as Write targets and authorized through
152
+ `inspectMutationGates` -- occupancy, ritual, scope, and the path fence -- the
153
+ same way ApplyPatch authorizes every mutation target (#3614). Always-on,
154
+ independent of `shellDestForms`. Named PowerShell parameters are honoured in
155
+ any order: `-Value` before `-Path` is not taken as the dest.
156
+
157
+ OS-temp dests and commands with no recognized dest (`git status`,
158
+ `occupancy:release`, `git commit -F` of a temp body) stay fail-open.
159
+
160
+ **Measured limits** (bound #3997 arc: synthesis 5472062522, table 5472059705):
161
+
162
+ - Recogniser recall is about **47%**: 2,843 of 5,372 real file-writing commands
163
+ were invisible to the classifier, and 17 of 29 probed in-repo write shapes
164
+ yielded zero targets. Recall work stays on #3987.
165
+ - Destinations that are shell **variables** are not recovered. That is most
166
+ logged shell: 1,089 of 1,131 calls were dynamic, compound, or emitted no
167
+ target.
168
+ - A **directory junction** created without elevation defeats `provablyExternal`:
169
+ the path is lexically outside the root and its realpath is inside. That is
170
+ **re-entry** polarity. `#3186` `assertProjectionContained` is **escape**
171
+ polarity (in-tree dest whose realpath leaves the tree) and does not close it.
172
+
173
+ Fail-open at this predicate is the bound posture (#3997). Inverting it to
174
+ fail-closed on dests the parser cannot prove external is that issue's refuted
175
+ proposal, not this gate's next patch.
176
+
177
+ Do not cite this merge as "the shell write path is gated." It narrows the
178
+ cooperative reissue hole. The residual class is every command whose dest is
179
+ not statically recoverable.
120
180
 
121
181
  ### Dest-form target recognition (#3438)
122
182
 
@@ -363,7 +363,7 @@
363
363
  {
364
364
  "path": "REFERENCES.md",
365
365
  "bucket": "repo-dev",
366
- "note": "Repo-level references index for maintainers (harness-entry-adjacent; stays at root)."
366
+ "note": "Maintainer-only Skills Index and lazy-load guide. Audience boundary (bucket repo-dev). Consumers use npx deft packs:slice skills list (#3601 / #3899). Do not reclassify as content."
367
367
  },
368
368
  {
369
369
  "path": "ROADMAP.md",
@@ -1,10 +1,11 @@
1
+ <!-- deft:deposit-link-rewrite v=1 source="content/conventions/references.md" -->
1
2
  # vBRIEF References — `x-vbrief/*` Type Registry
2
3
 
3
4
  Canonical reference for the shape and type registry of `plan.references` entries in vBRIEF files.
4
5
 
5
6
  Legend (from RFC2119): !=MUST, ~=SHOULD, ≉=SHOULD NOT, ⊗=MUST NOT, ?=MAY.
6
7
 
7
- **See also**: [../vbrief/vbrief.md](../vbrief/vbrief.md) | [../vbrief/schemas/vbrief-core.schema.json](../vbrief/schemas/vbrief-core.schema.json) | [../main.md](../../main.md)
8
+ **See also**: [../vbrief/vbrief.md](../vbrief/vbrief.md) | [../vbrief/schemas/vbrief-core.schema.json](../vbrief/schemas/vbrief-core.schema.json) | [../main.md](../main.md)
8
9
 
9
10
  ---
10
11
 
@@ -63,7 +64,7 @@ Consumer projects ? MAY extend the registry with additional `x-vbrief/*` values.
63
64
  ## Origin Provenance (D11)
64
65
 
65
66
  Scope vBRIEFs in `vbrief/pending/` and `vbrief/active/` SHOULD carry at least
66
- one reference whose `type` matches `^x-vbrief/`. `scripts/vbrief_validate.py`
67
+ one reference whose `type` matches `^x-vbrief/`. `task xbrief:validate`
67
68
  treats any `x-vbrief/*`-typed reference as an origin for the D11 check by
68
69
  default (schema-trusting behavior).
69
70
 
@@ -78,7 +79,7 @@ enforce the allow-list in CI can opt in via the same flag.
78
79
 
79
80
  For scope vBRIEFs ingested from a GitHub issue, the canonical provenance
80
81
  signal is the `plan.narratives.Origin` text emitted by
81
- `scripts/issue_ingest.py::_build_issue_vbrief`:
82
+ `task issue:ingest::_build_issue_vbrief`:
82
83
 
83
84
  - ! `Origin` MUST take one of these two forms:
84
85
  - `Ingested from https://github.com/{owner}/{repo}/issues/{N}` (browser URL resolves)
@@ -104,12 +105,13 @@ from *informational* references using this narrative:
104
105
  - ⊗ Mutate a `completed/` vBRIEF to remove a companion / sibling reference solely because `task issue:ingest` false-positives on it (rewriting completed history is an anti-pattern per `skills/deft-directive-refinement/SKILL.md`)
105
106
  - ~ When adding a companion / sibling / related-plan reference to an ingested vBRIEF, keep `Origin` pointing at the original ingest source so the dedup pass continues to recognise the vBRIEF as the canonical owner of that issue
106
107
 
107
- ## Schema Version: v0.6 (Canonical, Strict)
108
+ ## Schema Version: v0.8 (canonical write)
108
109
 
109
- - ! All vBRIEFs MUST emit `"vBRIEFInfo": { "version": "0.6" }`
110
- - ! `scripts/vbrief_validate.py` accepts ONLY `"0.6"`; any other version (including legacy `"0.5"`) is a hard validation error
111
- - ! The vendored schema at `../vbrief/schemas/vbrief-core.schema.json` is the canonical v0.6 copy from [`deftai/vBRIEF`](https://github.com/deftai/vBRIEF/blob/master/schemas/vbrief-core-0.6.schema.json) and pins `vBRIEFInfo.version` to `const: "0.6"`
112
- - ! `scripts/migrate_vbrief.py` emits `"0.6"`; pre-existing v0.5 vBRIEFs are swept to `"0.6"` as part of the migrator flip PR
110
+ - ! All new xBRIEFs MUST emit `"xBRIEFInfo": { "version": "0.8" }`
111
+ - ! `task vbrief:validate` / `task xbrief:validate` accepts `"0.8"` (current write) and `"0.6"` (legacy read)
112
+ - ! The current write schema at `../vbrief/schemas/xbrief-core-0.8.schema.json` pins `xBRIEFInfo.version` to `const: "0.8"`. The vendored v0.6 copy at `../vbrief/schemas/vbrief-core.schema.json` remains for read/migration.
113
+ - ! `deft migrate:xbrief` rewrites 0.6 envelopes to `xBRIEFInfo@0.8`
114
+ - ⊗ Emit `"version": "0.6"` on any new write path -- 0.6 is migration/read compatibility only
113
115
 
114
116
  ## Anti-Patterns
115
117
 
@@ -1,8 +1,9 @@
1
+ <!-- deft:deposit-link-rewrite v=1 source="content/conventions/task-caching.md" -->
1
2
  # Task Caching Convention
2
3
 
3
4
  Legend (from RFC2119): !=MUST, ~=SHOULD, ≉=SHOULD NOT, ⊗=MUST NOT, ?=MAY.
4
5
 
5
- **See also**: [main.md](../../main.md) | [tasks/prd.yml](../../tasks/prd.yml) | [tasks/scope.yml](../../tasks/scope.yml) | [tests/content/test_taskfile_caching.py](../../tests/content/test_taskfile_caching.py)
6
+ **See also**: [main.md](../main.md) | [tasks/prd.yml](../tasks/prd.yml) | [tasks/scope.yml](../tasks/scope.yml) | [tests/content/test_taskfile_caching.py](../../tests/content/test_taskfile_caching.py)
6
7
 
7
8
  ## Invariant
8
9
 
@@ -1,10 +1,11 @@
1
+ <!-- deft:deposit-link-rewrite v=1 source="content/conventions/vbrief-filenames.md" -->
1
2
  # vBRIEF Filename Conventions
2
3
 
3
4
  Canonical rules for scope vBRIEF filenames and slug normalization.
4
5
 
5
6
  Legend (from RFC2119): !=MUST, ~=SHOULD, ≉=SHOULD NOT, ⊗=MUST NOT, ?=MAY.
6
7
 
7
- **See also**: [../vbrief/vbrief.md](../vbrief/vbrief.md) | [./references.md](./references.md) | [../main.md](../../main.md)
8
+ **See also**: [../vbrief/vbrief.md](../vbrief/vbrief.md) | [./references.md](./references.md) | [../main.md](../main.md)
8
9
 
9
10
  ---
10
11
 
@@ -19,13 +20,13 @@ YYYY-MM-DD-<slug>.vbrief.json
19
20
  - ! The leading date is the **creation date** in `YYYY-MM-DD` form. It is immutable — it MUST NOT change as the scope moves through the lifecycle.
20
21
  - ! The `<slug>` is a lowercase hyphen-separated descriptor derived from the scope title (or origin issue title for ingested vBRIEFs).
21
22
  - ! The filename MUST end in `.vbrief.json`.
22
- - ! The filename MUST match `scripts/vbrief_validate.py`'s `FILENAME_PATTERN`: `^\d{4}-\d{2}-\d{2}-[a-z0-9]+(?:-[a-z0-9]+)*\.vbrief\.json$`.
23
+ - ! The filename MUST match `task xbrief:validate`'s `FILENAME_PATTERN`: `^\d{4}-\d{2}-\d{2}-[a-z0-9]+(?:-[a-z0-9]+)*\.vbrief\.json$`.
23
24
 
24
25
  speckit Phase 4 scope vBRIEFs use the extended pattern `YYYY-MM-DD-ip<NNN>-<slug>.vbrief.json` where `<NNN>` is the implementation-phase index zero-padded to exactly three digits (e.g. `ip001`, `ip042`, `ip128`). See [`../vbrief/vbrief.md`](../vbrief/vbrief.md#speckit-phase-4-scope-vbriefs) for detail.
25
26
 
26
27
  ## Slug Normalization Rules
27
28
 
28
- `scripts/slug_normalize.py` exposes the canonical `normalize_slug(title, issue_number=None)` function. Every tool or skill that coins a scope vBRIEF filename MUST either call that function or apply the same rules documented below so that two different producers always agree on the slug for the same title.
29
+ `conventions/vbrief-filenames.md` exposes the canonical `normalize_slug(title, issue_number=None)` function. Every tool or skill that coins a scope vBRIEF filename MUST either call that function or apply the same rules documented below so that two different producers always agree on the slug for the same title.
29
30
 
30
31
  The rules, applied in order:
31
32
 
@@ -67,4 +68,4 @@ Scope vBRIEF filenames are part of the file's identity. Renames MUST preserve th
67
68
  - ⊗ Use uppercase letters, underscores, or camelCase in the slug
68
69
  - ⊗ Put the origin issue number in the date segment (`2026-04-22-#541-...`) — the issue number belongs in the suffix (`...-issue-541`) or in `references`
69
70
  - ⊗ Change the date prefix when a scope moves between lifecycle folders — the date is the creation date, not the current-status date
70
- - ⊗ Coin slugs by hand inside skills; call `scripts/slug_normalize.py` (`normalize_slug`) instead
71
+ - ⊗ Coin slugs by hand inside skills; call `conventions/vbrief-filenames.md` (`normalize_slug`) instead
@@ -39,6 +39,41 @@ A **partial local** root check aggregate still fails closed — the include must
39
39
 
40
40
  Root cause of red `greenfield-python-free-smoke` after #3145: the gate only inspected the root Taskfile, treated include-only greenfield as “no check composition,” and hard-failed (`exit 201` via #3188). That was a **gate false positive** for the intentional deposit shape, not a missing deposit wiring bug.
41
41
 
42
+ ## Merge-chokepoint gate scoping (#3893)
43
+
44
+ Some gates are repo-wide by default and must be **narrowed** when composed on a
45
+ merge chokepoint. `verify:orphan-active` is the first: composed unscoped it
46
+ fails a candidate for lifecycle residue another merge stranded, and N stranded
47
+ briefs make N single-brief lifecycle PRs mutually unmergeable.
48
+
49
+ The contract therefore records a required argument per gate
50
+ (`MERGE_CHOKEPOINT_SCOPED_GATE_ARGS`) and reports a check aggregate that lists
51
+ the gate without it:
52
+
53
+ ```yaml
54
+ check:consumer:
55
+ deps:
56
+ - task: verify:orphan-active
57
+ vars:
58
+ CLI_ARGS: "--changed-only"
59
+ ```
60
+
61
+ The check reads the dependency's effective `CLI_ARGS` value, not the raw entry
62
+ text, so the flag appearing in a comment, a sibling variable, or a descriptive
63
+ value does not satisfy it.
64
+
65
+ - **`--framework-source`: fail closed.** This repo owns its own composition, so
66
+ a regression to the unscoped form is a hard failure here.
67
+ - **Consumer deposits: warn.** `deft update` re-deposits the Taskfile; the
68
+ warning names the exact one-line repair in the meantime.
69
+ - **Aggregates that do not list the gate are silent.** Include-only greenfield
70
+ roots and orchestrator-body `check` tasks are unaffected.
71
+
72
+ This is a strengthening, not a relaxation: nothing about the detector, the
73
+ required-gate set, or any exit code is weakened. Repo-wide residue truth still
74
+ runs on the bare verb, at the delivery tip, and on the after-merge
75
+ `verify:orphan-active -- --issue N` DONE gate (#3429).
76
+
42
77
  ## Repair path
43
78
 
44
79
  1. Restore deposit Taskfiles: `deft update` (includes `tasks/verify.yml` under `.deft/core/`)
@@ -34,6 +34,8 @@ MUST evaluate this gate before automatic retry or re-dispatch.
34
34
  | `BLOCK_ELAPSED_BUDGET` | Wall-clock budget exhausted |
35
35
  | `BLOCK_TOOL_OR_TOKEN_BUDGET` | Tool-call (or host-token when telemetried) budget exhausted |
36
36
 
37
+ evaluateInFlight(ledger, input) applies the same elapsed budget to a run that is already queued or running. Pre-dispatch never sees that case: an active attempt is DENY_DUPLICATE_ACTIVE. In-flight elapsed is totalElapsedSeconds plus wall-clock since startedAt. Under budget returns ALLOW_RESUME. Exhausted returns BLOCK_ELAPSED_BUDGET. No second budget store.
38
+
37
39
  Once a **block** decision is emitted, automatic re-dispatch MUST stop until a
38
40
  declared resume condition is satisfied or an audited operator override is
39
41
  recorded. Persist the terminal handoff (`buildTerminalHandoff` /
@@ -1,3 +1,4 @@
1
+ <!-- deft:deposit-link-rewrite v=1 source="content/docs/gate-integrity.md" -->
1
2
  # Gate integrity — a failing gate must not be fixed by editing the gate (#3156)
2
3
 
3
4
  General product and process rule for Directive fix loops, refine loops, and quality-gate repair: **when a gate is red, clear red by fixing the work under test — not by mutating the gate.**
@@ -80,7 +81,7 @@ Field notes and parent framing: issue [#3156](https://github.com/deftai/directiv
80
81
  ## Discoverability
81
82
 
82
83
  - Pre-PR Diff phase checklist: [deft-directive-pre-pr](../skills/deft-directive-pre-pr/SKILL.md) (gate-integrity bullet).
83
- - Stance / propose-not-apply: [main.md § Self-Improving, Not Self-Editing (#3164)](../../main.md#self-improving-not-self-editing-3164), [philosophy.md](../meta/philosophy.md).
84
+ - Stance / propose-not-apply: [main.md § Self-Improving, Not Self-Editing (#3164)](../main.md#self-improving-not-self-editing-3164), [philosophy.md](../meta/philosophy.md).
84
85
  - Verification outcomes: [verification.md](../verification/verification.md).
85
86
  - Goal/gate rigidity: [goal-gate-determinism.md](../patterns/goal-gate-determinism.md) (#852).
86
87
  - Scope self-auth instance: [scope-provenance.md](./scope-provenance.md) (#3145).
@@ -94,13 +95,27 @@ Full CI automation that blocks “diff touches a gate that just failed” withou
94
95
  | Topic | Where |
95
96
  |-------|--------|
96
97
  | Parent epic | [#3179](https://github.com/deftai/directive/issues/3179) |
97
- | Stance (propose-not-apply) | [#3164](https://github.com/deftai/directive/issues/3164), [main.md](../../main.md#self-improving-not-self-editing-3164) |
98
+ | Stance (propose-not-apply) | [#3164](https://github.com/deftai/directive/issues/3164), [main.md](../main.md#self-improving-not-self-editing-3164) |
98
99
  | Refine-internal SkillOpt | [#2436](https://github.com/deftai/directive/issues/2436) |
99
100
  | Fixed evaluator / agent-loop | [#782](https://github.com/deftai/directive/issues/782) |
100
101
  | Verification independence | [#1499](https://github.com/deftai/directive/issues/1499) |
101
102
  | Scope self-authorization | [#3145](https://github.com/deftai/directive/issues/3145), [scope-provenance.md](./scope-provenance.md) |
102
103
  | Host self-mutate honesty | [#3162](https://github.com/deftai/directive/issues/3162), [host-surface-assumptions.md](./host-surface-assumptions.md) |
103
104
  | Safety via formal gates | [#1200](https://github.com/deftai/directive/issues/1200) |
105
+ | Poisoned product-oracle history from a safety refusal | [#3615](https://github.com/deftai/directive/issues/3615), this page § Product-oracle history poisoned by a safety refusal |
106
+
107
+ ---
108
+
109
+ ## Product-oracle history poisoned by a safety refusal (#3615)
110
+
111
+ A safety-refused acceptance command is not a product measurement. `verify:ac` keeps the acceptance gate red, and it does not write `verification.outcome=fail` for an all-refused walk. A mixed walk that still has a refusal does not write a product-oracle pass.
112
+
113
+ Records already written still pair on `session_id` + `check_id`. A correct classifier going forward does not unwrite a `fail` already on disk. Recovery is an operator workaround, not independent re-derivation:
114
+
115
+ 1. Start a new Deft session (new `DEFT_SESSION_ID`) so the integrity layer does not pair against the poisoned fail, or
116
+ 2. Truncate or delete the run-summary JSONL (default `.deft-run-summary.json`, typically gitignored).
117
+
118
+ ⊗ Set `independent_rederivation=true` to clear this class. That flag asserts both sides were rebuilt from scratch. The refused side never executed.
104
119
 
105
120
  ---
106
121
 
@@ -1,3 +1,4 @@
1
+ <!-- deft:deposit-link-rewrite v=1 source="content/docs/host-surface-assumptions.md" -->
1
2
  # Host-surface assumptions: file gates vs REPL / self-mutating hosts (#3162)
2
3
 
3
4
  Directive control surfaces assume two host behaviors that **some** modern agent hosts break by design.
@@ -131,7 +132,7 @@ Named hook home for managed-path deny: **Tier-1 host hooks** (`packages/core/src
131
132
 
132
133
  | Topic | Where |
133
134
  |-------|--------|
134
- | Self-improving, not self-editing stance | [main.md § Self-Improving, Not Self-Editing (#3164)](../../main.md#self-improving-not-self-editing-3164), [philosophy.md](../meta/philosophy.md) |
135
+ | Self-improving, not self-editing stance | [main.md § Self-Improving, Not Self-Editing (#3164)](../main.md#self-improving-not-self-editing-3164), [philosophy.md](../meta/philosophy.md) |
135
136
  | Gate integrity (do not clear red by editing the gate) | [gate-integrity.md](./gate-integrity.md) (#3156); refine-internal #2436 separate |
136
137
  | Skill pin tiers (process pins, not full #830) | [skill-pin-policy.md](./skill-pin-policy.md) |
137
138
  | Host lifecycle duty list | [host-lifecycle-duties.md](../contracts/host-lifecycle-duties.md) |
@@ -35,6 +35,37 @@ aggregate unknown would break `--skip-gh`, offline runs, and fresh clones.
35
35
 
36
36
  ---
37
37
 
38
+ ## What each run is allowed to look at (#3893)
39
+
40
+ Mode decides *how* a verdict is reached. Scope decides *which briefs* are in
41
+ the question at all.
42
+
43
+ | | Bare / `--issue N` | `--changed-only` |
44
+ |---|---|---|
45
+ | Caller | doctor, manual runs, swarm finalize, after-merge DONE proof | `check:framework-source` / `check:consumer` merge chokepoint |
46
+ | Briefs evaluated | every running brief under `active/` | only briefs in the candidate's own diff against `origin/<deliveryBranch>` |
47
+ | On the delivery line | n/a | falls back to the full sweep -- this run *is* the delivery-tip check |
48
+ | Unresolvable git or base ref | n/a | falls back to the full sweep, never to a narrower scan |
49
+
50
+ The merge chokepoint was unscoped until #3893. Measured 2026-08-28: four
51
+ unrelated PRs failed the same two lanes on one stranded brief, one of them
52
+ passed an unchanged re-run once the strandings landed, and two single-brief
53
+ lifecycle PRs could not pass alone because each left the other orphan.
54
+
55
+ The pre-merge run was never preventive for its own candidate either: the gate
56
+ reads REST `merged_at`, and the candidate's linked PR still reads null while
57
+ that candidate's gate runs. The pre-merge signal for closeout residue is
58
+ `verify:pr-closeout-attestable` (#3781), which keys on the PR's structured
59
+ closing references rather than the branch diff.
60
+
61
+ The detector is unchanged. The linked-PR/open-issue signature still fails a
62
+ candidate whose own diff carries it.
63
+
64
+ ⊗ Do not read a `--changed-only` pass as repo-wide truth. Every run prints its
65
+ scope; `EvaluateResult.scope` carries the same thing structurally.
66
+
67
+ ---
68
+
38
69
  ## The freshness choice
39
70
 
40
71
  **Age bound plus re-validation, applied per mode.** Both options offered by
@@ -164,3 +195,5 @@ mode for callers.
164
195
  - [#3476](https://github.com/deftai/directive/issues/3476) — `verify:completed-tracked`
165
196
  - [#3752](https://github.com/deftai/directive/issues/3752) — the open-inventory mechanism reused here
166
197
  - [#3156](https://github.com/deftai/directive/issues/3156) — gate integrity; this was a deliberate gate-definition change
198
+ - [#3893](https://github.com/deftai/directive/issues/3893) — merge-chokepoint scoping; the named check-composition change
199
+ - [#3781](https://github.com/deftai/directive/issues/3781) — `verify:pr-closeout-attestable`, the pre-merge closeout signal
@@ -13,7 +13,7 @@ Pins are for **false-negative-sensitive process gates**, not for copying entire
13
13
  | Tier | Meaning | How the agent discovers it |
14
14
  |---|---|---|
15
15
  | **always-pin** | Named in AGENTS.md (managed or consumer unmanaged header) as a required `SKILL.md` load when a matching work type starts | Always-loaded AGENTS.md |
16
- | **on-demand** | Routed via Skills Index triggers in `REFERENCES.md` — scan Level-0, read Level-1 on match | Skills Index → `SKILL.md` |
16
+ | **on-demand** | Routed via Skills Index triggers (consumer: `npx deft packs:slice skills list`; maintainer: `REFERENCES.md`) — scan Level-0, read Level-1 on match | Skills Index → `SKILL.md` |
17
17
  | **reference-only** | External, rare, or maintainer-only corpora; no standing trigger routing | Explicit doc pointer only |
18
18
 
19
19
  ### Criteria for always-pin
package/events/README.md CHANGED
@@ -27,11 +27,11 @@ data-file-convention check ([#710](https://github.com/deftai/directive/issues/71
27
27
 
28
28
  - **`detection-bound`** — detectable from filesystem state alone (e.g. dirty
29
29
  tree, vBRIEF schema invalidity, version drift). Emitted via
30
- `scripts/_event_detect.py`. Detector lives at the call site documented in
30
+ `task lifecycle:event`. Detector lives at the call site documented in
31
31
  the entry's `trigger` field.
32
32
  - **`behavioral`** — requires runtime instrumentation (paired
33
33
  `session:interrupted` / `session:resumed`, `plan:approved`,
34
- `legacy:detected`). Emitted via `scripts/_events.py`, which manages 1:1
34
+ `legacy:detected`). Emitted via `task lifecycle:event`, which manages 1:1
35
35
  session-pair invariants and a JSONL append-only log at
36
36
  `<project_root>/.deft-cache/events.jsonl`.
37
37
 
@@ -48,29 +48,28 @@ conforming to `event-record.schema.json`:
48
48
  }
49
49
  ```
50
50
 
51
- `scripts/_event_detect.py::emit(name, payload)` validates against the full
51
+ `task lifecycle:event -- emit <name> ...` validates against the full
52
52
  registry (any registered name is accepted) and is silent by default; when the
53
53
  `DEFT_EVENT_LOG` environment variable points to a writable path, each
54
54
  emission is appended as a single JSON line.
55
55
 
56
- `scripts/_events.py::emit(name, payload)` validates against the
57
- `category="behavioral"` subset of the registry, generates a sortable event id
58
- for pairing semantics, enforces required-payload contracts, and persists to
59
- `<project_root>/.deft-cache/events.jsonl` (or a path injected via `log_path` /
56
+ Behavioral emits validate against the `category="behavioral"` subset of the
57
+ registry, generate a sortable event id for pairing semantics, enforce
58
+ required-payload contracts, and persist to
59
+ `<project_root>/.deft-cache/events.jsonl` (or a path injected via `--log` /
60
60
  `DEFT_EVENT_LOG`). The log lives under the already-gitignored `.deft-cache/`
61
61
  rather than `.deft/`, because `.deft/` is not blanket-gitignored on hybrid
62
62
  installs (`.deft/core/` is gitignored and reconstituted by `directive init`,
63
- per #1942 / #1465). Use this helper when emitting behavioral events from
64
- skills (`python -m scripts._events emit ...`).
63
+ per #1942 / #1465). Skills emit behavioral events with
64
+ `task lifecycle:event -- emit <name> ...`.
65
65
 
66
66
  ## Adding an event
67
67
 
68
68
  1. Append the entry to `registry.json` with the appropriate `category`,
69
69
  payload contract, trigger pointer, and consumer pointers (validate via
70
- `tests/cli/test_events.py` and `tests/cli/test_behavioral_events.py`).
71
- 2. Add the detection / emission call site in `scripts/` or the relevant
72
- surface (use `scripts/_event_detect.py` for detection-bound,
73
- `scripts/_events.py` for behavioral).
70
+ `packages/core/src/lifecycle/events.ts` tests).
71
+ 2. Add the detection / emission call site in the TypeScript lifecycle surface
72
+ (use `task lifecycle:event` for both detection-bound and behavioral events).
74
73
  3. Reference the event by name from at least one consumer (skill, task,
75
74
  script) so the surface stays usable -- the schema requires `consumers`
76
75
  to be a non-empty array.
package/glossary.md CHANGED
@@ -1,3 +1,4 @@
1
+ <!-- deft:deposit-link-rewrite v=1 source="content/glossary.md" -->
1
2
  # Glossary
2
3
 
3
4
  The authoritative vocabulary for the Deft framework.
@@ -120,7 +121,7 @@ Canonical vocabulary for the xBRIEF lifecycle. **xBRIEF** / `xbrief/` is the sol
120
121
 
121
122
  - **Cross-scope dependency** -- Dependency between two scope xBRIEFs at `plan.metadata.dependencies` (array of dependency IDs). See [vbrief/vbrief.md](./vbrief/vbrief.md).
122
123
 
123
- - **Exit Commands** -- The seven deterministic `task scope:*` commands that transition a scope xBRIEF between lifecycle folders: `scope:promote`, `scope:activate`, `scope:complete`, `scope:cancel`, `scope:restore`, `scope:block`, `scope:unblock` (see [tasks/scope.yml](../tasks/scope.yml)).
124
+ - **Exit Commands** -- The seven deterministic `task scope:*` commands that transition a scope xBRIEF between lifecycle folders: `scope:promote`, `scope:activate`, `scope:complete`, `scope:cancel`, `scope:restore`, `scope:block`, `scope:unblock` (see [tasks/scope.yml](tasks/scope.yml)).
124
125
 
125
126
  - **Origin provenance** -- A `references` entry linking a scope xBRIEF to its origin issue / ticket / user-request. Required for ingestion dedup. See [vbrief/vbrief.md](./vbrief/vbrief.md).
126
127
 
@@ -1,3 +1,4 @@
1
+ <!-- deft:deposit-link-rewrite v=1 source="content/incidents/README.md" -->
1
2
  # Incidents Library
2
3
 
3
4
  This directory captures real-world incidents that motivated specific Deft
@@ -53,7 +54,7 @@ Examples:
53
54
  - `2026-04-15-some-other-incident.md`
54
55
 
55
56
  Filenames use hyphens, not underscores -- this mirrors the project-wide
56
- filename convention in [`main.md`](../../main.md).
57
+ filename convention in [`main.md`](../main.md).
57
58
 
58
59
  ## When to add an entry
59
60
 
package/interfaces/cli.md CHANGED
@@ -1,3 +1,4 @@
1
+ <!-- deft:deposit-link-rewrite v=1 source="content/interfaces/cli.md" -->
1
2
  # CLI Best Practices
2
3
 
3
4
  Opinionated patterns for command-line interfaces with AI agents.
@@ -110,4 +111,4 @@ else:
110
111
 
111
112
  ---
112
113
 
113
- **See also**: [python.md](../languages/python.md) | [typescript.md](../languages/typescript.md) | [tui.md](../interfaces/tui.md)
114
+ **See also**: [python.md](../languages/python.md) | [typescript.md](../languages/typescript.md) | [tui.md](tui.md)
@@ -1,8 +1,9 @@
1
+ <!-- deft:deposit-link-rewrite v=1 source="content/languages/6502-DASM.md" -->
1
2
  # 6502 + DASM Standards
2
3
 
3
4
  Legend (from RFC2119): !=MUST, ~=SHOULD, ≉=SHOULD NOT, ⊗=MUST NOT, ?=MAY.
4
5
 
5
- **⚠️ See also**: [main.md](../../main.md) | [PROJECT.md](../../PROJECT.md)
6
+ **⚠️ See also**: [main.md](../main.md) | [PROJECT.md](../../PROJECT.md)
6
7
 
7
8
  ## Scope
8
9
  - This guide covers:
package/languages/c.md CHANGED
@@ -1,8 +1,9 @@
1
+ <!-- deft:deposit-link-rewrite v=1 source="content/languages/c.md" -->
1
2
  # C Standards
2
3
 
3
4
  Legend (from RFC2119): !=MUST, ~=SHOULD, ≉=SHOULD NOT, ⊗=MUST NOT, ?=MAY.
4
5
 
5
- **⚠️ See also**: [main.md](../../main.md) | [PROJECT.md](../../PROJECT.md) | [telemetry.md](../tools/telemetry.md)
6
+ **⚠️ See also**: [main.md](../main.md) | [PROJECT.md](../../PROJECT.md) | [telemetry.md](../tools/telemetry.md)
6
7
 
7
8
  **Stack**: C17/C23, CMake 3.25+, clang/GCC; Testing: Unity/cmocka; Analysis: clang-tidy, cppcheck, AddressSanitizer
8
9
 
package/languages/cpp.md CHANGED
@@ -1,8 +1,9 @@
1
+ <!-- deft:deposit-link-rewrite v=1 source="content/languages/cpp.md" -->
1
2
  # C++ Standards
2
3
 
3
4
  Legend (from RFC2119): !=MUST, ~=SHOULD, ≉=SHOULD NOT, ⊗=MUST NOT, ?=MAY.
4
5
 
5
- **⚠️ See also**: [main.md](../../main.md) | [PROJECT.md](../../PROJECT.md) | [telemetry.md](../tools/telemetry.md)
6
+ **⚠️ See also**: [main.md](../main.md) | [PROJECT.md](../../PROJECT.md) | [telemetry.md](../tools/telemetry.md)
6
7
 
7
8
  **Stack**: C++20/23, CMake 3.25+, Catch2/GoogleTest, GSL (Guidelines Support Library); CLI: CLI11; TUI: FTXUI; Async: Asio/libcoro
8
9
 
@@ -1,8 +1,9 @@
1
+ <!-- deft:deposit-link-rewrite v=1 source="content/languages/csharp.md" -->
1
2
  # C# Standards
2
3
 
3
4
  Legend (from RFC2119): !=MUST, ~=SHOULD, ≉=SHOULD NOT, ⊗=MUST NOT, ?=MAY.
4
5
 
5
- **⚠️ See also**: [main.md](../../main.md) | [PROJECT.md](../../PROJECT.md) | [telemetry.md](../tools/telemetry.md)
6
+ **⚠️ See also**: [main.md](../main.md) | [PROJECT.md](../../PROJECT.md) | [telemetry.md](../tools/telemetry.md)
6
7
 
7
8
  **Stack**: C# 12+/.NET 8+ (LTS), SDK-style projects; Web: ASP.NET Core/Minimal APIs; Testing: xUnit + NSubstitute + FluentAssertions; Analysis: Roslyn Analyzers, StyleCop.Analyzers
8
9
 
package/languages/dart.md CHANGED
@@ -1,8 +1,9 @@
1
+ <!-- deft:deposit-link-rewrite v=1 source="content/languages/dart.md" -->
1
2
  # Dart Standards
2
3
 
3
4
  Legend (from RFC2119): !=MUST, ~=SHOULD, ≉=SHOULD NOT, ⊗=MUST NOT, ?=MAY.
4
5
 
5
- **⚠️ See also**: [main.md](../../main.md) | [PROJECT.md](../../PROJECT.md)
6
+ **⚠️ See also**: [main.md](../main.md) | [PROJECT.md](../../PROJECT.md)
6
7
 
7
8
  **Stack**: Dart 3.x+; Framework: Flutter (mobile/web) or standalone; Testing: `package:test` / `flutter_test`; Lint: `analysis_options.yaml` + custom_lint; Format: `dart format`; Docs: dartdoc
8
9
 
@@ -1,8 +1,9 @@
1
+ <!-- deft:deposit-link-rewrite v=1 source="content/languages/delphi.md" -->
1
2
  # Delphi / Object Pascal Standards
2
3
 
3
4
  Legend (from RFC2119): !=MUST, ~=SHOULD, ≉=SHOULD NOT, ⊗=MUST NOT, ?=MAY.
4
5
 
5
- **⚠️ See also**: [main.md](../../main.md) | [PROJECT.md](../../PROJECT.md) | [telemetry.md](../tools/telemetry.md)
6
+ **⚠️ See also**: [main.md](../main.md) | [PROJECT.md](../../PROJECT.md) | [telemetry.md](../tools/telemetry.md)
6
7
 
7
8
  **Stack**: Delphi 12+/Object Pascal, RAD Studio; GUI: VCL (Windows), FMX (cross-platform); DB: FireDAC; Testing: DUnitX
8
9
 
@@ -1,8 +1,9 @@
1
+ <!-- deft:deposit-link-rewrite v=1 source="content/languages/elixir.md" -->
1
2
  # Elixir Standards
2
3
 
3
4
  Legend (from RFC2119): !=MUST, ~=SHOULD, ≉=SHOULD NOT, ⊗=MUST NOT, ?=MAY.
4
5
 
5
- **⚠️ See also**: [main.md](../../main.md) | [PROJECT.md](../../PROJECT.md)
6
+ **⚠️ See also**: [main.md](../main.md) | [PROJECT.md](../../PROJECT.md)
6
7
 
7
8
  **Stack**: Elixir 1.16+ / OTP 26+; Build: Mix; Testing: ExUnit; Lint: Credo; Format: `mix format`; Docs: ExDoc; Types: Dialyxir
8
9
 
package/languages/go.md CHANGED
@@ -1,8 +1,9 @@
1
+ <!-- deft:deposit-link-rewrite v=1 source="content/languages/go.md" -->
1
2
  # Go Standards
2
3
 
3
4
  Legend (from RFC2119): !=MUST, ~=SHOULD, ≉=SHOULD NOT, ⊗=MUST NOT, ?=MAY.
4
5
 
5
- **⚠️ See also**: [main.md](../../main.md) | [PROJECT.md](../../PROJECT.md) | [telemetry.md](../tools/telemetry.md)
6
+ **⚠️ See also**: [main.md](../main.md) | [PROJECT.md](../../PROJECT.md) | [telemetry.md](../tools/telemetry.md)
6
7
 
7
8
  ## Standards
8
9
 
package/languages/java.md CHANGED
@@ -1,8 +1,9 @@
1
+ <!-- deft:deposit-link-rewrite v=1 source="content/languages/java.md" -->
1
2
  # Java Standards
2
3
 
3
4
  Legend (from RFC2119): !=MUST, ~=SHOULD, ≉=SHOULD NOT, ⊗=MUST NOT, ?=MAY.
4
5
 
5
- **⚠️ See also**: [main.md](../../main.md) | [PROJECT.md](../../PROJECT.md) | [telemetry.md](../tools/telemetry.md)
6
+ **⚠️ See also**: [main.md](../main.md) | [PROJECT.md](../../PROJECT.md) | [telemetry.md](../tools/telemetry.md)
6
7
 
7
8
  **Stack**: Java 21+ (LTS), Maven/Gradle; Web: Spring Boot 3+; Testing: JUnit 5 + Mockito + AssertJ; Analysis: Checkstyle, SpotBugs, Error Prone
8
9
 
@@ -1,8 +1,9 @@
1
+ <!-- deft:deposit-link-rewrite v=1 source="content/languages/javascript.md" -->
1
2
  # JavaScript Standards
2
3
 
3
4
  Legend (from RFC2119): !=MUST, ~=SHOULD, ≉=SHOULD NOT, ⊗=MUST NOT, ?=MAY.
4
5
 
5
- **⚠️ See also**: [main.md](../../main.md) | [PROJECT.md](../../PROJECT.md) | [telemetry.md](../tools/telemetry.md)
6
+ **⚠️ See also**: [main.md](../main.md) | [PROJECT.md](../../PROJECT.md) | [telemetry.md](../tools/telemetry.md)
6
7
 
7
8
  **Stack**: ES2022+, Node 20+; Build: Vite/esbuild; Testing: Vitest/Jest; Lint: ESLint + Prettier; Web: React/Next.js
8
9
 
@@ -1,8 +1,9 @@
1
+ <!-- deft:deposit-link-rewrite v=1 source="content/languages/julia.md" -->
1
2
  # Julia Standards
2
3
 
3
4
  Legend (from RFC2119): !=MUST, ~=SHOULD, ≉=SHOULD NOT, ⊗=MUST NOT, ?=MAY.
4
5
 
5
- **⚠️ See also**: [main.md](../../main.md) | [PROJECT.md](../../PROJECT.md)
6
+ **⚠️ See also**: [main.md](../main.md) | [PROJECT.md](../../PROJECT.md)
6
7
 
7
8
  **Stack**: Julia 1.10+; Packages: Pkg.jl; Testing: `Test` stdlib + Aqua.jl; Docs: Documenter.jl; Format: JuliaFormatter.jl; Lint: JET.jl / Aqua.jl
8
9
 
@@ -1,8 +1,9 @@
1
+ <!-- deft:deposit-link-rewrite v=1 source="content/languages/kotlin.md" -->
1
2
  # Kotlin Standards
2
3
 
3
4
  Legend (from RFC2119): !=MUST, ~=SHOULD, ≉=SHOULD NOT, ⊗=MUST NOT, ?=MAY.
4
5
 
5
- **⚠️ See also**: [main.md](../../main.md) | [PROJECT.md](../../PROJECT.md)
6
+ **⚠️ See also**: [main.md](../main.md) | [PROJECT.md](../../PROJECT.md)
6
7
 
7
8
  **Stack**: Kotlin 2.0+; Build: Gradle (Kotlin DSL); Testing: JUnit 5 + kotest; Lint: detekt; Format: ktfmt/ktlint; Docs: KDoc
8
9