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.
@@ -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.50.0"}], "version": "12.50.0"}
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.50.0",
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.50.0",
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 project_scope() -> str:
967
- """The current project's memory scope, ``project:<cwd-basename>``.
966
+ def checkout_root() -> Optional[Path]:
967
+ """The git checkout the cwd is in, resolved through linked worktrees.
968
968
 
969
- The one default for both directions: memories are stored here, and recall
970
- sees this scope plus its ancestors (root-scoped memories included). Reading
971
- every project's memories is an explicit opt-in (``scope_prefix: ""``).
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
- name = Path.cwd().name or "unknown"
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
- try:
139
- here = Path.cwd().resolve()
140
- except OSError:
138
+ root = _estate_client.checkout_root()
139
+ if root is None:
141
140
  return {}
142
- for d in (here, *here.parents):
143
- if (d / ".git").exists():
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, versions,
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
- if any(_is_sha(m.group(0)) for m in _HEX_RUN.finditer(text.lower())):
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
 
@@ -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, "today", or what the repo,
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, ports picked by hand,
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
- The backend refuses the mechanical cases (ids, versions, SHAs, PIDs, "today",
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.
@@ -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
- QE_ROOT="${WICKED_QE_LEDGER_DIR:-.wicked-qe}"
54
- mkdir -p "$QE_ROOT/projects" "$QE_ROOT/strategies" "$QE_ROOT/scenarios" \
55
- "$QE_ROOT/runs" "$QE_ROOT/verdicts" "$QE_ROOT/tasks" \
56
- "$QE_ROOT/evidence"
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. Never
142
- propose a graph metric (PageRank rank, churn count, blast-radius size): estate's
143
- tools re-derive those on demand.
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). Propose it as:
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 notes.
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