@r1z4x/tezgah 1.6.0 → 1.7.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 (64) hide show
  1. package/CHANGELOG.md +80 -1
  2. package/MANIFEST +5 -0
  3. package/README.md +6 -3
  4. package/bin/tezgah-docs +18 -16
  5. package/bin/tezgah-doctor +152 -0
  6. package/bin/tezgah-research +234 -10
  7. package/bin/tezgah-route +48 -1
  8. package/bin/tezgah-setup +26 -13
  9. package/bin/tezgah-task +589 -17
  10. package/bin/tezgah-taste +48 -10
  11. package/docs/architecture.md +7 -4
  12. package/docs/clarity-baseline.json +2 -2
  13. package/docs/contract.md +108 -24
  14. package/docs/evidence.md +201 -28
  15. package/docs/feature-audit.md +1 -1
  16. package/docs/gate.md +60 -22
  17. package/docs/glossary.md +4 -4
  18. package/docs/hosts.md +8 -8
  19. package/docs/index.json +11 -5
  20. package/docs/judge.md +9 -3
  21. package/docs/models.md +35 -2
  22. package/docs/operations.md +103 -3
  23. package/docs/research.md +346 -13
  24. package/docs/skills.md +1 -1
  25. package/docs/status-line.md +5 -5
  26. package/docs/testing.md +2 -2
  27. package/docs/vision.md +12 -4
  28. package/hooks/hooks.json +2 -2
  29. package/hooks/lang/en.json +323 -0
  30. package/hooks/lang/tr.json +344 -0
  31. package/hooks/projects-posttooluse.py +24 -5
  32. package/hooks/projects-pretooluse.py +9 -4
  33. package/hooks/projects-stop.py +28 -16
  34. package/hooks/tezgah_agents.py +29 -10
  35. package/hooks/tezgah_clarity.py +7 -3
  36. package/hooks/tezgah_context.py +425 -406
  37. package/hooks/tezgah_gate.py +583 -33
  38. package/hooks/tezgah_integrity.py +1480 -197
  39. package/hooks/tezgah_models.py +337 -15
  40. package/hooks/tezgah_paths.py +103 -10
  41. package/hooks/tezgah_policy.py +258 -126
  42. package/hooks/tezgah_rank.py +29 -27
  43. package/hooks/tezgah_replay.py +200 -30
  44. package/hooks/tezgah_research.py +1294 -9
  45. package/hooks/tezgah_stopspec.py +7 -0
  46. package/hooks/tezgah_store.py +74 -27
  47. package/hooks/tezgah_task.py +915 -6
  48. package/hosts/codex/hook.py +9 -10
  49. package/hosts/cursor/hook.py +21 -14
  50. package/hosts/dsh/hooks.json +2 -2
  51. package/hosts/omp/hook.py +44 -23
  52. package/hosts/omp/tezgah-hook.ts.in +93 -7
  53. package/hosts/opencode/plugins/tezgah.js +102 -2
  54. package/output-styles/tezgah.md +76 -42
  55. package/package.json +1 -1
  56. package/skills/i-have-adhd/EVALS.md +22 -2
  57. package/skills/i-have-adhd/SKILL.md +12 -5
  58. package/skills/plan-add/SKILL.md +5 -3
  59. package/skills/plan-sync/SKILL.md +5 -3
  60. package/skills/ponytail/EVALS.md +21 -2
  61. package/skills/ponytail/SKILL.md +4 -4
  62. package/skills/research/SKILL.md +89 -1
  63. package/skills/tezgah-contract/EVALS.md +52 -7
  64. package/skills/tezgah-contract/SKILL.md +151 -56
@@ -102,6 +102,13 @@ NOT_COUNTED = (
102
102
  "constants over one trace, read off the reply, not a row"),
103
103
  ("evidence tampered", "a trace-validity guard in the selector, before the fold"),
104
104
  )
105
+ # Plan 080 (2026-10-11), after the pre-registration and not a change to it: the
106
+ # admission ("doğrulanmadı") is applied by the selector to whichever fold it is
107
+ # given (`tezgah_integrity._admitted`: a check-repair class cleared after a check
108
+ # that failed since the newest change), and `residual work` is a selector verdict
109
+ # asked at every exit that would end the turn (`_residual_block`). Both reach the
110
+ # spec's verdict through the shared selector, so neither is a formula here.
111
+
105
112
  # GO 2's live floor: shadow rows needed before the 99% bar is read. At 400 a
