@techierathore/techieflow 1.0.4 → 1.1.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/commands/TechieFlow/agents/flow-master.md +2 -0
- package/.claude/commands/TechieFlow/agents/verifier.md +2 -2
- package/.claude/commands/TechieFlow/tasks/_develop-mode.md +34 -0
- package/.claude/commands/TechieFlow/tasks/_owner-language.md +1 -0
- package/.claude/commands/TechieFlow/tasks/_status-update-gate.md +4 -3
- package/.claude/commands/TechieFlow/tasks/_yolo-mode.md +2 -2
- package/.claude/commands/TechieFlow/tasks/amend-docs.md +2 -2
- package/.claude/commands/TechieFlow/tasks/day1-brownfield.md +3 -3
- package/.claude/commands/TechieFlow/tasks/day1-greenfield.md +5 -5
- package/.claude/commands/TechieFlow/tasks/develop-end-to-end.md +30 -0
- package/.claude/commands/TechieFlow/tasks/fix-issues.md +3 -2
- package/.claude/commands/TechieFlow/tasks/generate-html.md +2 -2
- package/.claude/commands/TechieFlow/tasks/handoff-phase.md +2 -2
- package/.claude/commands/TechieFlow/tasks/mockups.md +2 -2
- package/.claude/commands/TechieFlow/tasks/productguide.md +2 -2
- package/.claude/commands/TechieFlow/tasks/render-workflow-docs.md +1 -1
- package/.claude/commands/TechieFlow/tasks/triage-and-fix.md +2 -2
- package/.claude/commands/TechieFlow/tasks/triage-issues.md +2 -2
- package/.claude/commands/TechieFlow/tasks/verify-phase.md +12 -11
- package/.opencode/plugin/techieflow.js +194 -14
- package/.tfcore/agents/flow-master.md +2 -0
- package/.tfcore/agents/verifier.md +2 -2
- package/.tfcore/core-config.yaml +2 -1
- package/.tfcore/hooks/block-git.sh +4 -2
- package/.tfcore/hooks/guard-build.sh +5 -1
- package/.tfcore/hooks/guard-db.sh +112 -13
- package/.tfcore/hooks/guard-status-html.sh +86 -2
- package/.tfcore/hooks/guard-verify-deps.sh +16 -1
- package/.tfcore/hooks/guard-verify.sh +8 -3
- package/.tfcore/hooks/sweep-artifacts.sh +46 -0
- package/.tfcore/hooks/yolo-allow.sh +27 -0
- package/.tfcore/standards/blazor-zoom.cs.txt +191 -0
- package/.tfcore/standards/coding-standards-dotnet.md +31 -3
- package/.tfcore/standards/owner-words.txt +23 -0
- package/.tfcore/tasks/_develop-mode.md +34 -0
- package/.tfcore/tasks/_owner-language.md +1 -0
- package/.tfcore/tasks/_status-update-gate.md +4 -3
- package/.tfcore/tasks/_yolo-mode.md +2 -2
- package/.tfcore/tasks/amend-docs.md +2 -2
- package/.tfcore/tasks/day1-brownfield.md +3 -3
- package/.tfcore/tasks/day1-greenfield.md +5 -5
- package/.tfcore/tasks/develop-end-to-end.md +30 -0
- package/.tfcore/tasks/fix-issues.md +3 -2
- package/.tfcore/tasks/generate-html.md +2 -2
- package/.tfcore/tasks/handoff-phase.md +2 -2
- package/.tfcore/tasks/mockups.md +2 -2
- package/.tfcore/tasks/productguide.md +2 -2
- package/.tfcore/tasks/render-workflow-docs.md +1 -1
- package/.tfcore/tasks/triage-and-fix.md +2 -2
- package/.tfcore/tasks/triage-issues.md +2 -2
- package/.tfcore/tasks/verify-phase.md +12 -11
- package/.tfcore/telemetry/SCHEMA.md +10 -2
- package/.tfcore/telemetry/install-metrics.sh +3 -2
- package/.tfcore/telemetry/tf-metrics.sh +38 -2
- package/.tfcore/templates/stack-defaults/dotnet.md +3 -1
- package/.tfcore/templates/v4custom/app-agents-md-tmpl.md +6 -0
- package/.tfcore/templates/v4custom/app-architecture-tmpl.md +5 -0
- package/.tfcore/templates/v4custom/app-brd-tmpl.md +7 -4
- package/.tfcore/templates/v4custom/app-checklist-tmpl.md +11 -0
- package/.tfcore/templates/v4custom/app-phase-brd-tmpl.md +94 -0
- package/.tfcore/templates/v4custom/app-phase-uidesign-tmpl.md +60 -0
- package/.tfcore/templates/v4custom/app-phases-tmpl.md +2 -1
- package/.tfcore/templates/v4custom/app-uidesign-tmpl.md +4 -2
- package/.tfcore/templates/v4custom/build-subagent-prompt.md +2 -2
- package/.tfcore/templates/v4custom/html-render-shell.md +6 -2
- package/.tfcore/utils/__pycache__/tf-devguide-list.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-doc-check.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-split-brd.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-status-facts.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-verify-list.cpython-312.pyc +0 -0
- package/.tfcore/utils/tf-appium.mjs +179 -0
- package/.tfcore/utils/tf-assets-browser.mjs +96 -0
- package/.tfcore/utils/tf-assets.sh +97 -12
- package/.tfcore/utils/tf-brd-status.py +14 -5
- package/.tfcore/utils/tf-build-list.py +95 -12
- package/.tfcore/utils/tf-build-list.sh +2 -1
- package/.tfcore/utils/tf-build.sh +163 -15
- package/.tfcore/utils/tf-checklist-edit.py +21 -3
- package/.tfcore/utils/tf-ci-repro.py +685 -0
- package/.tfcore/utils/tf-ci-repro.sh +24 -0
- package/.tfcore/utils/tf-day1-files.py +55 -28
- package/.tfcore/utils/tf-develop.sh +264 -0
- package/.tfcore/utils/tf-devguide-list.py +53 -24
- package/.tfcore/utils/tf-doc-check.py +218 -26
- package/.tfcore/utils/tf-doc-tests.sh +116 -0
- package/.tfcore/utils/tf-emit.sh +132 -45
- package/.tfcore/utils/tf-feedback.sh +13 -0
- package/.tfcore/utils/tf-fix-close.sh +87 -31
- package/.tfcore/utils/tf-goal.sh +12 -12
- package/.tfcore/utils/tf-harness.sh +9 -4
- package/.tfcore/utils/tf-lock.sh +29 -0
- package/.tfcore/utils/tf-log-miss.py +34 -8
- package/.tfcore/utils/tf-login.mjs +167 -0
- package/.tfcore/utils/tf-maccatalyst-check.sh +92 -0
- package/.tfcore/utils/tf-menus.mjs +401 -0
- package/.tfcore/utils/tf-misses-md.py +10 -4
- package/.tfcore/utils/tf-mockup-parity.mjs +763 -71
- package/.tfcore/utils/tf-mockup-parity.sh +25 -2
- package/.tfcore/utils/tf-owner-text.py +233 -0
- package/.tfcore/utils/tf-owner-text.sh +14 -0
- package/.tfcore/utils/tf-phase.sh +38 -4
- package/.tfcore/utils/tf-portable.sh +230 -0
- package/.tfcore/utils/tf-render-html.py +79 -5
- package/.tfcore/utils/tf-req-ids.py +36 -0
- package/.tfcore/utils/tf-selfcheck.sh +39 -6
- package/.tfcore/utils/tf-split-brd.py +11 -1
- package/.tfcore/utils/tf-stack-check.sh +3 -0
- package/.tfcore/utils/tf-status-evidence.sh +11 -9
- package/.tfcore/utils/tf-status-facts.py +160 -45
- package/.tfcore/utils/tf-theme.mjs +189 -0
- package/.tfcore/utils/tf-triage.py +74 -17
- package/.tfcore/utils/tf-triage.sh +2 -0
- package/.tfcore/utils/tf-verify-boot.sh +529 -54
- package/.tfcore/utils/tf-verify-emit.sh +57 -17
- package/.tfcore/utils/tf-verify-env.sh +33 -4
- package/.tfcore/utils/tf-verify-list.py +290 -24
- package/.tfcore/utils/tf-verify-list.sh +3 -1
- package/.tfcore/utils/tf-verify-native.mjs +307 -0
- package/.tfcore/utils/tf-verify-screens.mjs +341 -44
- package/.tfcore/utils/tf-verify-screens.sh +23 -5
- package/.tfcore/utils/tf-verify-tests.sh +310 -19
- package/.tfcore/utils/tf-verify-verdict.py +163 -21
- package/.tfcore/utils/tf-verify-zoom.mjs +193 -0
- package/.tfcore/utils/tf-verify-zoom.sh +29 -0
- package/.tfcore/utils/tf-yolo-alias.sh +84 -0
- package/.tfcore/utils/tf-yolo-perms.py +135 -0
- package/.tfcore/utils/tf-yolo-start.sh +44 -0
- package/.tfcore/utils/tf-yolo.sh +31 -3
- package/.tfcore/utils/tf_defect.py +27 -0
- package/.tfcore/utils/tf_feedback.py +272 -0
- package/.tfcore/utils/tf_roadmap.py +35 -0
- package/.tfcore/utils/tf_stack_check.py +93 -0
- package/README.md +6 -2
- package/docs/TechieFlow-Installation.md +5 -3
- package/opencode.jsonc +6 -2
- package/package.json +1 -1
- package/scripts/install.mjs +13 -2
- package/.tfcore/utils/__pycache__/tf-brd-status.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-codex-bind.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-codex-telemetry.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-day1-files.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-log-miss.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-misses-md.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-mockups-locate.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-opencode-audit.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-render-html.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-triage.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-verify-verdict.cpython-312.pyc +0 -0
package/.tfcore/utils/tf-emit.sh
CHANGED
|
@@ -23,6 +23,8 @@
|
|
|
23
23
|
# # marks a WRONG run record as not to be counted: both
|
|
24
24
|
# # records stay, every figure skips it and says how many
|
|
25
25
|
# # and why (SCHEMA.md §2.7). Refuses if no such run.
|
|
26
|
+
# tf-emit.sh --void-miss MISS-App-20260919-01 "logged again from an earlier triage's leftovers"
|
|
27
|
+
# # the same for a WRONG miss (SCHEMA.md §5.5.11)
|
|
26
28
|
# tf-emit.sh --origin-of REQ-UI-014 # prints "<started> <cmd> <agent>" of the last build or fix
|
|
27
29
|
# # run that touched the row, nothing when there is none
|
|
28
30
|
# tf-emit.sh --where # prints the resolved docs/metrics dir
|
|
@@ -292,7 +294,7 @@ if [[ "$1" == "--open-misses" ]]; then
|
|
|
292
294
|
import json, os, sys
|
|
293
295
|
DOC = {"brd", "architecture", "uidesign", "checklist", "devguide"}
|
|
294
296
|
app, klass = os.environ.get("TF_OMAPP") or "", os.environ.get("TF_OMCLASS") or ""
|
|
295
|
-
opened, fixes = [], {}
|
|
297
|
+
opened, fixes, voided = [], {}, set()
|
|
296
298
|
try:
|
|
297
299
|
with open(sys.argv[1], encoding="utf-8") as fh:
|
|
298
300
|
for line in fh:
|
|
@@ -305,6 +307,8 @@ try:
|
|
|
305
307
|
continue
|
|
306
308
|
if r.get("kind") == "miss":
|
|
307
309
|
opened.append(r)
|
|
310
|
+
elif r.get("kind") == "miss-void":
|
|
311
|
+
voided.add(r.get("miss_id"))
|
|
308
312
|
elif r.get("kind") == "miss-fix":
|
|
309
313
|
mid = r.get("miss_id")
|
|
310
314
|
if mid:
|
|
@@ -314,6 +318,8 @@ try:
|
|
|
314
318
|
except FileNotFoundError:
|
|
315
319
|
pass
|
|
316
320
|
for r in opened:
|
|
321
|
+
if r.get("miss_id") in voided:
|
|
322
|
+
continue
|
|
317
323
|
if app and r.get("app") and r.get("app") != app:
|
|
318
324
|
continue
|
|
319
325
|
art = r.get("artifact") or ""
|
|
@@ -335,7 +341,7 @@ if [[ "$1" == "--open-miss" ]]; then
|
|
|
335
341
|
python3 - "$MET_DIR/misses.jsonl" "$REQ" 2>"$TF_ERR" <<'PY' || true
|
|
336
342
|
import json, sys
|
|
337
343
|
path, req = sys.argv[1], sys.argv[2]
|
|
338
|
-
opened, fixes = [], {}
|
|
344
|
+
opened, fixes, voided = [], {}, set()
|
|
339
345
|
try:
|
|
340
346
|
with open(path, encoding="utf-8") as fh:
|
|
341
347
|
for line in fh:
|
|
@@ -348,6 +354,8 @@ try:
|
|
|
348
354
|
continue
|
|
349
355
|
if r.get("kind") == "miss" and r.get("req_id") == req:
|
|
350
356
|
opened.append(r)
|
|
357
|
+
elif r.get("kind") == "miss-void":
|
|
358
|
+
voided.add(r.get("miss_id"))
|
|
351
359
|
elif r.get("kind") == "miss-fix":
|
|
352
360
|
mid = r.get("miss_id")
|
|
353
361
|
if mid:
|
|
@@ -357,6 +365,8 @@ try:
|
|
|
357
365
|
except FileNotFoundError:
|
|
358
366
|
pass
|
|
359
367
|
for r in reversed(opened): # most recent first
|
|
368
|
+
if r.get("miss_id") in voided:
|
|
369
|
+
continue
|
|
360
370
|
fix = fixes.get(r.get("miss_id"))
|
|
361
371
|
if fix is None or fix.get("verdict_after") != "Verified":
|
|
362
372
|
print("%s %s" % (r.get("miss_id"), r.get("miss_class") or "other"))
|
|
@@ -458,6 +468,45 @@ PY
|
|
|
458
468
|
exit 0
|
|
459
469
|
fi
|
|
460
470
|
|
|
471
|
+
# --- write helper: withdraw a miss that should never have been written -----
|
|
472
|
+
# SCHEMA.md §5.5.11 — --void-run's answer for the misses stream (TrBlazeUI TF-001).
|
|
473
|
+
if [[ "$1" == "--void-miss" ]]; then
|
|
474
|
+
VMID="$2"; VWHY="$3"
|
|
475
|
+
VREC="$(python3 - "$MET_DIR/misses.jsonl" "$VMID" "$VWHY" 2>"$TF_ERR" <<'PY' || true
|
|
476
|
+
import json, sys
|
|
477
|
+
path, mid, why = sys.argv[1], sys.argv[2], sys.argv[3]
|
|
478
|
+
found, voided = False, False
|
|
479
|
+
try:
|
|
480
|
+
for line in open(path, encoding="utf-8"):
|
|
481
|
+
try:
|
|
482
|
+
r = json.loads(line)
|
|
483
|
+
except Exception:
|
|
484
|
+
continue
|
|
485
|
+
if r.get("miss_id") == mid:
|
|
486
|
+
found = found or r.get("kind") == "miss"
|
|
487
|
+
voided = voided or r.get("kind") == "miss-void"
|
|
488
|
+
except FileNotFoundError:
|
|
489
|
+
pass
|
|
490
|
+
if not found:
|
|
491
|
+
print("REFUSED no miss record %s on this stream" % mid)
|
|
492
|
+
elif voided:
|
|
493
|
+
print("REFUSED %s is already voided — one void is enough" % mid)
|
|
494
|
+
elif not why.strip():
|
|
495
|
+
print("REFUSED a void needs a reason a reader can check")
|
|
496
|
+
else:
|
|
497
|
+
print("OK " + json.dumps({"kind": "miss-void", "miss_id": mid, "reason": why.strip()},
|
|
498
|
+
separators=(",", ":"), ensure_ascii=False))
|
|
499
|
+
PY
|
|
500
|
+
)"
|
|
501
|
+
case "$VREC" in
|
|
502
|
+
"OK "*) printf '%s' "${VREC#OK }" | bash "${BASH_SOURCE[0]}" misses
|
|
503
|
+
echo "tf-emit: voided $VMID — $VWHY" ;;
|
|
504
|
+
"REFUSED "*) echo "tf-emit: void refused — ${VREC#REFUSED }" ;;
|
|
505
|
+
*) echo "tf-emit: void could not be evaluated — nothing written" ;;
|
|
506
|
+
esac
|
|
507
|
+
exit 0
|
|
508
|
+
fi
|
|
509
|
+
|
|
461
510
|
# --- write helper: mark a run record as not to be counted -----------------
|
|
462
511
|
# SCHEMA.md §2.7. The same doctrine as --amend, one stream over: a run record that
|
|
463
512
|
# is WRONG cannot be edited or deleted, because the stream is append-only and the
|
|
@@ -536,7 +585,7 @@ esac
|
|
|
536
585
|
|
|
537
586
|
# The program is captured into a variable rather than fed on stdin — a heredoc
|
|
538
587
|
# on `python3 -` would REPLACE the caller's piped JSON and silently drop every event.
|
|
539
|
-
|
|
588
|
+
IFS= read -r -d '' TF_PROG <<'PY' || true # bash 3.2 misreads a here-document inside a command substitution
|
|
540
589
|
import json, os, sys, datetime, glob, re, subprocess, time
|
|
541
590
|
|
|
542
591
|
met_dir, stream, root = sys.argv[1], sys.argv[2], sys.argv[3]
|
|
@@ -966,16 +1015,34 @@ def _window_opencode(t0, t1):
|
|
|
966
1015
|
return None
|
|
967
1016
|
dbp = ptr.get("db_path")
|
|
968
1017
|
sid = ptr.get("session_id")
|
|
969
|
-
if not dbp or not
|
|
1018
|
+
if not dbp or not os.path.isfile(dbp):
|
|
970
1019
|
return None
|
|
971
1020
|
try:
|
|
972
1021
|
import sqlite3
|
|
973
1022
|
db = sqlite3.connect("file:%s?mode=ro" % dbp, uri=True)
|
|
974
|
-
|
|
1023
|
+
# OpenCode 2 keeps sessions in session_v2 and messages in session_message; the old tables
|
|
1024
|
+
# stopped on 2026-09-27, so every OpenCode record since read tokens_scope "none". Both are read.
|
|
1025
|
+
tables = {r[0] for r in db.execute("SELECT name FROM sqlite_master WHERE type='table'")}
|
|
1026
|
+
pairs = db.execute("SELECT id, parent_id FROM session").fetchall() if "session" in tables else []
|
|
1027
|
+
if "session_v2" in tables:
|
|
1028
|
+
pairs += db.execute("SELECT id, parent_id FROM session_v2").fetchall()
|
|
975
1029
|
kids = {}
|
|
976
1030
|
for i, p in pairs:
|
|
977
1031
|
kids.setdefault(p, []).append(i)
|
|
978
|
-
|
|
1032
|
+
# The roots: the pointer's session, and every root session OpenCode 2 recorded in THIS project's
|
|
1033
|
+
# folder. Each *develop-end-to-end phase runs in a new session and the plugin's pointer kept the
|
|
1034
|
+
# first one, so the later phases read "none" (2026-10-08). Messages are still taken only inside
|
|
1035
|
+
# the record's own window.
|
|
1036
|
+
roots = {sid} if sid else set()
|
|
1037
|
+
if "session_v2" in tables:
|
|
1038
|
+
here = os.path.realpath(root)
|
|
1039
|
+
for i, p_, dr in db.execute("SELECT id, parent_id, directory FROM session_v2"):
|
|
1040
|
+
if not p_ and dr and os.path.realpath(dr) == here:
|
|
1041
|
+
roots.add(i)
|
|
1042
|
+
if not roots:
|
|
1043
|
+
db.close()
|
|
1044
|
+
return None
|
|
1045
|
+
tree, queue = set(roots), list(roots)
|
|
979
1046
|
while queue:
|
|
980
1047
|
for c in kids.get(queue.pop(), []):
|
|
981
1048
|
if c not in tree:
|
|
@@ -987,15 +1054,20 @@ def _window_opencode(t0, t1):
|
|
|
987
1054
|
# Same rule as the Claude branch: a CHILD session counts as a subagent run
|
|
988
1055
|
# for THIS window only if it produced output inside it.
|
|
989
1056
|
child_out = {}
|
|
990
|
-
|
|
991
|
-
|
|
1057
|
+
rows = []
|
|
1058
|
+
if "message" in tables:
|
|
1059
|
+
rows += db.execute("SELECT session_id, data FROM message WHERE time_created BETWEEN ? AND ?", (t0, t1)).fetchall()
|
|
1060
|
+
if "session_message" in tables:
|
|
1061
|
+
rows += [(a, b) for a, b in db.execute(
|
|
1062
|
+
"SELECT session_id, data FROM session_message WHERE type = 'assistant' AND time_created BETWEEN ? AND ?", (t0, t1))]
|
|
1063
|
+
for msid, data in rows:
|
|
992
1064
|
if msid not in tree:
|
|
993
1065
|
continue
|
|
994
1066
|
try:
|
|
995
1067
|
d = json.loads(data)
|
|
996
1068
|
except Exception:
|
|
997
1069
|
continue
|
|
998
|
-
if d.get("role")
|
|
1070
|
+
if d.get("role") not in (None, "assistant"):
|
|
999
1071
|
continue
|
|
1000
1072
|
t = d.get("tokens") or {}
|
|
1001
1073
|
cache = t.get("cache") or {}
|
|
@@ -1005,9 +1077,11 @@ def _window_opencode(t0, t1):
|
|
|
1005
1077
|
tot["cr"] += cache.get("read") or 0
|
|
1006
1078
|
tot["cw"] += cache.get("write") or 0
|
|
1007
1079
|
cost += d.get("cost") or 0
|
|
1008
|
-
if msid
|
|
1080
|
+
if msid not in roots and o:
|
|
1009
1081
|
child_out[msid] = child_out.get(msid, 0) + o
|
|
1010
|
-
|
|
1082
|
+
mo = d.get("model") if isinstance(d.get("model"), dict) else {}
|
|
1083
|
+
prov = d.get("providerID") or mo.get("providerID") or ""
|
|
1084
|
+
m = (prov + "/" if prov else "") + (d.get("modelID") or mo.get("id") or "")
|
|
1011
1085
|
if m:
|
|
1012
1086
|
models[m] = models.get(m, 0) + o
|
|
1013
1087
|
db.close()
|
|
@@ -1054,20 +1128,25 @@ def _prior_runs():
|
|
|
1054
1128
|
# (MISS-TechieFlow-20260910-04, sorted `ignored`: it was written down and not followed).
|
|
1055
1129
|
#
|
|
1056
1130
|
# A stream is append-only, so the only place to stop it is before the write. The rule: for one
|
|
1057
|
-
# app, a live `run` record may not
|
|
1058
|
-
#
|
|
1059
|
-
#
|
|
1131
|
+
# app, a live `run` record's window may not OVERLAP another live `run` record's window. It is the
|
|
1132
|
+
# windows that are compared, not the order of writing: a command that chains another one inside
|
|
1133
|
+
# itself -- *build-phase chaining *verify -- finishes last but did its own first segment before the
|
|
1134
|
+
# inner run started, and comparing against the newest record alone meant that segment could never be
|
|
1135
|
+
# recorded at all (TfLens TF-040, 80 minutes and five builder clusters on no record). Two windows
|
|
1136
|
+
# that merely touch at a boundary do not overlap. Voided and backfilled records are ignored, which
|
|
1137
|
+
# is what makes the void-then-re-emit flow work. Records with no `started` are untouched.
|
|
1060
1138
|
#
|
|
1061
1139
|
# The one legitimate overlap is two machines appending to the same merge=union stream, so
|
|
1062
1140
|
# `--allow-overlap` exists and the refusal message names it. It is deliberately a flag and not a
|
|
1063
1141
|
# fallback: the mistake this refuses is easy to make and invisible afterwards.
|
|
1064
|
-
|
|
1142
|
+
_LIVE = None
|
|
1065
1143
|
|
|
1066
1144
|
|
|
1067
|
-
def
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1145
|
+
def _live_windows(app):
|
|
1146
|
+
"""[(started, ended, cmd)] of every live run record for this app, oldest first."""
|
|
1147
|
+
global _LIVE
|
|
1148
|
+
if _LIVE is None:
|
|
1149
|
+
_LIVE = {}
|
|
1071
1150
|
voided = set()
|
|
1072
1151
|
rows = []
|
|
1073
1152
|
try:
|
|
@@ -1089,6 +1168,9 @@ def _last_live_end(app):
|
|
|
1089
1168
|
for r in rows:
|
|
1090
1169
|
if (r.get("app"), r.get("cmd"), r.get("started")) in voided:
|
|
1091
1170
|
continue
|
|
1171
|
+
start = r.get("started")
|
|
1172
|
+
if not start:
|
|
1173
|
+
continue
|
|
1092
1174
|
# `ended` ONLY. `ts` is when the record was WRITTEN, not when the run finished, so
|
|
1093
1175
|
# falling back to it would make every historical record emitted today look like an
|
|
1094
1176
|
# overlap with the record written a second ago. A run with no `ended` has no window
|
|
@@ -1103,38 +1185,37 @@ def _last_live_end(app):
|
|
|
1103
1185
|
end = r.get("ended")
|
|
1104
1186
|
if not end:
|
|
1105
1187
|
continue
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
_LAST_END[a] = (end, r.get("cmd"), r.get("started"))
|
|
1109
|
-
return _LAST_END.get(app)
|
|
1188
|
+
_LIVE.setdefault(r.get("app"), []).append((start, end, r.get("cmd")))
|
|
1189
|
+
return _LIVE.get(app, [])
|
|
1110
1190
|
|
|
1111
1191
|
|
|
1112
1192
|
def _overlap_ok(rec):
|
|
1113
|
-
"""False (and the reason printed) when this run
|
|
1193
|
+
"""False (and the reason printed) when this run's window overlaps a live one's."""
|
|
1114
1194
|
if stream != "runs" or rec.get("kind", "run") != "run" or rec.get("backfilled"):
|
|
1115
1195
|
return True
|
|
1116
1196
|
if os.environ.get("TF_ALLOW_OVERLAP") == "1":
|
|
1117
1197
|
return True
|
|
1118
|
-
started = rec.get("started")
|
|
1198
|
+
started, ended = rec.get("started"), rec.get("ended")
|
|
1119
1199
|
if not started:
|
|
1120
1200
|
return True
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
return
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1201
|
+
def key(v):
|
|
1202
|
+
k = _iso_ms(v)
|
|
1203
|
+
return k if k is not None else v # unparseable: compare as strings, as before
|
|
1204
|
+
s, e = key(started), key(ended or started)
|
|
1205
|
+
for (ps, pe, pcmd) in _live_windows(rec.get("app")):
|
|
1206
|
+
a, b = key(ps), key(pe)
|
|
1207
|
+
if type(a) is not type(s) or type(b) is not type(e):
|
|
1208
|
+
continue
|
|
1209
|
+
if s < b and a < e: # windows that only touch at a boundary are fine
|
|
1210
|
+
sys.stdout.write(
|
|
1211
|
+
"tf-emit: REFUSED — this run (%s to %s) overlaps the %s run of %s already on the "
|
|
1212
|
+
"stream (%s to %s). Every total over the two would be that much too big. Record the "
|
|
1213
|
+
"part that did not overlap -- a command that chained another one records its own "
|
|
1214
|
+
"segment before the inner run started -- or pass --allow-overlap if they really did "
|
|
1215
|
+
"run at the same time (a second machine on the same stream). Nothing was appended.\n"
|
|
1216
|
+
% (started, ended or "now", pcmd, rec.get("app"), ps, pe))
|
|
1217
|
+
return False
|
|
1218
|
+
return True
|
|
1138
1219
|
|
|
1139
1220
|
|
|
1140
1221
|
def stamp_attempt(rec):
|
|
@@ -1407,7 +1488,7 @@ _ALLOWED = {
|
|
|
1407
1488
|
"routed", "tokens_in", "tokens_out", "tokens_cache_read", "tokens_cache_write",
|
|
1408
1489
|
"cost_usd", "tokens_scope", "attempt", "subagent_runs", "tokens_out_subagents",
|
|
1409
1490
|
"model_tokens_out", "session_id", "usage_start_event_ts", "usage_end_event_ts",
|
|
1410
|
-
"billing_mode", "cost_source", "fallback_from"},
|
|
1491
|
+
"billing_mode", "cost_source", "fallback_from", "app_phase"},
|
|
1411
1492
|
# A correction, not a deletion: it names a run record that is wrong by the pair every
|
|
1412
1493
|
# run carries (`cmd` + `started`) and says why, so every figure can skip it while the
|
|
1413
1494
|
# record itself stays where it is. Written by --void-run, which refuses one that names
|
|
@@ -1418,7 +1499,7 @@ _ALLOWED = {
|
|
|
1418
1499
|
"tier", "tier_model", "model", "models", "tokens_in", "tokens_out", "tokens_cache_read",
|
|
1419
1500
|
"tokens_cache_write", "cost_usd", "tokens_scope", "model_tokens_out", "routed",
|
|
1420
1501
|
"subagent_runs", "tokens_out_subagents", "session_id",
|
|
1421
|
-
"billing_mode", "cost_source", "fallback_from"},
|
|
1502
|
+
"billing_mode", "cost_source", "fallback_from", "app_phase"},
|
|
1422
1503
|
("sessions", "session"): {"session_id", "model", "duration_s", "input_tokens", "output_tokens",
|
|
1423
1504
|
"cache_read_tokens", "cache_creation_tokens", "cost_usd", "children_sessions"},
|
|
1424
1505
|
("commits", "commit"): {"sha", "files", "insertions", "deletions", "subject_prefix", "branch"},
|
|
@@ -1431,6 +1512,8 @@ _ALLOWED = {
|
|
|
1431
1512
|
"tokens_cache_write", "cost_usd", "tokens_scope", "model",
|
|
1432
1513
|
"billing_mode", "cost_source"},
|
|
1433
1514
|
("misses", "miss-amend"): {"miss_id", "field", "value"},
|
|
1515
|
+
# a miss that should never have been written, withdrawn by --void-miss (SCHEMA.md §5.5.11)
|
|
1516
|
+
("misses", "miss-void"): {"miss_id", "reason"},
|
|
1434
1517
|
# an owner review of a phase's output (FR-36, built 2026-09-06): how many corrections were
|
|
1435
1518
|
# given, what producing the reviewed output cost, what applying the corrections cost. The two
|
|
1436
1519
|
# costs are copied from the runs named by reviewed_run_id and correction_run_id (their `started`).
|
|
@@ -1461,6 +1544,11 @@ def _shape_ok(rec):
|
|
|
1461
1544
|
if kind == "run" and rec.get("build_result") not in (None, "pass", "fail", "not-run"):
|
|
1462
1545
|
sys.stdout.write("tf-emit: REFUSED — build_result must be pass, fail or not-run (SCHEMA.md §2); got %r. Put the detail in the checklist Remarks or Known blockers, not here. Nothing was appended.\n" % (rec.get("build_result"),))
|
|
1463
1546
|
return False
|
|
1547
|
+
# one REQ's verdict, never a suite note: Lekhak hand-wrote four {"gate":"verify-suite","result":…}
|
|
1548
|
+
# records with neither, and the report counted each as a failure (TF-020)
|
|
1549
|
+
if kind == "gate" and not rec.get("backfilled") and not (rec.get("req_id") and rec.get("verdict")):
|
|
1550
|
+
sys.stdout.write("tf-emit: REFUSED — a gate record is one REQ's verdict and needs both req_id and verdict (SCHEMA.md §3). A suite result (unit tests, the whole browser run) belongs in the run record's notes, not here. Nothing was appended.\n")
|
|
1551
|
+
return False
|
|
1464
1552
|
if kind == "miss" and not rec.get("miss_id"):
|
|
1465
1553
|
sys.stdout.write("tf-emit: REFUSED — a miss record needs a miss_id; get one with: bash .tfcore/utils/tf-emit.sh --next-miss-id. Nothing was appended.\n")
|
|
1466
1554
|
return False
|
|
@@ -1588,7 +1676,6 @@ except Exception as e:
|
|
|
1588
1676
|
warn("append failed (%s) — event dropped" % e)
|
|
1589
1677
|
raise SystemExit(0)
|
|
1590
1678
|
PY
|
|
1591
|
-
)"
|
|
1592
1679
|
python3 -c "$TF_PROG" "$MET_DIR" "$STREAM" "$ROOT" 2>"$TF_ERR"
|
|
1593
1680
|
|
|
1594
1681
|
# The readable miss list beside the record (FR-31, Session 5 2026-09-07): after any write to the
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# tf-feedback.sh — the state of every entry in this project's upstream feedback files, and the
|
|
3
|
+
# one way to mark an entry closed once its fix has been re-checked here (2026-09-11).
|
|
4
|
+
#
|
|
5
|
+
# bash .tfcore/utils/tf-feedback.sh <App> every entry: open, fixed upstream, closed
|
|
6
|
+
# bash .tfcore/utils/tf-feedback.sh <App> --waiting fixed upstream, not yet re-checked here
|
|
7
|
+
# bash .tfcore/utils/tf-feedback.sh <App> --close <ID> "<what you ran and what it showed>"
|
|
8
|
+
#
|
|
9
|
+
# Details: tf_feedback.py. Writes nothing except the one closing line --close adds.
|
|
10
|
+
set -u
|
|
11
|
+
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
12
|
+
command -v python3 >/dev/null 2>&1 || { echo "tf-feedback: python3 is required" >&2; exit 2; }
|
|
13
|
+
exec python3 "$HERE/tf_feedback.py" "$@"
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
#
|
|
4
4
|
# bash .tfcore/utils/tf-fix-close.sh <App> [--started <ISO>] [--reqs REQ-UI-009,REQ-FN-014]
|
|
5
5
|
# [--subagents trblazeui,builder] [--build pass|fail|not-run] [--files N]
|
|
6
|
+
# [--cmd triage-and-fix] # the command the run record is filed under
|
|
6
7
|
# bash .tfcore/utils/tf-fix-close.sh <App> --misses MISS-App-20260901-06,MISS-App-20260830-03 \
|
|
7
8
|
# --fix-cmd amend-docs [--verdict Verified]
|
|
8
9
|
#
|
|
@@ -15,18 +16,25 @@
|
|
|
15
16
|
#
|
|
16
17
|
# Emits, in this order, through tf-emit.sh only:
|
|
17
18
|
# 1. the fix-issues run record (cmd fix-issues, mode fix, the rows touched) — first, because the
|
|
18
|
-
# miss-fix records below carry no numbers of their own: the emitter copies this run's token window
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
19
|
+
# miss-fix records below carry no numbers of their own: the emitter copies this run's token window.
|
|
20
|
+
# A run the fix chained (the inline verify) keeps its own record, so the fix is recorded in the gaps
|
|
21
|
+
# around it; the first gap carries the rows and the fix's start (TF-052).
|
|
22
|
+
# 2. one miss-fix per row that has an open miss, with verdict_after taken from every verify the fix
|
|
23
|
+
# chained: the gate records written since the fix started, then the ledger docs/.last-verify.json
|
|
24
|
+
# when it belongs to this fix (PASS → Verified; FAIL → FAIL; any other failing verdict → Needs
|
|
25
|
+
# re-verify). A row no verify graded gets no miss-fix and is listed. A row with no open miss is listed,
|
|
22
26
|
# not invented: a fix on a defect nobody logged is a triage gap, so open it first with tf-triage.sh
|
|
23
27
|
# demote or tf-log-miss.sh, then re-run this.
|
|
24
|
-
# --
|
|
28
|
+
# --cmd names the command the run record is filed under (default fix-issues): *triage-and-fix passes
|
|
29
|
+
# --cmd triage-and-fix, so its run is counted as itself and no second record over the same window is
|
|
30
|
+
# needed, which the overlap rule refused (TrBlazeUI TF-004). The miss-fix records keep fix_cmd fix-issues.
|
|
31
|
+
# --reqs defaults to every row those verifies graded. The start defaults to the fix's marker, or its
|
|
32
|
+
# "outer" entry when a chained verify replaced it. Telemetry never blocks: refusals are printed, exit 0.
|
|
25
33
|
set -u
|
|
26
34
|
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
27
35
|
[[ $# -ge 1 && "$1" != "-h" && "$1" != "--help" ]] || { sed -n '2,17p' "$0" | sed 's/^# \{0,1\}//'; exit 3; }
|
|
28
36
|
APP="$1"; shift
|
|
29
|
-
STARTED=""; REQS=""; SUBS=""; BUILD="pass"; FILES=""; MISSES=""; FIXCMD="fix-issues"; VERDICT=""
|
|
37
|
+
STARTED=""; REQS=""; SUBS=""; BUILD="pass"; FILES=""; MISSES=""; FIXCMD="fix-issues"; VERDICT=""; RUNCMD="fix-issues"
|
|
30
38
|
while [[ $# -gt 0 ]]; do
|
|
31
39
|
case "$1" in
|
|
32
40
|
--started) STARTED="${2:-}"; shift 2 ;;
|
|
@@ -37,14 +45,21 @@ while [[ $# -gt 0 ]]; do
|
|
|
37
45
|
--misses) MISSES="${2:-}"; shift 2 ;;
|
|
38
46
|
--fix-cmd) FIXCMD="${2:-}"; shift 2 ;;
|
|
39
47
|
--verdict) VERDICT="${2:-}"; shift 2 ;;
|
|
48
|
+
--cmd) RUNCMD="${2:-}"; shift 2 ;;
|
|
40
49
|
*) echo "tf-fix-close: unknown argument $1" >&2; exit 3 ;;
|
|
41
50
|
esac
|
|
42
51
|
done
|
|
43
|
-
|
|
52
|
+
# the fix's start: its own marker, or the "outer" one when a verify it chained has replaced the marker
|
|
53
|
+
[[ -z "$STARTED" ]] && STARTED="$(bash "$HERE/tf-phase.sh" show 2>/dev/null | python3 -c "import json,sys
|
|
54
|
+
try: m=json.load(sys.stdin)
|
|
55
|
+
except Exception: m={}
|
|
56
|
+
o=m.get('outer') or {}
|
|
57
|
+
print(m.get('started','') if m.get('cmd')!='verify-phase' or not o.get('started') else o['started'])" 2>/dev/null)"
|
|
44
58
|
TF_APP="$APP" TF_STARTED="$STARTED" TF_REQS="$REQS" TF_SUBS="$SUBS" TF_BUILD="$BUILD" TF_FILES="$FILES" \
|
|
45
|
-
TF_MISSES="$MISSES" TF_FIXCMD="$FIXCMD" TF_VERDICT="$VERDICT" TF_EMIT="$HERE/tf-emit.sh" python3 - <<'PY'
|
|
59
|
+
TF_MISSES="$MISSES" TF_FIXCMD="$FIXCMD" TF_RUNCMD="$RUNCMD" TF_VERDICT="$VERDICT" TF_EMIT="$HERE/tf-emit.sh" python3 - <<'PY'
|
|
46
60
|
import datetime, json, os, subprocess
|
|
47
61
|
EMIT = os.environ["TF_EMIT"]; app = os.environ["TF_APP"]; started = os.environ["TF_STARTED"]
|
|
62
|
+
cmd = os.environ.get("TF_RUNCMD") or "fix-issues"
|
|
48
63
|
now = datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
49
64
|
def q(*a): return subprocess.run(["bash", EMIT, *a], capture_output=True, text=True).stdout.strip()
|
|
50
65
|
def emit(stream, rec):
|
|
@@ -71,45 +86,86 @@ if direct:
|
|
|
71
86
|
% (fix_cmd, closed, len(direct)))
|
|
72
87
|
raise SystemExit(0)
|
|
73
88
|
|
|
89
|
+
met = q("--where") or "docs/metrics"
|
|
90
|
+
def jsonl(name):
|
|
91
|
+
try:
|
|
92
|
+
for line in open(os.path.join(met, name), encoding="utf-8", errors="replace"):
|
|
93
|
+
try: yield json.loads(line)
|
|
94
|
+
except Exception: pass
|
|
95
|
+
except OSError:
|
|
96
|
+
return
|
|
97
|
+
# The verdicts: every verify this fix chained, not only the last. docs/.last-verify.json holds one scoped
|
|
98
|
+
# verify, so a fix that verified a Phase 3 scope and then a --phase 1 scope read the Phase 3 rows as absent
|
|
99
|
+
# and wrote Needs re-verify on their misses (TF-052). The gate records each verify wrote since the fix
|
|
100
|
+
# started come first; the ledger fills in only when it belongs to this fix.
|
|
101
|
+
rows = {} # rid -> checklist status
|
|
102
|
+
if started:
|
|
103
|
+
for g in jsonl("gates.jsonl"):
|
|
104
|
+
if g.get("kind") == "gate" and g.get("app") in (None, app) and not g.get("backfilled") and g.get("gate") != "escaped" \
|
|
105
|
+
and (g.get("run_id") or "") >= started and g.get("req_id"):
|
|
106
|
+
rows[g["req_id"].upper()] = g.get("verdict")
|
|
74
107
|
ledger = {}
|
|
75
108
|
try:
|
|
76
109
|
ledger = json.load(open(os.path.join("docs", ".last-verify.json"), encoding="utf-8"))
|
|
77
110
|
except Exception:
|
|
78
111
|
pass
|
|
79
|
-
|
|
112
|
+
if not started or not ledger.get("run_id") or ledger["run_id"] >= started:
|
|
113
|
+
row_runs = ledger.get("row_runs") # a ledger kept row by row also holds older runs' rows (Lekhak TF-007)
|
|
114
|
+
for rid, v in (ledger.get("rows") or {}).items():
|
|
115
|
+
if started and isinstance(row_runs, dict) and rid not in (ledger.get("this_run") or []) \
|
|
116
|
+
and (row_runs.get(rid) or "") < started:
|
|
117
|
+
continue
|
|
118
|
+
# verdict_after takes a checklist status (SCHEMA §5.5): the ledger's verdict is mapped, never passed raw
|
|
119
|
+
# (MISS-TechieFlow-20260906-25: RENDER-FAIL was refused and twenty miss-fix records never landed)
|
|
120
|
+
rows.setdefault(rid.upper(), "Verified" if v == "PASS" else "FAIL" if v in ("FAIL", "BUILD-FAIL") else "Needs re-verify")
|
|
80
121
|
reqs = [r.strip().upper() for r in os.environ["TF_REQS"].split(",") if r.strip()] or sorted(rows)
|
|
81
122
|
subs = [s.strip() for s in os.environ["TF_SUBS"].split(",") if s.strip()]
|
|
82
123
|
files = os.environ["TF_FILES"]
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if
|
|
96
|
-
|
|
97
|
-
|
|
124
|
+
# The fix's own time only: a run it chained (the inline verify) has its own record, so the fix is recorded
|
|
125
|
+
# in the gaps around it, the first gap keyed on the fix's start with the rows (its miss-fixes name that start).
|
|
126
|
+
inner, voided = [], set()
|
|
127
|
+
for r in jsonl("runs.jsonl"):
|
|
128
|
+
if r.get("kind") == "run-void":
|
|
129
|
+
voided.add((r.get("cmd"), r.get("started")))
|
|
130
|
+
elif r.get("kind", "run") == "run" and r.get("app") == app and r.get("cmd") not in ("fix-issues", cmd) and not r.get("backfilled") \
|
|
131
|
+
and started and (r.get("started") or "") >= started and (r.get("ended") or "") and r["ended"] <= now:
|
|
132
|
+
inner.append((r["started"], r["ended"], r.get("cmd")))
|
|
133
|
+
inner = sorted(w for w in inner if (w[2], w[0]) not in voided)
|
|
134
|
+
gaps, t = [], started
|
|
135
|
+
for s, e, _c in inner:
|
|
136
|
+
if t and s > t: gaps.append((t, s))
|
|
137
|
+
t = max(t, e) if t else e
|
|
138
|
+
if not started or not gaps or t < now: gaps.append((t, now))
|
|
139
|
+
existing = {r.get("started") for r in jsonl("runs.jsonl") if r.get("kind", "run") == "run" and r.get("cmd") == cmd}
|
|
140
|
+
wrote = had = 0
|
|
141
|
+
for i, (s, e) in enumerate(gaps):
|
|
142
|
+
first = i == 0
|
|
143
|
+
run = {"kind": "run", "app": app, "cmd": cmd, "mode": "fix", "ended": e, "reqs_touched": reqs if first else [],
|
|
144
|
+
"reqs_count": len(reqs) if first else 0, "subagents": subs if first else [],
|
|
145
|
+
"files_written": (int(files) if files.isdigit() else len(reqs)) if first else 0, "build_result": os.environ["TF_BUILD"]}
|
|
146
|
+
if s: run["started"] = s
|
|
147
|
+
# one run record per start: a close called twice must not count the fix twice
|
|
148
|
+
if s and s in existing:
|
|
149
|
+
had += 1; continue
|
|
150
|
+
wrote += emit("runs", run)
|
|
151
|
+
if inner:
|
|
152
|
+
print(f"{cmd}: recorded around {len(inner)} chained run(s) ({', '.join(sorted({c for _s, _e, c in inner}))}) in {len(gaps)} segment(s)")
|
|
153
|
+
closed, none, unverified = 0, [], []
|
|
98
154
|
for rid in reqs:
|
|
155
|
+
if rid not in rows:
|
|
156
|
+
unverified.append(rid); continue
|
|
99
157
|
opened = q("--open-miss", rid)
|
|
100
158
|
if not opened:
|
|
101
159
|
none.append(rid); continue
|
|
102
160
|
mid = opened.split()[0]
|
|
103
161
|
fa = q("--next-fix-attempt", mid) or "1"
|
|
104
|
-
v = rows.get(rid)
|
|
105
|
-
# verdict_after takes a checklist status (SCHEMA §5.5): the ledger's verdict is mapped, never passed raw
|
|
106
|
-
# (MISS-TechieFlow-20260906-25: RENDER-FAIL was refused and twenty miss-fix records never landed)
|
|
107
|
-
after = ("Verified" if v == "PASS" else "FAIL" if v in ("FAIL", "BUILD-FAIL") else "Needs re-verify")
|
|
108
162
|
rec = {"kind": "miss-fix", "miss_id": mid, "req_id": rid, "fix_cmd": "fix-issues", "fix_attempt": int(fa) if fa.isdigit() else 1,
|
|
109
|
-
"verdict_after":
|
|
163
|
+
"verdict_after": rows[rid], "reopened": False}
|
|
110
164
|
if started: rec["fix_run_id"] = started
|
|
111
165
|
if emit("misses", rec): closed += 1
|
|
112
|
-
|
|
113
|
-
|
|
166
|
+
state = "already there" if had == len(gaps) else "written" if wrote + had == len(gaps) else "not written"
|
|
167
|
+
print(f"{cmd}: run record {state}; {closed} miss-fix record(s) with the verifier's verdict"
|
|
168
|
+
+ (f"; no open miss on {', '.join(none)} (log it first, then re-run)" if none else "")
|
|
169
|
+
+ (f"; no verify graded {', '.join(unverified)} during this fix, so its miss stays open with no miss-fix" if unverified else ""))
|
|
114
170
|
PY
|
|
115
171
|
exit 0
|
package/.tfcore/utils/tf-goal.sh
CHANGED
|
@@ -68,6 +68,8 @@
|
|
|
68
68
|
# Override command lines with TF_GOAL_CLAUDE_FLAGS / TF_GOAL_OPENCODE_FLAGS /
|
|
69
69
|
|
|
70
70
|
set -u
|
|
71
|
+
# GNU-only commands (setsid, stat -c, date -d) go through the shim, so a stock Mac runs this too.
|
|
72
|
+
source "$(dirname "${BASH_SOURCE[0]}")/tf-portable.sh"
|
|
71
73
|
|
|
72
74
|
HARNESS="claude"; MODEL=""; BUFFER_MIN=15; DEFAULT_WAIT_MIN=60; MAX_CYCLES=60; IDLE_RETRY=30
|
|
73
75
|
PROBE_MIN=15; PROBE_MAX_H=8; STALL_MIN=15
|
|
@@ -162,7 +164,7 @@ else
|
|
|
162
164
|
_lr="$(state_get last_reason)"
|
|
163
165
|
case "$_lr" in
|
|
164
166
|
done:*|max-cycles|stopped|"") ;;
|
|
165
|
-
*) echo "tf-goal: a run looks active in $APP_DIR (goal.json last_reason=$_lr, touched $(
|
|
167
|
+
*) echo "tf-goal: a run looks active in $APP_DIR (goal.json last_reason=$_lr, touched $(tf_date_from -u "@$(tf_stat_mtime "$STATE")" +%H:%MZ)). Wait for it, or continue it with --resume." >&2; exit 2 ;;
|
|
166
168
|
esac
|
|
167
169
|
fi
|
|
168
170
|
CYCLE=0; SESSION_ID=""; STALLS=0
|
|
@@ -436,11 +438,11 @@ probe_until_clear() {
|
|
|
436
438
|
else
|
|
437
439
|
( cd "$APP_DIR" && opencode run --auto "Reply with the single word OK." ) > "$pout" 2>&1; prc=$?
|
|
438
440
|
fi
|
|
439
|
-
if [[ $prc -eq 0 ]] && ! grep -qiE 'usage limit|hit your limit|rate[ _-]?limit|limit (has been )?(reached|exceeded)|too many requests
|
|
441
|
+
if [[ $prc -eq 0 ]] && ! grep -qiE 'usage limit|hit your limit|rate[ _-]?limit|limit (has been )?(reached|exceeded)|too many requests|(^|[^[:alnum:]_])429([^[:alnum:]_]|$)|overloaded|weekly limit|resets? (at|in)([^[:alnum:]_]|$)' "$pout"; then
|
|
440
442
|
log "probe #$n OK"; return 0
|
|
441
443
|
fi
|
|
442
444
|
log "probe #$n still limited (rc=$prc: $(head -c 120 "$pout" | tr '\n' ' ')) — next in ${PROBE_MIN}m"
|
|
443
|
-
state_set resume_at "probe #$((n+1)) at $(
|
|
445
|
+
state_set resume_at "probe #$((n+1)) at $(tf_date_from "+${PROBE_MIN} min" '+%H:%M' 2>/dev/null)"
|
|
444
446
|
if [[ $(date +%s) -ge $deadline ]]; then log "probe window (${PROBE_MAX_H}h) exhausted — resuming anyway"; return 1; fi
|
|
445
447
|
nap $(( PROBE_MIN * 60 ))
|
|
446
448
|
done
|
|
@@ -450,7 +452,7 @@ sleep_until() { # epoch
|
|
|
450
452
|
local target="$1" now left
|
|
451
453
|
while :; do
|
|
452
454
|
now=$(date +%s); left=$(( target - now )); [[ $left -le 0 ]] && break
|
|
453
|
-
state_set resume_at "$(
|
|
455
|
+
state_set resume_at "$(tf_date_from -u "@$target" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || echo "$target")"
|
|
454
456
|
if [[ $left -gt 300 ]]; then nap 300; else nap "$left"; fi
|
|
455
457
|
done
|
|
456
458
|
state_set resume_at ""
|
|
@@ -482,16 +484,14 @@ run_cycle() { # runs "${CMD[@]}" in $APP_DIR, output to $OUT and $LOG; sets RC,
|
|
|
482
484
|
STALLED=0
|
|
483
485
|
local quiet=0 prev=-1 size
|
|
484
486
|
if [[ -n "${TF_GOAL_FAKE_CMD:-}" ]]; then CMD=(bash -c "$TF_GOAL_FAKE_CMD"); fi
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
( cd "$APP_DIR" && TF_YOLO=1 exec "${CMD[@]}" ) > >(tee -a "$LOG" > "$OUT") 2>&1 &
|
|
489
|
-
fi
|
|
487
|
+
# Its own session and process group (tf_setsid: setsid(1), or python3/perl on a Mac), so
|
|
488
|
+
# kill_child can stop the whole tree.
|
|
489
|
+
( cd "$APP_DIR" && TF_YOLO=1 tf_setsid --exec "${CMD[@]}" ) > >(tee -a "$LOG" > "$OUT") 2>&1 &
|
|
490
490
|
CHILD=$!
|
|
491
491
|
while kill -0 "$CHILD" 2>/dev/null; do
|
|
492
492
|
nap "$STALL_TICK"
|
|
493
493
|
kill -0 "$CHILD" 2>/dev/null || break
|
|
494
|
-
size="$(
|
|
494
|
+
size="$(tf_stat_size "$OUT" 2>/dev/null || echo 0)"
|
|
495
495
|
if [[ "$size" == "$prev" ]]; then quiet=$(( quiet + STALL_TICK )); else quiet=0; prev="$size"; fi
|
|
496
496
|
if [[ "$STALL_SEC" -gt 0 && $quiet -ge "$STALL_SEC" ]]; then
|
|
497
497
|
log "STALL: no output for $(( quiet / 60 ))m$(( quiet % 60 ))s (cycle $CYCLE, ${size} bytes) — stopping the harness, re-prompting"
|
|
@@ -569,7 +569,7 @@ while :; do
|
|
|
569
569
|
OUT="$STATE_DIR/goal-cycle-$CYCLE.out"
|
|
570
570
|
if [[ $DRY -eq 1 ]]; then echo "dry run — would launch cycle $CYCLE ($KIND):" >&2; printf ' %q' "${CMD[@]}"; echo; exit 0; fi
|
|
571
571
|
state_set cycle "$CYCLE"; CYCLE_START="$(date +%s)"
|
|
572
|
-
log "cycle $CYCLE ($KIND) → ${CMD[*]:0:6} … (prompt ${#CMD[-1]} chars)"
|
|
572
|
+
log "cycle $CYCLE ($KIND) → ${CMD[*]:0:6} … (prompt ${#CMD[${#CMD[@]}-1]} chars)"
|
|
573
573
|
|
|
574
574
|
# The run's start is now, not when the agent reaches step 0: write an unclaimed marker the
|
|
575
575
|
# first command's `tf-phase.sh start` claims (MISS-TechieFlow-20260905-20).
|
|
@@ -620,7 +620,7 @@ while :; do
|
|
|
620
620
|
log "probe succeeded — resuming session ${SESSION_ID:-(--continue)} after a ${BUFFER_MIN}m buffer"
|
|
621
621
|
nap $(( BUFFER_MIN * 60 ))
|
|
622
622
|
else
|
|
623
|
-
WHEN="$(
|
|
623
|
+
WHEN="$(tf_date_from "@$DETAIL" '+%Y-%m-%d %H:%M %Z' 2>/dev/null || echo "$DETAIL")"
|
|
624
624
|
log "USAGE LIMIT hit (cycle $CYCLE) — RETRY AT $WHEN (stated reset + ${BUFFER_MIN}m buffer). Sleeping."
|
|
625
625
|
state_set last_reason "limit" resume_at "$WHEN"
|
|
626
626
|
sleep_until "$DETAIL"
|