wicked-garden 12.51.0 → 12.52.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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +15 -0
- package/package.json +1 -1
- package/scripts/_context_backend.py +0 -3
- package/scripts/_estate_client.py +47 -6
- package/scripts/learn/index.py +69 -21
- 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/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.52.0"}], "version": "12.52.0"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wicked-garden",
|
|
3
|
-
"version": "12.
|
|
3
|
+
"version": "12.52.0",
|
|
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,21 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [12.52.0] — 2026-10-11
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
- **Learn indexing reads what merged and replaces a topic by its source (PR #1280).**
|
|
9
|
+
- `scripts/learn/index.py --ref <ref>` reads `.wicked/knowledge/` at a git ref, never the working copy. wicked-crew's learn schedule passes `origin/<default>`.
|
|
10
|
+
- When estate advertises `knowledge.forget` (estate K1+K2), each topic becomes one `knowledge.ingest {replace_source: true}` carrying `observed_at` and `source_rev`, and a topic with nothing left is forgotten by its source.
|
|
11
|
+
- An older estate keeps the per-revision scope fallback.
|
|
12
|
+
|
|
13
|
+
## [12.51.1] — 2026-10-11
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
- **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.
|
|
17
|
+
- **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.
|
|
18
|
+
- **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.
|
|
19
|
+
|
|
5
20
|
## [12.51.0] — 2026-10-11
|
|
6
21
|
|
|
7
22
|
### Fixed
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wicked-garden",
|
|
3
|
-
"version": "12.
|
|
3
|
+
"version": "12.52.0",
|
|
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():
|
package/scripts/learn/index.py
CHANGED
|
@@ -4,14 +4,16 @@
|
|
|
4
4
|
wicked-garden run scripts/learn/index.py --repo <checkout> [--name <repo name>] [--slug <slug>] [--dry-run]
|
|
5
5
|
|
|
6
6
|
Reads ``.wicked/knowledge/<slug>/`` from the checkout (run it on the default branch: index what was
|
|
7
|
-
MERGED, never an open pull request) and, per topic, REPLACES
|
|
8
|
-
``knowledge.
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
MERGED, never an open pull request) and, per topic, REPLACES its knowledge in estate: one
|
|
8
|
+
``knowledge.ingest {replace_source: true}`` (source ``repo:<repo>/.wicked/knowledge/<slug>``, scope
|
|
9
|
+
``learn:<repo>/topic:<slug>``, ``observed_at`` = the topic's refreshed_at, ``source_rev`` = its tree hash)
|
|
10
|
+
whose chunks are the topic's ACTIVE claims (each chunk ends with its sources and observed dates) and the
|
|
11
|
+
README's sections. A retired, superseded or contradicted claim therefore leaves recall at the next
|
|
12
|
+
index; a topic with nothing left is forgotten by its source (``knowledge.forget``).
|
|
11
13
|
|
|
12
|
-
When
|
|
13
|
-
scope ``learn:<repo>/topic:<slug>/rev:<tree hash>`` instead and the output says
|
|
14
|
-
(an older revision stays recallable until estate can
|
|
14
|
+
When estate does not advertise ``knowledge.forget`` (before estate K1+K2), the topic is ingested under a
|
|
15
|
+
per-revision scope ``learn:<repo>/topic:<slug>/rev:<tree hash>`` instead and the output says
|
|
16
|
+
``"replace": "unavailable"`` (an older revision stays recallable until estate can replace it).
|
|
15
17
|
|
|
16
18
|
Never from inside a governed run: a run's estate is read-only, and learned knowledge reaches the
|
|
17
19
|
store only after its pull request merged. Prints one JSON object ``{ok, repo, topics: [{slug, scope,
|
|
@@ -77,40 +79,72 @@ def topic_payload(repo: Path, name: str, d: Path) -> dict:
|
|
|
77
79
|
"source": f"repo:{name}/.wicked/knowledge/{t.slug}",
|
|
78
80
|
"chunks": chunks,
|
|
79
81
|
"tree_hash": tree_hash(d),
|
|
82
|
+
# when the topic was last researched (topic.json refreshed_at, else created_at)
|
|
83
|
+
"observed_at": (t.meta.get("refreshed_at") or t.meta.get("created_at")) if isinstance(t.meta, dict) else None,
|
|
80
84
|
"read_errors": t.errors,
|
|
81
85
|
}
|
|
82
86
|
|
|
83
87
|
|
|
84
|
-
def index_topic(client, p: dict) -> dict:
|
|
88
|
+
def index_topic(client, p: dict, tools: list | None = None) -> dict:
|
|
89
|
+
"""Replace one topic in estate. With ``knowledge.forget`` advertised (estate K1+K2), the topic's
|
|
90
|
+
source is replaced in place (``knowledge.ingest {replace_source: true}``, carrying ``observed_at``
|
|
91
|
+
and ``source_rev``), and a topic with nothing left to index is forgotten by its source. Without
|
|
92
|
+
it, the topic is ingested under a per-revision scope and the output says so."""
|
|
85
93
|
out = {"slug": p["slug"], "scope": p["scope"], "chunks": len(p["chunks"]), "tree_hash": p["tree_hash"]}
|
|
86
94
|
if p["read_errors"]:
|
|
87
95
|
out["read_errors"] = p["read_errors"]
|
|
88
|
-
if p["read_errors"]:
|
|
89
96
|
out["ingested"] = False
|
|
90
|
-
out["reason"] = "the topic could not be fully read; its indexed
|
|
97
|
+
out["reason"] = "the topic could not be fully read; its indexed knowledge is left as it was"
|
|
91
98
|
return out
|
|
92
|
-
|
|
93
|
-
if erased is None:
|
|
94
|
-
out["replace"] = "unavailable"
|
|
95
|
-
out["scope"] = f"{p['scope']}/rev:{p['tree_hash']}"
|
|
96
|
-
else:
|
|
97
|
-
out["replace"] = "erased"
|
|
98
|
-
out["erased"] = erased
|
|
99
|
+
replace = "knowledge.forget" in (tools if tools is not None else client.list_tools())
|
|
99
100
|
if not p["chunks"]:
|
|
100
101
|
out["ingested"] = False
|
|
101
102
|
out["reason"] = "no active claims and no README to index"
|
|
103
|
+
if replace:
|
|
104
|
+
forgotten = client.call("knowledge.forget", {"source": p["source"]}, timeout=30.0)
|
|
105
|
+
out["replace"] = "forgotten" if forgotten is not None else "forget-failed"
|
|
106
|
+
out["forgotten"] = forgotten
|
|
107
|
+
out["ok"] = forgotten is not None
|
|
102
108
|
return out
|
|
103
|
-
|
|
104
|
-
|
|
109
|
+
args = {"title": p["title"], "chunks": p["chunks"], "source": p["source"]}
|
|
110
|
+
if replace:
|
|
111
|
+
out["replace"] = "replaced"
|
|
112
|
+
args.update(scope=p["scope"], replace_source=True, source_rev=p["tree_hash"])
|
|
113
|
+
if p.get("observed_at"):
|
|
114
|
+
args["observed_at"] = p["observed_at"]
|
|
115
|
+
else:
|
|
116
|
+
out["replace"] = "unavailable"
|
|
117
|
+
out["scope"] = f"{p['scope']}/rev:{p['tree_hash']}"
|
|
118
|
+
args["scope"] = out["scope"]
|
|
119
|
+
res = client.call("knowledge.ingest", args, timeout=60.0)
|
|
105
120
|
out["ingested"] = res is not None
|
|
106
121
|
if res is None:
|
|
107
122
|
out["reason"] = "estate knowledge.ingest failed or estate is unreachable"
|
|
108
123
|
return out
|
|
109
124
|
|
|
110
125
|
|
|
126
|
+
def materialize(repo: Path, ref: str, dest: Path) -> None:
|
|
127
|
+
"""Copy ``.wicked/knowledge/`` as it is at git ``ref`` (e.g. ``origin/main``: what MERGED) into
|
|
128
|
+
``dest``, so the index reads the merged tree, never the checkout's working copy."""
|
|
129
|
+
listing = subprocess.run(["git", "-C", str(repo), "ls-tree", "-r", "-z", "--name-only", ref, "--", ".wicked/knowledge"],
|
|
130
|
+
capture_output=True, timeout=30)
|
|
131
|
+
if listing.returncode != 0:
|
|
132
|
+
raise RuntimeError(f"git ls-tree {ref} failed: {listing.stderr.decode('utf-8', 'replace').strip()[:200]}")
|
|
133
|
+
for rel in filter(None, listing.stdout.decode("utf-8", "replace").split("\0")):
|
|
134
|
+
blob = subprocess.run(["git", "-C", str(repo), "show", f"{ref}:{rel}"], capture_output=True, timeout=30)
|
|
135
|
+
if blob.returncode != 0:
|
|
136
|
+
raise RuntimeError(f"git show {ref}:{rel} failed")
|
|
137
|
+
target = dest / rel
|
|
138
|
+
if not target.resolve().is_relative_to(dest.resolve()):
|
|
139
|
+
raise RuntimeError(f"refusing a path outside the index root: {rel}")
|
|
140
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
141
|
+
target.write_bytes(blob.stdout)
|
|
142
|
+
|
|
143
|
+
|
|
111
144
|
def main(argv: list[str] | None = None) -> int:
|
|
112
145
|
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
|
113
146
|
parser.add_argument("--repo", type=Path, default=Path("."))
|
|
147
|
+
parser.add_argument("--ref", help="read the knowledge at this git ref (e.g. origin/main) instead of the working tree")
|
|
114
148
|
parser.add_argument("--name", help="the repository name in the scope (default: origin's name, else the directory)")
|
|
115
149
|
parser.add_argument("--slug")
|
|
116
150
|
parser.add_argument("--dry-run", action="store_true", help="print what would be indexed; touch no store")
|
|
@@ -123,13 +157,27 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
123
157
|
"learned knowledge is indexed from the merged default branch (DES-learn-workflow §4.2)"}))
|
|
124
158
|
return 2
|
|
125
159
|
name = args.name or repo_name(repo)
|
|
160
|
+
if args.ref:
|
|
161
|
+
import tempfile
|
|
162
|
+
with tempfile.TemporaryDirectory(prefix="learn-index-") as tmp:
|
|
163
|
+
try:
|
|
164
|
+
materialize(repo, args.ref, Path(tmp))
|
|
165
|
+
except (RuntimeError, OSError, subprocess.SubprocessError) as e:
|
|
166
|
+
print(json.dumps({"ok": False, "repo": name, "reason": str(e)}))
|
|
167
|
+
return 1
|
|
168
|
+
return _run(client, Path(tmp), name, args)
|
|
169
|
+
return _run(client, repo, name, args)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _run(client, repo: Path, name: str, args) -> int:
|
|
126
173
|
payloads = [topic_payload(repo, name, d) for d in topic_dirs(repo, args.slug)]
|
|
127
174
|
if args.dry_run:
|
|
128
175
|
print(json.dumps({"ok": True, "repo": name, "dry_run": True,
|
|
129
176
|
"topics": [{k: v for k, v in p.items() if k != "chunks"} | {"chunks": len(p["chunks"])} for p in payloads]}, indent=2))
|
|
130
177
|
return 0
|
|
131
|
-
|
|
132
|
-
|
|
178
|
+
tools = client.list_tools()
|
|
179
|
+
results = [index_topic(client, p, tools) for p in payloads]
|
|
180
|
+
ok = all(r.get("ingested") or (not r["chunks"] and "read_errors" not in r and r.get("ok", True)) for r in results)
|
|
133
181
|
print(json.dumps({"ok": ok, "repo": name, "topics": results}, indent=2))
|
|
134
182
|
return 0 if ok else 1
|
|
135
183
|
|
|
@@ -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.
|
|
@@ -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
|
|