106
113
  # 99% rate is 4 disagreements, so one more or less moves it by 0.25 pp.
107
114
  MIN_LIVE_N = 400
@@ -269,17 +269,58 @@ def _taste_put(table):
269
269
  return put
270
270
 
271
271
 
272
+ def _recorded(conn, name, st):
273
+ """(the bytes `imported` counts for the file `name` with stat `st`, the
274
+ device id of the row they were found under when it is not the current one)
275
+ or None. A volume remount can renumber the device of a file whose name and
276
+ inode are unchanged, and a store from before the fix imported such a file
277
+ again from offset 0 under the new id, so every row for (name, inode) is one
278
+ import stream. The longest is the file's offset under any id - this id's
279
+ own row included, when the id comes back to a shorter stream - so no byte
280
+ goes in twice."""
281
+ found = conn.execute("SELECT bytes, dev FROM imported WHERE name = ? AND ino = ? "
282
+ "ORDER BY bytes DESC, dev = ? DESC LIMIT 1",
283
+ (name, st.st_ino, st.st_dev)).fetchone()
284
+ if found is None:
285
+ return None
286
+ return found[0], None if found[1] == st.st_dev else found[1]
287
+
288
+
289
+ def _record(conn, key, start, end, stale):
290
+ """The file `key` in up to byte `end`, its import having resumed at `start`.
291
+ An import resumed from a row under another device id (`stale`) moves that
292
+ row to the current key, and a shorter stream's row already under the
293
+ current key takes the stale id: the two swap, so one row per import stream
294
+ stays, which is what the doctor's repair counts a double import by
295
+ (`tezgah-doctor --repair-evidence`). A file cut below what went in (a row
296
+ past `start`) no longer holds what its imports read: it goes on as one
297
+ stream, which resumes at its end and which the repair leaves alone."""
298
+ name, dev, ino = key
299
+ if conn.execute("SELECT 1 FROM imported WHERE name = ? AND ino = ? AND bytes > ?",
300
+ (name, ino, start)).fetchone():
301
+ conn.execute("DELETE FROM imported WHERE name = ? AND ino = ?", (name, ino))
302
+ elif stale is not None:
303
+ conn.execute("DELETE FROM imported WHERE name = ? AND dev = ? AND ino = ?",
304
+ (name, stale, ino))
305
+ conn.execute("UPDATE imported SET dev = ? WHERE name = ? AND dev = ? AND ino = ?",
306
+ (stale, name, dev, ino))
307
+ conn.execute("INSERT OR REPLACE INTO imported (name, dev, ino, bytes) "
308
+ "VALUES (?, ?, ?, ?)", key + (end,))
309
+
310
+
272
311
  def _offset(conn, name, fh):
273
- """(the file's key, the byte offset its import resumes at, whether that
274
- differs from the recorded one). A file cut below what went in (rewritten in
275
- place) resumes at its end, so the rows appended after the cut still
276
- import."""
312
+ """(the file's key, the byte offset its import resumes at, whether the
313
+ recorded row must be rewritten, the device id of the row it resumes from
314
+ when that is not the current one (`_recorded`)). The row is rewritten when
315
+ its offset differs or it is under another id: a file cut below what went
316
+ in (rewritten in place) resumes at its end, so the rows appended after the
317
+ cut still import."""
277
318
  st = os.fstat(fh.fileno())
278
319
  key = (name, st.st_dev, st.st_ino)
279
- found = conn.execute("SELECT bytes FROM imported WHERE name = ? AND dev = ? "
280
- "AND ino = ?", key).fetchone()
320
+ found = _recorded(conn, name, st)
281
321
  start = min(found[0], st.st_size) if found else 0
282
- return key, start, bool(found and found[0] != start)
322
+ stale = found[1] if found else None
323
+ return key, start, bool(found and found[0] != start) or stale is not None, stale
283
324
 
284
325
 
285
326
  def _import_rows(conn, name, src, st, put=None, keep=False):
@@ -303,14 +344,14 @@ def _import_rows(conn, name, src, st, put=None, keep=False):
303
344
  _failed(conn, name, st)
304
345
  return
