wicked-garden 12.50.0 → 12.51.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +19 -0
- package/package.json +1 -1
- package/scripts/_context_backend.py +0 -3
- package/scripts/_estate_client.py +47 -6
- package/scripts/mem/estate_memory.py +21 -11
- package/skills/mem/SKILL.md +2 -1
- package/skills/mem-capture/SKILL.md +5 -2
- package/skills/qe/refs/setup.md +12 -4
- package/skills/repo-learn/SKILL.md +6 -3
- package/skills/repo-learn/refs/capture-proposals.md +10 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name": "wicked-garden", "owner": {"name": "Mike Parcewski"}, "plugins": [{"name": "wicked-garden", "source": "./", "description": "Your coding agent already plans and swarms \u2014 Wicked Garden is the curated toolkit for what it can't: proof-not-claims gates, code links grep can't see, deterministic refactor, cross-session memory, multi-model second opinions (fallback seats labelled). Don't fight the harness; fill its gaps. The gate needs wicked-vault (a self-contained infra peer, installed directly) and re-derives through garden's built-in engine; estate/bus are opt-in layers (wicked-testing and wicked-brain are retired \u2014 QE ships in-catalog as the qe domain, memory lives in wicked-estate).", "version": "12.
|
|
1
|
+
{"name": "wicked-garden", "owner": {"name": "Mike Parcewski"}, "plugins": [{"name": "wicked-garden", "source": "./", "description": "Your coding agent already plans and swarms \u2014 Wicked Garden is the curated toolkit for what it can't: proof-not-claims gates, code links grep can't see, deterministic refactor, cross-session memory, multi-model second opinions (fallback seats labelled). Don't fight the harness; fill its gaps. The gate needs wicked-vault (a self-contained infra peer, installed directly) and re-derives through garden's built-in engine; estate/bus are opt-in layers (wicked-testing and wicked-brain are retired \u2014 QE ships in-catalog as the qe domain, memory lives in wicked-estate).", "version": "12.51.1"}], "version": "12.51.1"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wicked-garden",
|
|
3
|
-
"version": "12.
|
|
3
|
+
"version": "12.51.1",
|
|
4
4
|
"description": "Your coding agent's harness already plans and swarms; Wicked Garden is the curated toolkit for what it can't do alone. Proof, not claims \u2014 gates re-derive 'done' through the internal loom engine (fail-closed, independently attested via wicked-vault), so a self-asserted pass can't lie its way green. Relationships grep can't see \u2014 the wicked-estate code graph + injected bus/dispatch/capability/archetype edges power blast-radius & lineage. Plus deterministic multi-file refactor (wicked-patch), cross-session memory + knowledge (the mem domain over wicked-estate), multi-model second opinions (fallback seats labelled) (jam:council), portable expertise as on-demand skill-refs, and evidence-gated testing (the in-catalog qe domain). It reads the shape of your work to apply the right rigor \u2014 steering, not a pipeline \u2014 then gets out of the way. The compiler stamps a self-contained evidence gate into any repo that runs with no wicked-garden installed. Don't fight the harness; fill its gaps. The evidence gate requires wicked-vault, a self-contained infra peer wicked-garden installs directly; wicked-bus is an opt-in layer \u2014 install what you'll use, the toolkit works without the rest.",
|
|
5
5
|
"wicked_vault_version": "^0.5.0",
|
|
6
6
|
"wicked_ledger_version": "^0.6.0",
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [12.51.1] — 2026-10-11
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- **Memory scope is the checkout, not the cwd (w9-memfix2, codex post-merge review of #1274).** `project:<name>` now comes from the git checkout (a linked worktree resolves to its main checkout), so recalling from `app/src` sees what was stored from `app/`; `WICKED_ESTATE_MEMORY_SCOPE` is the configured project for both capture and recall. Two unrelated checkouts with the same name still share a scope unless one is configured.
|
|
9
|
+
- **The durability gate keeps durable constants and constraints.** A version is refused only next to release status ("released 2.4.1"), so "keep SQLite >= 3.35.0 because RETURNING" is stored; a hex run counts as a commit SHA only when long (>= 12) or in commit context, so a CRC32 polynomial or a colour is kept.
|
|
10
|
+
- **repo-learn no longer submits unapprovable knowledge proposals.** Estate promotes only `memory` and `policy:*`, so the shim refuses `kind_type: "knowledge"` and repo-learn lists those objects as pending in its report until estate's knowledge proposal type ships.
|
|
11
|
+
|
|
12
|
+
## [12.51.0] — 2026-10-11
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
- **`wicked-garden-qe` setup scaffolds the ledger root with Python (wicked-core#881, wicked-crew#951, PR #1277).** It no longer uses a shell `mkdir` of `$WICKED_QE_LEDGER_DIR`. A governed run's write fence judges a shell write target as written, and the unexpanded variable read as a path inside the worktree, so a governed QE unit's scaffold was refused (rig run 46fb2c04). With wicked-core#889 (core-ts 0.9.2), the QE unit runs garden's pipeline into its own roots.
|
|
16
|
+
|
|
17
|
+
|
|
5
18
|
## [12.50.0] — 2026-10-11
|
|
6
19
|
|
|
7
20
|
### Added
|
|
@@ -13,6 +26,12 @@
|
|
|
13
26
|
|
|
14
27
|
### Changed
|
|
15
28
|
- **The estate client follows estate's paging cursor to the whole answer (estate#279, PR #1272).**
|
|
29
|
+
- **Memory creation keeps learnings, not narration (w9-memfix, PR #1274).**
|
|
30
|
+
- The Stop hook's regex fact extraction and its auto-memorize drain are removed: `scripts/mem/session_fact_extractor.py`, `scripts/mem/auto_memorize.py`, and the `wicked.garden.fact.extracted` event. They filed about 333 sentence fragments as semantic facts.
|
|
31
|
+
- `pre_compact` saves a working memory only when tasks are in progress, always scoped `project:<cwd-basename>` (it wrote one empty, unscoped "WIP snapshot" per compaction).
|
|
32
|
+
- Memory recall (`recall`, `sources`, the hooks' context backend) defaults to the current project and its ancestors; every scope is the explicit `scope_prefix: ""` opt-in. `recall`/`sources` pass the repo intent, so faceted repo-learn memories are reachable, and `sources` labels every item `memory · <kind> · <date> · <scope>` or `knowledge · src <source>`.
|
|
33
|
+
- `store` / `capture-batch` refuse non-durable content (SHAs, versions, PR/run ids, PIDs, "today", first-person narration, under 40 chars) and source-cited claims (knowledge: use `write`). mem-capture and mem store apply the durability and told/witnessed/decided tests; the event → episode row is gone.
|
|
34
|
+
- Shim proposals need `facets.project`; repo-learn proposes code facts as `kind_type: "knowledge"` (`content, class, source, source_rev`) and never graph metrics.
|
|
16
35
|
|
|
17
36
|
## [12.49.0] — 2026-10-11
|
|
18
37
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wicked-garden",
|
|
3
|
-
"version": "12.
|
|
3
|
+
"version": "12.51.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Curated toolkit for what AI coding agents can't do alone: evidence-gated work, codegraph relationships, deterministic multi-file refactor, multi-model second opinions, and cross-session memory. Installs as a Claude Code plugin.",
|
|
6
6
|
"keywords": [
|
|
@@ -77,9 +77,6 @@ def _memory_scope() -> str:
|
|
|
77
77
|
its ancestors (root-scoped memories included) but not other projects.
|
|
78
78
|
Override via WICKED_ESTATE_MEMORY_SCOPE.
|
|
79
79
|
"""
|
|
80
|
-
pinned = os.environ.get("WICKED_ESTATE_MEMORY_SCOPE", "").strip()
|
|
81
|
-
if pinned:
|
|
82
|
-
return pinned
|
|
83
80
|
import _estate_client
|
|
84
81
|
|
|
85
82
|
return _estate_client.project_scope()
|
|
@@ -963,15 +963,49 @@ def context(
|
|
|
963
963
|
return call("ContextBundle", args, timeout=timeout)
|
|
964
964
|
|
|
965
965
|
|
|
966
|
-
def
|
|
967
|
-
"""The
|
|
966
|
+
def checkout_root() -> Optional[Path]:
|
|
967
|
+
"""The git checkout the cwd is in, resolved through linked worktrees.
|
|
968
968
|
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
969
|
+
Walks up to the nearest ``.git``. A linked worktree's ``.git`` is a file
|
|
970
|
+
(``gitdir: <main>/.git/worktrees/<name>``): its project is the MAIN checkout,
|
|
971
|
+
so a run worktree shares the repo's memories. None outside any checkout.
|
|
972
972
|
"""
|
|
973
973
|
try:
|
|
974
|
-
|
|
974
|
+
here = Path.cwd().resolve()
|
|
975
|
+
except OSError:
|
|
976
|
+
return None
|
|
977
|
+
for d in (here, *here.parents):
|
|
978
|
+
git = d / ".git"
|
|
979
|
+
if git.is_dir():
|
|
980
|
+
return d
|
|
981
|
+
if git.is_file():
|
|
982
|
+
try:
|
|
983
|
+
line = git.read_text(encoding="utf-8").strip()
|
|
984
|
+
except OSError:
|
|
985
|
+
return d
|
|
986
|
+
gitdir = line[len("gitdir:"):].strip() if line.startswith("gitdir:") else ""
|
|
987
|
+
marker = f"{os.sep}.git{os.sep}worktrees{os.sep}"
|
|
988
|
+
norm = gitdir.replace("/", os.sep)
|
|
989
|
+
return Path(norm.split(marker)[0]) if marker in norm else d
|
|
990
|
+
return None
|
|
991
|
+
|
|
992
|
+
|
|
993
|
+
def project_scope() -> str:
|
|
994
|
+
"""The current project's memory scope: one identity for capture AND recall.
|
|
995
|
+
|
|
996
|
+
``WICKED_ESTATE_MEMORY_SCOPE`` (the configured project) wins; else
|
|
997
|
+
``project:<checkout name>`` from :func:`checkout_root` (stable across
|
|
998
|
+
subdirectories and worktrees); else ``project:<cwd-basename>``. Two
|
|
999
|
+
unrelated checkouts with the same name share a scope unless one is
|
|
1000
|
+
configured. Recall sees this scope plus its ancestors (root-scoped
|
|
1001
|
+
memories included); every project is the explicit ``scope_prefix: ""``.
|
|
1002
|
+
"""
|
|
1003
|
+
pinned = os.environ.get("WICKED_ESTATE_MEMORY_SCOPE", "").strip()
|
|
1004
|
+
if pinned:
|
|
1005
|
+
return pinned
|
|
1006
|
+
root = checkout_root()
|
|
1007
|
+
try:
|
|
1008
|
+
name = (root.name if root else Path.cwd().name) or "unknown"
|
|
975
1009
|
except OSError:
|
|
976
1010
|
name = "unknown"
|
|
977
1011
|
return f"project:{name}"
|
|
@@ -1051,6 +1085,13 @@ def propose(
|
|
|
1051
1085
|
run's `WICKED_RUN_PROJECT` supplies): an unscoped proposal is refused here
|
|
1052
1086
|
with the shape-error code, so it never lands project-less in the queue.
|
|
1053
1087
|
"""
|
|
1088
|
+
if kind_type == "knowledge":
|
|
1089
|
+
# Gate (DES-memory-vs-knowledge K3): estate's approve promotes only memory + policy:*,
|
|
1090
|
+
# so a knowledge proposal would strand in the queue. Lift when estate ships the type.
|
|
1091
|
+
return {"ok": False, "code": -32602,
|
|
1092
|
+
"reason": 'kind_type "knowledge" is not approvable yet (estate promotes only '
|
|
1093
|
+
'memory and policy:*): keep the object in your report under '
|
|
1094
|
+
'"knowledge (pending)"; do not re-file it as a memory'}
|
|
1054
1095
|
facets = _scope_facets_to_run_project(facets)
|
|
1055
1096
|
project = (facets or {}).get("project")
|
|
1056
1097
|
if not isinstance(project, str) or not project.strip():
|
|
@@ -135,15 +135,11 @@ def _default_intent():
|
|
|
135
135
|
(the umbrella, e.g. ``wicked`` for ``wicked/wicked-estate``). Outside a git
|
|
136
136
|
checkout: {} (unfaceted memories only, as before).
|
|
137
137
|
"""
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
except OSError:
|
|
138
|
+
root = _estate_client.checkout_root()
|
|
139
|
+
if root is None:
|
|
141
140
|
return {}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
project = (os.environ.get("WICKED_RUN_PROJECT") or "").strip() or d.parent.name
|
|
145
|
-
return {k: v for k, v in (("repo", d.name), ("project", project)) if v}
|
|
146
|
-
return {}
|
|
141
|
+
project = (os.environ.get("WICKED_RUN_PROJECT") or "").strip() or root.parent.name
|
|
142
|
+
return {k: v for k, v in (("repo", root.name), ("project", project)) if v}
|
|
147
143
|
|
|
148
144
|
|
|
149
145
|
def _memory_recall_args(args, query, budget):
|
|
@@ -209,11 +205,21 @@ def _parse_budget(args):
|
|
|
209
205
|
# narration and status never reach the store even when the judgment slips.
|
|
210
206
|
MIN_CONTENT_CHARS = 40
|
|
211
207
|
_HEX_RUN = re.compile(r"\b[0-9a-f]{7,40}\b")
|
|
208
|
+
# A hex run is a commit id when it is long (>= 12) or sits in commit context; a short one
|
|
209
|
+
# alone (a CRC32 polynomial, a colour) is a constant, and constants are durable.
|
|
210
|
+
_COMMIT_CONTEXT = re.compile(
|
|
211
|
+
r"\b(?:commit|sha|rev(?:ision)?|cherry-?pick(?:ed)?|revert(?:ed)?|fixed in|landed in|merged as"
|
|
212
|
+
r"|pin(?:ned)?(?: moved)? (?:to|at))\b|@[0-9a-f]{7}", re.I)
|
|
213
|
+
# A version is narration only next to release status ("released 2.4.1", "shipped 0.7.26"); a
|
|
214
|
+
# version constraint ("keep SQLite >= 3.35.0 because RETURNING") is a durable rule.
|
|
215
|
+
_VERSION = re.compile(r"(?<![\w.])v?\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.]+)?(?![.\d])")
|
|
216
|
+
_RELEASE_STATUS = re.compile(
|
|
217
|
+
r"\b(?:released|shipped|published|tagged|cut|bumped|landed|merged|deployed|rolled out"
|
|
218
|
+
r"|upgraded to|is out|went out|now on)\b", re.I)
|
|
212
219
|
_NON_DURABLE = (
|
|
213
220
|
(re.compile(r"\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b", re.I),
|
|
214
221
|
"a run/session id (uuid)"),
|
|
215
222
|
# x.y.z, but not a dotted quad (127.0.0.1) or a longer dotted run.
|
|
216
|
-
(re.compile(r"(?<![\w.])v?\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.]+)?(?![.\d])"), "a version number"),
|
|
217
223
|
# PR #12 / issue #12 / run 38093054271 / core#880 / a bare #880 — not "run 3 retries".
|
|
218
224
|
(re.compile(r"\b(?:pr|pull request|issue|run|job)\s*(?:#\s*\d+|\d{5,})\b"
|
|
219
225
|
r"|\b[a-z][\w.-]*#\d+\b|(?<![\w&])#\d+\b", re.I), "a PR/issue/run id"),
|
|
@@ -235,7 +241,8 @@ def _is_sha(token):
|
|
|
235
241
|
def durability_rejects(content):
|
|
236
242
|
"""Why ``content`` is not a durable learning ([] = it may be stored).
|
|
237
243
|
|
|
238
|
-
Deterministic and offline: empty or too-short bodies, commit SHAs,
|
|
244
|
+
Deterministic and offline: empty or too-short bodies, commit SHAs (long, or
|
|
245
|
+
in commit context), versions next to release status,
|
|
239
246
|
PR/issue/run ids, PIDs, "today"-style dates, and first-person narration
|
|
240
247
|
("I'll retry", "going to guess again") are records of a moment; the repo,
|
|
241
248
|
CHANGELOG or tracker already holds those.
|
|
@@ -245,8 +252,11 @@ def durability_rejects(content):
|
|
|
245
252
|
return [f"too short to state a rule, why it holds and when it applies "
|
|
246
253
|
f"(< {MIN_CONTENT_CHARS} chars)"]
|
|
247
254
|
reasons = []
|
|
248
|
-
|
|
255
|
+
shas = [m.group(0) for m in _HEX_RUN.finditer(text.lower()) if _is_sha(m.group(0))]
|
|
256
|
+
if any(len(h) >= 12 for h in shas) or (shas and _COMMIT_CONTEXT.search(text)):
|
|
249
257
|
reasons.append("a commit SHA")
|
|
258
|
+
if _VERSION.search(text) and _RELEASE_STATUS.search(text):
|
|
259
|
+
reasons.append("release status (a version that was shipped/released)")
|
|
250
260
|
reasons += [why for rx, why in _NON_DURABLE if rx.search(text)]
|
|
251
261
|
return reasons
|
|
252
262
|
|
package/skills/mem/SKILL.md
CHANGED
|
@@ -85,7 +85,8 @@ A chat seat asked by wicked-crew to label the operator's decisions reads
|
|
|
85
85
|
1. Apply the durability test: *would this help a fresh session, on a
|
|
86
86
|
different task, three months from now?* Store a rule, why it holds, and
|
|
87
87
|
when it applies, in 1–3 self-contained sentences. Never store progress or
|
|
88
|
-
status, versions, SHAs, PR/run ids, PIDs,
|
|
88
|
+
status, release versions ("shipped 2.4.1"), commit SHAs, PR/run ids, PIDs,
|
|
89
|
+
"today", or what the repo,
|
|
89
90
|
CHANGELOG or tracker already records. The backend refuses the mechanical
|
|
90
91
|
cases with `not a durable learning`; treat that as "don't store", not as
|
|
91
92
|
an error to work around.
|
|
@@ -47,11 +47,14 @@ Reject, and do not rephrase into something that sneaks past:
|
|
|
47
47
|
|
|
48
48
|
- progress and status: what you did, are doing or will do ("I'll retry",
|
|
49
49
|
"going to try again", "shipped", "merged", "now green");
|
|
50
|
-
- versions, commit SHAs, PR / issue / run ids, PIDs,
|
|
50
|
+
- release versions ("shipped 2.4.1"), commit SHAs, PR / issue / run ids, PIDs,
|
|
51
|
+
ports picked by hand,
|
|
51
52
|
and anything dated ("today", "this morning");
|
|
52
53
|
- anything the repo, its CHANGELOG or the tracker already records.
|
|
53
54
|
|
|
54
|
-
|
|
55
|
+
A version *constraint* ("needs SQLite >= 3.35.0 because …") and a fixed constant
|
|
56
|
+
(a CRC polynomial, a colour) are durable; keep them.
|
|
57
|
+
The backend refuses the mechanical cases (ids, release versions, commit SHAs, PIDs, "today",
|
|
55
58
|
first-person narration, bodies under 40 characters) with
|
|
56
59
|
`not a durable learning`. A refusal means the candidate failed the test:
|
|
57
60
|
drop it, unless it holds a real rule you can state without the moment.
|
package/skills/qe/refs/setup.md
CHANGED
|
@@ -49,11 +49,19 @@ npx --no-install cypress --version > /dev/null 2>&1 && echo "npx-cypress: true"
|
|
|
49
49
|
The ledger root is `$WICKED_QE_LEDGER_DIR` when it is set (a governed crew run's own ledger,
|
|
50
50
|
outside the worktree), else `.wicked-qe`:
|
|
51
51
|
|
|
52
|
+
Create it with Python, not a shell `mkdir` of the variable: a governed run's write fence
|
|
53
|
+
judges a shell write target as written, and an unexpanded `"$WICKED_QE_LEDGER_DIR/..."` reads
|
|
54
|
+
as a path inside the worktree, so it is refused.
|
|
55
|
+
|
|
52
56
|
```bash
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
+
PY="$(command -v python3 || command -v python)"
|
|
58
|
+
"$PY" -c "
|
|
59
|
+
import os
|
|
60
|
+
root = os.environ.get('WICKED_QE_LEDGER_DIR') or '.wicked-qe'
|
|
61
|
+
for d in ('projects', 'strategies', 'scenarios', 'runs', 'verdicts', 'tasks', 'evidence'):
|
|
62
|
+
os.makedirs(os.path.join(root, d), exist_ok=True)
|
|
63
|
+
print(root)
|
|
64
|
+
"
|
|
57
65
|
```
|
|
58
66
|
|
|
59
67
|
### 3. Write config.json
|
|
@@ -138,9 +138,12 @@ when the proposal is approved, so the learning is lost):
|
|
|
138
138
|
- **Knowledge** — how the code works, read from the code (*acquired*, so it is
|
|
139
139
|
knowledge, not memory: DES-memory-vs-knowledge). `kind_type: "knowledge"`; payload
|
|
140
140
|
`{"content": "<1–3 sentences>", "class": "concept", "source": "<repo>:<path>",
|
|
141
|
-
"source_rev": "<commit sha>"}`. Most repo-learn findings are this kind.
|
|
142
|
-
|
|
143
|
-
|
|
141
|
+
"source_rev": "<commit sha>"}`. Most repo-learn findings are this kind. **Not
|
|
142
|
+
submittable yet:** estate cannot approve a knowledge proposal until its knowledge
|
|
143
|
+
proposal type ships, so the shim refuses `kind_type: "knowledge"`. List these objects
|
|
144
|
+
in your report's fenced `json` block under `knowledge (pending)` and leave them out
|
|
145
|
+
of the `wicked-capture-report` counts. Never propose a graph metric (PageRank rank,
|
|
146
|
+
churn count, blast-radius size): estate's tools re-derive those on demand.
|
|
144
147
|
- **Memory** — only something *witnessed* while studying (a gotcha you hit, with
|
|
145
148
|
why) or *told* by the operator. Split a mixed finding: "we chose fail-closed
|
|
146
149
|
because of incident X" is memory, "the implementation fails closed" is
|
|
@@ -71,7 +71,14 @@ estate/steering types — pick the one the rule governs, not a freeform tag.
|
|
|
71
71
|
## Knowledge payload (code facts: the usual case)
|
|
72
72
|
|
|
73
73
|
What you learn by reading code is **acquired**, so it is knowledge, not memory
|
|
74
|
-
(DES-memory-vs-knowledge §3).
|
|
74
|
+
(DES-memory-vs-knowledge §3).
|
|
75
|
+
|
|
76
|
+
**Not submittable yet.** Estate's `proposal.approve` promotes only `memory` and
|
|
77
|
+
`policy:*`; a knowledge proposal would sit unapprovable in the queue, so the shim
|
|
78
|
+
refuses `kind_type: "knowledge"` (`-32602`) until estate's knowledge proposal type
|
|
79
|
+
ships. Until then, put each object below in your report's fenced `json` block under
|
|
80
|
+
`knowledge (pending)`, and leave them out of the `wicked-capture-report` counts. Do
|
|
81
|
+
not re-file a code fact as a `memory` to get it through. The shape is:
|
|
75
82
|
|
|
76
83
|
```json
|
|
77
84
|
{
|
|
@@ -120,7 +127,8 @@ implementation fails closed" is knowledge.
|
|
|
120
127
|
`WICKED_RUN_PROJECT` supplies it.
|
|
121
128
|
- `content` must pass the durability test: a rule, why it holds, and when it
|
|
122
129
|
applies, useful to a fresh session on a different task months from now. No
|
|
123
|
-
versions, SHAs, PR/run ids or progress
|
|
130
|
+
release-status versions ("shipped 2.4.1"), commit SHAs, PR/run ids or progress
|
|
131
|
+
notes. A version *constraint* ("needs SQLite >= 3.35.0 because …") is fine.
|
|
124
132
|
|
|
125
133
|
## Policy payload + facets
|
|
126
134
|
|