@r1z4x/tezgah 1.2.0 → 1.3.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/CHANGELOG.md +12 -1
- package/bin/tezgah-taste +8 -3
- package/docs/clarity-baseline.json +1 -1
- package/docs/judge.md +10 -5
- package/docs/operations.md +8 -0
- package/hooks/tezgah_context.py +8 -0
- package/hooks/tezgah_taste.py +51 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,16 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [1.3.0] - 2026-10-07
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **Armed taste learns by itself.** Session start runs `tezgah-taste learn` in
|
|
14
|
+
the background when the repository's signals grew, at most once an hour. It
|
|
15
|
+
runs only when a TypeSafe key resolves, with the new `--no-fallback`, so it
|
|
16
|
+
asks no other provider. It does not run on Windows yet. Its log goes under
|
|
17
|
+
the cache dir, in `taste-learn/`.
|
|
18
|
+
|
|
9
19
|
## [1.2.0] - 2026-10-07
|
|
10
20
|
|
|
11
21
|
### Added
|
|
@@ -823,7 +833,8 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
823
833
|
the copy could not find `hooks/` and died on import. It now links into the
|
|
824
834
|
installed tree, and also installs the command as `tezgah`.
|
|
825
835
|
|
|
826
|
-
[Unreleased]: https://github.com/r1z4x/tezgah/compare/v1.
|
|
836
|
+
[Unreleased]: https://github.com/r1z4x/tezgah/compare/v1.3.0...HEAD
|
|
837
|
+
[1.3.0]: https://github.com/r1z4x/tezgah/releases/tag/v1.3.0
|
|
827
838
|
[1.2.0]: https://github.com/r1z4x/tezgah/releases/tag/v1.2.0
|
|
828
839
|
[1.1.0]: https://github.com/r1z4x/tezgah/releases/tag/v1.1.0
|
|
829
840
|
[1.0.0]: https://github.com/r1z4x/tezgah/releases/tag/v1.0.0
|
package/bin/tezgah-taste
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
tezgah-taste measure --samples FILE --labels FILE [--model M] [--json]
|
|
6
6
|
tezgah-taste rate --in MINED [--limit N] [--json]
|
|
7
7
|
tezgah-taste learn [--repo DIR] [--from-transcripts] [--host omp|claude|all]
|
|
8
|
-
[--limit N] [--json]
|
|
8
|
+
[--limit N] [--no-fallback] [--json]
|
|
9
9
|
tezgah-taste list [--repo DIR] [--all] [--json]
|
|
10
10
|
tezgah-taste show|accept|reject ID [--repo DIR]
|
|
11
11
|
tezgah-taste edit ID --text TEXT [--repo DIR]
|
|
@@ -701,7 +701,8 @@ def cmd_learn(args):
|
|
|
701
701
|
"preference": 0, "defect": 0, "none": 0}
|
|
702
702
|
tokens = [0, 0]
|
|
703
703
|
for sig in todo:
|
|
704
|
-
dec, result = decide(sig, held(ledgers),
|
|
704
|
+
dec, result = decide(sig, held(ledgers),
|
|
705
|
+
fallback=not args.no_fallback and sig["id"] not in decided)
|
|
705
706
|
if dec is None:
|
|
706
707
|
counts["unanswered"] += 1
|
|
707
708
|
continue
|
|
@@ -729,7 +730,9 @@ def cmd_learn(args):
|
|
|
729
730
|
for lid, learning in tl.every(ledgers).items():
|
|
730
731
|
if learning["state"] == "active" and lid not in before \
|
|
731
732
|
and not learning.get("written") and not learning.get("edited"):
|
|
732
|
-
|
|
733
|
+
# --no-fallback asks nothing of a generative provider: the user's
|
|
734
|
+
# own words stay the learning's line
|
|
735
|
+
line = None if args.no_fallback else write_line(learning)
|
|
733
736
|
if line:
|
|
734
737
|
learning.update(text=" ".join(line.split())[:400], written=True)
|
|
735
738
|
tl.save(repo, ledgers)
|
|
@@ -878,6 +881,8 @@ def main(argv):
|
|
|
878
881
|
learn.add_argument("--from-transcripts", action="store_true")
|
|
879
882
|
learn.add_argument("--host", choices=("omp", "claude", "all"), default="all")
|
|
880
883
|
learn.add_argument("--limit", type=int)
|
|
884
|
+
learn.add_argument("--no-fallback", action="store_true",
|
|
885
|
+
help="TypeSafe only: never pay another provider for a decision")
|
|
881
886
|
listing = sub.add_parser("list")
|
|
882
887
|
listing.add_argument("--all", action="store_true")
|
|
883
888
|
for name in ("show", "accept", "reject", "edit"):
|
package/docs/judge.md
CHANGED
|
@@ -59,10 +59,13 @@ certificates, destructive data or history operations, privilege and security.
|
|
|
59
59
|
The prompt-path skill hint sends the user's prompt through the same redactor. It
|
|
60
60
|
also cuts the prompt at 2,000 characters (`PROMPT_MAX`,
|
|
61
61
|
`hooks/tezgah_skill_pick.py::PROMPT_MAX`), because a prompt can quote a token and
|
|
62
|
-
nobody chose to send it.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
nobody chose to send it. Three of the callers are explicit - a person runs
|
|
63
|
+
the tool. `bin/tezgah-route` is what the ORCHESTRATE paragraph tells the router
|
|
64
|
+
to run before every delegation, which is why its redaction is not optional.
|
|
65
|
+
`bin/tezgah-taste learn` also starts by itself at session start once the user
|
|
66
|
+
armed `taste-on`. It then sends redacted signal text to TypeSafe only, and only
|
|
67
|
+
when a TypeSafe key resolves (`learn_later`, `hooks/tezgah_taste.py::learn_later`).
|
|
68
|
+
The switches below are the off buttons.
|
|
66
69
|
|
|
67
70
|
## The five callers
|
|
68
71
|
|
|
@@ -78,7 +81,9 @@ The third caller is the one no shell row can see: it runs on the prompt path,
|
|
|
78
81
|
caches one answer per `(session, prompt)` (`_remember`,
|
|
79
82
|
`hooks/tezgah_skill_pick.py::_remember`; `suggest`,
|
|
80
83
|
`hooks/tezgah_skill_pick.py::suggest`), and asks at all only when its own marker is
|
|
81
|
-
armed.
|
|
84
|
+
armed. `bin/tezgah-triage`, `bin/tezgah-docs` and `bin/tezgah-route` are bin
|
|
85
|
+
tools a session runs by name, and so is `bin/tezgah-taste`, except for the
|
|
86
|
+
background `learn` that armed taste starts.
|
|
82
87
|
|
|
83
88
|
## The providers and their order
|
|
84
89
|
|
package/docs/operations.md
CHANGED
|
@@ -442,6 +442,14 @@ contradicts, narrows or unrelated. When another provider answers, `learn` writes
|
|
|
442
442
|
the decision to `decisions.jsonl` as `unverified` and never applies it. A defect
|
|
443
443
|
goes to `defects.jsonl` and never into the ledger.
|
|
444
444
|
|
|
445
|
+
Armed taste also learns by itself. At session start, `learn` starts in the
|
|
446
|
+
background when `signals.jsonl` grew since the last start, at most once an hour
|
|
447
|
+
per repository (`learn_later`, `hooks/tezgah_taste.py::learn_later`). It runs
|
|
448
|
+
with `--no-fallback` and only when a TypeSafe key resolves, so it never pays a
|
|
449
|
+
generative provider for a decision it could not apply. It does not run on
|
|
450
|
+
Windows, which has no ledger lock yet. The stamp and the log live under the
|
|
451
|
+
cache dir, in `taste-learn/`.
|
|
452
|
+
|
|
445
453
|
The ledger is `<repo>/.tezgah/taste/ledger.json`, plus
|
|
446
454
|
`~/.config/tezgah/taste/ledger.json` for user-scope learnings
|
|
447
455
|
(`hooks/tezgah_taste_ledger.py`). Confidence is (r+1)/(r+s+2) over supporting
|
package/hooks/tezgah_context.py
CHANGED
|
@@ -2202,6 +2202,14 @@ def context_for(event, cwd, payload=None, with_core=True):
|
|
|
2202
2202
|
taste = ""
|
|
2203
2203
|
if taste:
|
|
2204
2204
|
parts.append(("taste", taste))
|
|
2205
|
+
# Armed taste learns by itself: a detached `learn` when the signals grew,
|
|
2206
|
+
# at most hourly (tezgah_taste.learn_later); session start only, with the
|
|
2207
|
+
# root `enabled` already resolved above.
|
|
2208
|
+
if taste_root and event == "session_start":
|
|
2209
|
+
try:
|
|
2210
|
+
tezgah_taste.learn_later(taste_root)
|
|
2211
|
+
except Exception:
|
|
2212
|
+
pass
|
|
2205
2213
|
broken = tezgah_research.failing(root) if not off("research-off") else []
|
|
2206
2214
|
if broken:
|
|
2207
2215
|
line_slug, err = broken[0]
|
package/hooks/tezgah_taste.py
CHANGED
|
@@ -15,15 +15,24 @@ PostToolUse hot paths, so the switch is read before anything else is.
|
|
|
15
15
|
|
|
16
16
|
Learning lives in `bin/tezgah-taste learn` and `tezgah_taste_ledger`; the one
|
|
17
17
|
thing fed back from here is `write_note`, the learnings in scope for a write.
|
|
18
|
+
`learn_later` starts that `learn` in the background at session start.
|
|
18
19
|
"""
|
|
19
20
|
import datetime
|
|
20
21
|
import json
|
|
21
22
|
import os
|
|
23
|
+
import re
|
|
24
|
+
import subprocess
|
|
25
|
+
import sys
|
|
26
|
+
import time
|
|
22
27
|
|
|
23
28
|
import tezgah_integrity as ti
|
|
29
|
+
import tezgah_paths
|
|
24
30
|
from tezgah_paths import _toplevel, armed, root_for, workspace_from_repo
|
|
25
31
|
|
|
26
32
|
ARM = "taste-on"
|
|
33
|
+
LEARN_EVERY = 3600 # seconds between two background `learn` starts per repo
|
|
34
|
+
BIN = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))),
|
|
35
|
+
"bin", "tezgah-taste")
|
|
27
36
|
MARK = ".no-taste"
|
|
28
37
|
VERSION = 1
|
|
29
38
|
PROMPT_MAX = 2000
|
|
@@ -203,6 +212,48 @@ def write_note(session_id, inp, cwd, tool=None):
|
|
|
203
212
|
return ""
|
|
204
213
|
|
|
205
214
|
|
|
215
|
+
def learn_later(root):
|
|
216
|
+
"""Start `tezgah-taste learn --repo <root> --no-fallback` detached when the
|
|
217
|
+
signals of `root` (the git root `enabled` returned) grew since the last
|
|
218
|
+
start and an hour has passed. Only when TypeSafe resolves: the automatic run
|
|
219
|
+
never pays a generative provider for a decision it may not apply. Not on a
|
|
220
|
+
machine without flock (Windows), where two background runs in two
|
|
221
|
+
repositories would race on the user ledger. ponytail: no Windows lock; add
|
|
222
|
+
one (msvcrt.locking) before enabling it there. Total; the stamp is written
|
|
223
|
+
before the spawn, so a failed start waits an hour."""
|
|
224
|
+
try:
|
|
225
|
+
if not root:
|
|
226
|
+
return
|
|
227
|
+
size = os.path.getsize(os.path.join(root, ".tezgah", "taste", "signals.jsonl"))
|
|
228
|
+
base = os.path.join(tezgah_paths.cache_dir(), "taste-learn")
|
|
229
|
+
name = re.sub(r"[^A-Za-z0-9]+", "-", root).strip("-")
|
|
230
|
+
stamp = os.path.join(base, name + ".json")
|
|
231
|
+
try:
|
|
232
|
+
with open(stamp, encoding="utf-8") as fh:
|
|
233
|
+
last = json.load(fh)
|
|
234
|
+
except (OSError, ValueError):
|
|
235
|
+
last = {}
|
|
236
|
+
now = time.time()
|
|
237
|
+
if size <= last.get("size", -1) or now - last.get("at", 0) < LEARN_EVERY:
|
|
238
|
+
return
|
|
239
|
+
# the costlier checks run only once a start is due: at most hourly
|
|
240
|
+
import tezgah_judge
|
|
241
|
+
import tezgah_taste_ledger
|
|
242
|
+
if tezgah_taste_ledger.fcntl is None or tezgah_paths.off("judge-off") \
|
|
243
|
+
or not tezgah_judge.named(("typesafe",)):
|
|
244
|
+
return
|
|
245
|
+
os.makedirs(base, exist_ok=True)
|
|
246
|
+
with open(stamp, "w", encoding="utf-8") as fh:
|
|
247
|
+
json.dump({"size": size, "at": now}, fh)
|
|
248
|
+
with open(os.path.join(base, name + ".log"), "ab") as log:
|
|
249
|
+
subprocess.Popen([sys.executable, BIN, "learn", "--repo", root,
|
|
250
|
+
"--no-fallback"],
|
|
251
|
+
cwd=root, stdin=subprocess.DEVNULL, stdout=log,
|
|
252
|
+
stderr=log, start_new_session=True)
|
|
253
|
+
except Exception:
|
|
254
|
+
pass
|
|
255
|
+
|
|
256
|
+
|
|
206
257
|
def main(argv):
|
|
207
258
|
"""`tezgah_taste.py '<json>'`: `note_write`, then `write_note`, for a host
|
|
208
259
|
that cannot call them in process (opencode's plugin is JavaScript). The one
|
|
@@ -223,5 +274,4 @@ def main(argv):
|
|
|
223
274
|
|
|
224
275
|
|
|
225
276
|
if __name__ == "__main__":
|
|
226
|
-
import sys
|
|
227
277
|
sys.exit(main(sys.argv[1:]))
|
package/package.json
CHANGED