305
346
  with fh: # closing it releases the flock
306
- key, start, cut = _offset(conn, name, fh)
347
+ key, start, cut, _stale = _offset(conn, name, fh)
307
348
  fh.seek(start)
308
349
  raw = fh.read()
309
350
  if b"\n" in raw or cut:
310
351
  with transaction(conn):
311
352
  if not _locked(fh):
312
353
  return # its writer holds it: the next open takes it
313
- key, start, cut = _offset(conn, name, fh)
354
+ key, start, cut, stale = _offset(conn, name, fh)
314
355
  fh.seek(start)
315
356
  raw = fh.read()
316
357
  whole = raw[:raw.rfind(b"\n") + 1]
@@ -318,8 +359,7 @@ def _import_rows(conn, name, src, st, put=None, keep=False):
318
359
  put = put or _taste_put(name[:-len(".jsonl")])
319
360
  for line in whole.split(b"\n"):
320
361
  put(conn, line)
321
- conn.execute("INSERT OR REPLACE INTO imported (name, dev, ino, bytes) "
322
- "VALUES (?, ?, ?, ?)", key + (start + len(whole),))
362
+ _record(conn, key, start, start + len(whole), stale)
323
363
  # aside only when every byte went in (no fragment past the last newline)
324
364
  # and no writer appended past the read (the old writer appends unlocked
325
365
  # once its own wait runs out), under the flock so no writer is inside
@@ -523,27 +563,36 @@ def _insert(conn, session, text):
523
563
  ts if ts is not None and -2 ** 63 <= ts < 2 ** 63 else None, text))
524
564
 
525
565
 
566
+ def _line_text(line):
567
+ """The row text one legacy ledger line (no newline) imports as, None for a
568
+ blank one: kept as it was written, so a line that does not parse stays a
569
+ line the reader names as damage."""
570
+ if not line.strip():
571
+ return None
572
+ try:
573
+ return line.decode("utf-8")
574
+ except UnicodeDecodeError:
575
+ # the marker `tezgah_integrity._parse` names as damage, hashed over the
576
+ # line with its newline as the file reader hashed it, so a damage row
577
+ # it wrote still names the same line
578
+ return "\x00not utf-8 %s" % hashlib.sha1(line + b"\n").hexdigest()[:12]
579
+
580
+
526
581
  def _evidence_put(session):
527
- """The import of one legacy ledger line: kept as it was written, so a line
528
- that does not parse stays a line the reader names as damage."""
582
+ """The import of one legacy ledger line (`_line_text`)."""
529
583
  def put(conn, line):
530
- if not line.strip():
531
- return
532
- try:
533
- text = line.decode("utf-8")
534
- except UnicodeDecodeError:
535
- # the marker `tezgah_integrity._parse` names as damage, hashed
536
- # over the line with its newline as the file reader hashed it, so
537
- # a damage row it wrote still names the same line
538
- text = "\x00not utf-8 %s" % hashlib.sha1(line + b"\n").hexdigest()[:12]
539
- _insert(conn, session, text)
584
+ text = _line_text(line)
585
+ if text is not None:
586
+ _insert(conn, session, text)
540
587
  return put
541
588
 
542
589
 
543
590
  def _sync(held, path):
544
591
  """Import the legacy JSONL at `path` past the bytes already in. Without a
545
592
  file, or with nothing new in it, this is one stat (and one lookup the first
546
- time a process sees it)."""
593
+ time a process sees it). A file whose offset sits under another device id
594
+ (`_recorded`) goes through the import even with nothing new, whose write
595
+ re-keys it."""
547
596
  try:
548
597
  st = os.stat(path)
549
598
  except OSError:
@@ -553,9 +602,7 @@ def _sync(held, path):
553
602
  if seen.get(path) == mark:
554
603
  return
555
604
  name = os.path.basename(path)
556
- found = conn.execute("SELECT bytes FROM imported WHERE name = ? AND dev = ? AND ino = ?",
557
- (name, st.st_dev, st.st_ino)).fetchone()
558
- if found and found[0] == st.st_size:
605
+ if _recorded(conn, name, st) == (st.st_size, None):
559
606
  seen[path] = mark
560
607
  return
561
608
  if conn.execute("SELECT 1 FROM import_failed WHERE name = ? AND mtime_ns = ? "