@techierathore/techieflow 1.0.1 → 1.0.3
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/analyst.md +0 -5
- package/.claude/commands/TechieFlow/agents/architect.md +0 -6
- package/.claude/commands/TechieFlow/agents/flow-master.md +41 -215
- package/.claude/commands/TechieFlow/agents/verifier.md +23 -64
- package/.claude/commands/TechieFlow/tasks/_metrics-emit-gate.md +24 -117
- package/.claude/commands/TechieFlow/tasks/_smoke-test-policy.md +20 -84
- package/.claude/commands/TechieFlow/tasks/_status-update-gate.md +10 -74
- package/.claude/commands/TechieFlow/tasks/_yolo-mode.md +20 -58
- package/.claude/commands/TechieFlow/tasks/amend-docs.md +20 -143
- package/.claude/commands/TechieFlow/tasks/build-phase.md +14 -224
- package/.claude/commands/TechieFlow/tasks/day1-brownfield.md +31 -505
- package/.claude/commands/TechieFlow/tasks/day1-greenfield.md +28 -213
- package/.claude/commands/TechieFlow/tasks/deploy-checklist.md +19 -0
- package/.claude/commands/TechieFlow/tasks/devguide.md +10 -219
- package/.claude/commands/TechieFlow/tasks/fix-issues.md +10 -129
- package/.claude/commands/TechieFlow/tasks/log-miss.md +19 -158
- package/.claude/commands/TechieFlow/tasks/metrics-report.md +1 -1
- package/.claude/commands/TechieFlow/tasks/mockups.md +12 -117
- package/.claude/commands/TechieFlow/tasks/refresh-status.md +9 -130
- package/.claude/commands/TechieFlow/tasks/split-brd.md +10 -126
- package/.claude/commands/TechieFlow/tasks/triage-and-fix.md +14 -0
- package/.claude/commands/TechieFlow/tasks/triage-issues.md +11 -152
- package/.claude/commands/TechieFlow/tasks/verify-phase.md +36 -572
- package/.opencode/plugin/techieflow.js +39 -12
- package/.tfcore/agents/analyst.md +0 -5
- package/.tfcore/agents/architect.md +0 -6
- package/.tfcore/agents/flow-master.md +41 -215
- package/.tfcore/agents/verifier.md +23 -64
- package/.tfcore/core-config.yaml +10 -0
- package/.tfcore/hooks/block-git.sh +4 -4
- package/.tfcore/hooks/guard-artifacts.sh +2 -2
- package/.tfcore/hooks/guard-build.sh +76 -0
- package/.tfcore/hooks/guard-db.sh +90 -0
- package/.tfcore/hooks/guard-metrics.sh +56 -0
- package/.tfcore/hooks/guard-status-html.sh +116 -71
- package/.tfcore/hooks/guard-status.sh +43 -16
- package/.tfcore/hooks/guard-verify.sh +70 -48
- package/.tfcore/hooks/sweep-artifacts.sh +2 -2
- package/.tfcore/install-manifest.yaml +0 -18
- package/.tfcore/routing.yaml +25 -14
- package/.tfcore/standards/coding-standards-core.md +61 -0
- package/.tfcore/standards/coding-standards-dotnet.md +93 -0
- package/.tfcore/tasks/_metrics-emit-gate.md +24 -117
- package/.tfcore/tasks/_smoke-test-policy.md +20 -84
- package/.tfcore/tasks/_status-update-gate.md +10 -74
- package/.tfcore/tasks/_yolo-mode.md +20 -58
- package/.tfcore/tasks/amend-docs.md +20 -143
- package/.tfcore/tasks/build-phase.md +14 -224
- package/.tfcore/tasks/day1-brownfield.md +31 -505
- package/.tfcore/tasks/day1-greenfield.md +28 -213
- package/.tfcore/tasks/deploy-checklist.md +19 -0
- package/.tfcore/tasks/devguide.md +10 -219
- package/.tfcore/tasks/fix-issues.md +10 -129
- package/.tfcore/tasks/log-miss.md +19 -158
- package/.tfcore/tasks/metrics-report.md +1 -1
- package/.tfcore/tasks/mockups.md +12 -117
- package/.tfcore/tasks/refresh-status.md +9 -130
- package/.tfcore/tasks/split-brd.md +10 -126
- package/.tfcore/tasks/triage-and-fix.md +14 -0
- package/.tfcore/tasks/triage-issues.md +11 -152
- package/.tfcore/tasks/verify-phase.md +36 -572
- package/.tfcore/telemetry/SCHEMA.md +43 -7
- package/.tfcore/telemetry/tf-metrics.sh +152 -17
- package/.tfcore/templates/project-brief-tmpl.yaml +1 -1
- package/.tfcore/templates/stack-defaults/dotnet.md +35 -0
- package/.tfcore/templates/stack-questions.md +77 -0
- package/.tfcore/templates/v4custom/app-agents-md-tmpl.md +3 -4
- package/.tfcore/templates/v4custom/app-architecture-tmpl.md +107 -115
- package/.tfcore/templates/v4custom/app-brd-tmpl.md +108 -184
- package/.tfcore/templates/v4custom/app-checklist-tmpl.md +48 -73
- package/.tfcore/templates/v4custom/app-claude-md-tmpl.md +1 -1
- package/.tfcore/templates/v4custom/app-coding-standards-tmpl.md +52 -0
- package/.tfcore/templates/v4custom/app-deployment-checklist-tmpl.md +86 -0
- package/.tfcore/templates/v4custom/app-devguide-tmpl.md +69 -146
- package/.tfcore/templates/v4custom/app-phases-tmpl.md +35 -0
- package/.tfcore/templates/v4custom/app-productguide-tmpl.md +38 -35
- package/.tfcore/templates/v4custom/app-project-status-tmpl.md +63 -73
- package/.tfcore/templates/v4custom/app-uidesign-tmpl.md +62 -37
- package/.tfcore/templates/v4custom/app-usageguide-tmpl.md +69 -54
- package/.tfcore/templates/v4custom/build-invocation-ladder.md +17 -180
- package/.tfcore/templates/v4custom/build-subagent-prompt.md +17 -0
- package/.tfcore/templates/v4custom/metrics-report-template.md +2 -2
- package/.tfcore/user-guide.md +0 -23
- package/.tfcore/utils/__pycache__/tf-brd-status.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-checklist-edit.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-devguide-list.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-doc-check.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-perf-grade.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-render-html.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-triage.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-verify-list.cpython-312.pyc +0 -0
- package/.tfcore/utils/__pycache__/tf-verify-verdict.cpython-312.pyc +0 -0
- package/.tfcore/utils/tf-brd-status.py +176 -0
- package/.tfcore/utils/tf-brd-status.sh +19 -0
- package/.tfcore/utils/tf-build-list.py +191 -0
- package/.tfcore/utils/tf-build-list.sh +20 -0
- package/.tfcore/utils/tf-build.sh +143 -0
- package/.tfcore/utils/tf-cdp-relay.ps1 +64 -0
- package/.tfcore/utils/tf-checklist-edit.py +133 -0
- package/.tfcore/utils/tf-day1-files.py +208 -0
- package/.tfcore/utils/tf-day1-files.sh +20 -0
- package/.tfcore/utils/tf-devguide-list.py +249 -0
- package/.tfcore/utils/tf-devguide-list.sh +18 -0
- package/.tfcore/utils/tf-doc-check.py +1267 -0
- package/.tfcore/utils/tf-doc-check.sh +29 -0
- package/.tfcore/utils/tf-emit.sh +203 -13
- package/.tfcore/utils/tf-fix-close.sh +83 -0
- package/.tfcore/utils/tf-goal.sh +193 -62
- package/.tfcore/utils/tf-harness.sh +4 -9
- package/.tfcore/utils/tf-log-miss.py +197 -0
- package/.tfcore/utils/tf-log-miss.sh +14 -0
- package/.tfcore/utils/tf-misses-md.py +211 -0
- package/.tfcore/utils/tf-misses-md.sh +13 -0
- package/.tfcore/utils/tf-mockups-locate.py +66 -0
- package/.tfcore/utils/tf-mockups-locate.sh +18 -0
- package/.tfcore/utils/tf-opencode-audit.py +23 -7
- package/.tfcore/utils/tf-perf-grade.py +97 -0
- package/.tfcore/utils/tf-perf-grade.sh +12 -0
- package/.tfcore/utils/tf-phase.sh +74 -0
- package/.tfcore/utils/tf-routing-bind.sh +10 -5
- package/.tfcore/utils/tf-routing.sh +6 -7
- package/.tfcore/utils/tf-split-brd.py +304 -0
- package/.tfcore/utils/tf-split-brd.sh +22 -0
- package/.tfcore/utils/tf-status-evidence.sh +68 -0
- package/.tfcore/utils/tf-status-facts.py +309 -0
- package/.tfcore/utils/tf-status-facts.sh +20 -0
- package/.tfcore/utils/tf-triage.py +243 -0
- package/.tfcore/utils/tf-triage.sh +16 -0
- package/.tfcore/utils/tf-verify-boot.sh +250 -0
- package/.tfcore/utils/tf-verify-emit.sh +110 -0
- package/.tfcore/utils/tf-verify-env.sh +88 -0
- package/.tfcore/utils/tf-verify-list.py +270 -0
- package/.tfcore/utils/tf-verify-list.sh +13 -0
- package/.tfcore/utils/tf-verify-screens.mjs +283 -0
- package/.tfcore/utils/tf-verify-screens.sh +22 -0
- package/.tfcore/utils/tf-verify-tests.sh +123 -0
- package/.tfcore/utils/tf-verify-verdict.py +283 -0
- package/.tfcore/utils/tf-verify-verdict.sh +15 -0
- package/.tfcore/utils/tf-yolo.sh +59 -0
- package/README.md +114 -1149
- package/docs/TechieFlow-Installation.md +27 -7
- package/opencode.jsonc +13 -33
- package/package.json +4 -7
- package/scripts/install.mjs +73 -46
- package/scripts/npm-cleanup.mjs +137 -0
- package/scripts/npm-postinstall.mjs +49 -0
- package/.claude/commands/TechieFlow/tasks/advanced-elicitation.md +0 -117
- package/.claude/commands/TechieFlow/tasks/author-brd.md +0 -79
- package/.claude/commands/TechieFlow/tasks/document-project.md +0 -343
- package/.claude/commands/TechieFlow/tasks/execute-checklist.md +0 -86
- package/.claude/commands/TechieFlow/tasks/index-docs.md +0 -173
- package/.claude/commands/TechieFlow/tasks/kb-mode-interaction.md +0 -75
- package/.claude/commands/TechieFlow/tasks/shard-doc.md +0 -185
- package/.codex/config.toml +0 -46
- package/.codex/hooks.json +0 -63
- package/.codex/rules/techieflow.rules +0 -8
- package/.tfcore/hooks/codex-adapter.py +0 -248
- package/.tfcore/tasks/advanced-elicitation.md +0 -117
- package/.tfcore/tasks/author-brd.md +0 -79
- package/.tfcore/tasks/document-project.md +0 -343
- package/.tfcore/tasks/execute-checklist.md +0 -86
- package/.tfcore/tasks/index-docs.md +0 -173
- package/.tfcore/tasks/kb-mode-interaction.md +0 -75
- package/.tfcore/tasks/shard-doc.md +0 -185
- package/.tfcore/utils/tf-codex-bind.py +0 -196
- package/.tfcore/utils/tf-codex-telemetry.py +0 -60
- package/WORKFLOW.html +0 -1986
- package/scripts/test-install.mjs +0 -505
- package/scripts/validate.mjs +0 -163
package/.tfcore/utils/tf-goal.sh
CHANGED
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
# bash .tfcore/utils/tf-goal.sh [options] <app-dir> @goal.md
|
|
11
11
|
#
|
|
12
12
|
# Options
|
|
13
|
-
# --harness claude|opencode
|
|
14
|
-
# --model <id> claude: --model; opencode: -m
|
|
13
|
+
# --harness claude|opencode default: claude
|
|
14
|
+
# --model <id> claude: --model; opencode: -m
|
|
15
15
|
# --buffer-min <n> minutes added after a stated limit-reset time (default 15)
|
|
16
16
|
# --probe-min <n> limit hit but NO reset time parseable → fire a one-turn probe every n
|
|
17
17
|
# minutes until the API answers again, then resume (default 15)
|
|
@@ -19,11 +19,21 @@
|
|
|
19
19
|
# --default-wait-min <n> (legacy) only used to stamp resume_at in goal.json when probing
|
|
20
20
|
# --max-cycles <n> give up after n launches (default 60)
|
|
21
21
|
# --idle-retry-sec <n> pause before re-prompting an agent that stopped without finishing (default 30)
|
|
22
|
+
# --stall-min <n> a cycle whose output has not grown for n minutes is killed and
|
|
23
|
+
# re-prompted (default 15; 0 disables) — MISS-TechieFlow-20260905-22
|
|
22
24
|
# --resume continue the last goal run recorded in .tfcore/.session/goal.json
|
|
25
|
+
# --fresh with --resume: keep the goal and the cycle count but start a NEW
|
|
26
|
+
# harness session (the old one hung on continue). The supervisor does
|
|
27
|
+
# this by itself after two stalled resumes in a row.
|
|
23
28
|
# --dry-run print the commands, run nothing
|
|
24
29
|
#
|
|
25
30
|
# Exit codes: 0 goal complete · 3 agent declared the goal BLOCKED (owner input
|
|
26
|
-
# needed) · 4 max cycles reached ·
|
|
31
|
+
# needed) · 4 max cycles reached · 5 the provider refuses the model (a monthly or
|
|
32
|
+
# balance limit OpenCode reports only in its own log) · 2 usage error · 130 stopped
|
|
33
|
+
# by Ctrl-C / kill (the harness child is stopped too; `--resume` continues the same session).
|
|
34
|
+
#
|
|
35
|
+
# Test-only knobs (tests/goal/run.sh): TF_GOAL_FAKE_CMD="<shell>" replaces the harness
|
|
36
|
+
# command; TF_GOAL_STALL_SEC / TF_GOAL_STALL_TICK set the stall clock in seconds.
|
|
27
37
|
#
|
|
28
38
|
# Files (all under <app-dir>/.tfcore/.session/, never committed):
|
|
29
39
|
# yolo.json YOLO flag (tf-yolo.sh on --source goal) — the hook reads it
|
|
@@ -42,19 +52,14 @@
|
|
|
42
52
|
# claude -p "<prompt>" --permission-mode bypassPermissions --output-format stream-json --verbose
|
|
43
53
|
# resume: claude -p --resume <session_id> "<continue>" (fallback: --continue)
|
|
44
54
|
# opencode run --auto "<prompt>" resume: opencode run --auto -c "<continue>"
|
|
45
|
-
# codex exec --json --sandbox workspace-write -c approval_policy="never" "<prompt>"
|
|
46
|
-
# NB: `--ask-for-approval` is NOT a `codex exec` flag (verified against
|
|
47
|
-
# codex-cli 0.149.1: "error: unexpected argument '--ask-for-approval'",
|
|
48
55
|
# exit 2). The approval policy is set as a config override instead.
|
|
49
|
-
# resume: codex exec resume <thread-id> "<continue>" --json
|
|
50
56
|
# Override command lines with TF_GOAL_CLAUDE_FLAGS / TF_GOAL_OPENCODE_FLAGS /
|
|
51
|
-
# TF_GOAL_CODEX_FLAGS.
|
|
52
57
|
|
|
53
58
|
set -u
|
|
54
59
|
|
|
55
60
|
HARNESS="claude"; MODEL=""; BUFFER_MIN=15; DEFAULT_WAIT_MIN=60; MAX_CYCLES=60; IDLE_RETRY=30
|
|
56
|
-
PROBE_MIN=15; PROBE_MAX_H=8
|
|
57
|
-
RESUME=0; DRY=0
|
|
61
|
+
PROBE_MIN=15; PROBE_MAX_H=8; STALL_MIN=15
|
|
62
|
+
RESUME=0; FRESH=0; DRY=0
|
|
58
63
|
while [[ $# -gt 0 ]]; do
|
|
59
64
|
case "$1" in
|
|
60
65
|
--harness) HARNESS="$2"; shift 2 ;;
|
|
@@ -65,7 +70,9 @@ while [[ $# -gt 0 ]]; do
|
|
|
65
70
|
--probe-max-hours) PROBE_MAX_H="$2"; shift 2 ;;
|
|
66
71
|
--max-cycles) MAX_CYCLES="$2"; shift 2 ;;
|
|
67
72
|
--idle-retry-sec) IDLE_RETRY="$2"; shift 2 ;;
|
|
73
|
+
--stall-min) STALL_MIN="$2"; shift 2 ;;
|
|
68
74
|
--resume) RESUME=1; shift ;;
|
|
75
|
+
--fresh) FRESH=1; shift ;;
|
|
69
76
|
--dry-run) DRY=1; shift ;;
|
|
70
77
|
-h|--help) sed -n '2,45p' "$0"; exit 0 ;;
|
|
71
78
|
--) shift; break ;;
|
|
@@ -74,12 +81,13 @@ while [[ $# -gt 0 ]]; do
|
|
|
74
81
|
esac
|
|
75
82
|
done
|
|
76
83
|
APP_DIR="${1:-}"; GOAL_ARG="${2:-}"
|
|
84
|
+
[[ -n "${TF_GOAL_CLASSIFY:-}" ]] && DRY=1 # the classify debug path touches no state
|
|
77
85
|
if [[ -z "$APP_DIR" || ( -z "$GOAL_ARG" && $RESUME -eq 0 ) ]]; then
|
|
78
86
|
echo "usage: tf-goal.sh [options] <app-dir> \"<goal>\" | @goal.md (or --resume <app-dir>)" >&2; exit 2
|
|
79
87
|
fi
|
|
80
88
|
APP_DIR="$(cd "$APP_DIR" 2>/dev/null && pwd)" || { echo "no such dir: $1" >&2; exit 2; }
|
|
81
89
|
[[ -d "$APP_DIR/.tfcore" ]] || { echo "$APP_DIR has no .tfcore/ — scaffold it first" >&2; exit 2; }
|
|
82
|
-
case "$HARNESS" in claude|opencode
|
|
90
|
+
case "$HARNESS" in claude|opencode) ;; *) echo "--harness must be claude|opencode" >&2; exit 2 ;; esac
|
|
83
91
|
command -v python3 >/dev/null 2>&1 || { echo "python3 is required" >&2; exit 2; }
|
|
84
92
|
|
|
85
93
|
STATE_DIR="$APP_DIR/.tfcore/.session"; mkdir -p "$STATE_DIR"
|
|
@@ -94,6 +102,9 @@ else
|
|
|
94
102
|
fi
|
|
95
103
|
|
|
96
104
|
ts() { date -u +%Y-%m-%dT%H:%M:%SZ; }
|
|
105
|
+
# nap: a sleep the INT/TERM trap can interrupt at once (bash defers a trap while a
|
|
106
|
+
# foreground command runs, so a plain `sleep 300` held a kill for up to five minutes).
|
|
107
|
+
nap() { sleep "$1" & wait $!; }
|
|
97
108
|
log() { printf '[%s] tf-goal: %s\n' "$(ts)" "$*" | tee -a "$LOG" >&2; }
|
|
98
109
|
|
|
99
110
|
state_get() { python3 -c 'import json,sys
|
|
@@ -117,18 +128,31 @@ if [[ $RESUME -eq 1 ]]; then
|
|
|
117
128
|
HARNESS="$(state_get harness)"; HARNESS="${HARNESS:-claude}"
|
|
118
129
|
CYCLE="$(state_get cycle)"; CYCLE="${CYCLE:-0}"
|
|
119
130
|
SESSION_ID="$(state_get session_id)"
|
|
120
|
-
|
|
131
|
+
STALLS="$(state_get stalls)"; STALLS="${STALLS:-0}"
|
|
132
|
+
if [[ $FRESH -eq 1 ]]; then log "resuming goal (cycle $CYCLE) in a FRESH session — the old one (${SESSION_ID:-none}) is left behind"; SESSION_ID=""; STALLS=0; state_set session_id "" stalls 0
|
|
133
|
+
else log "resuming goal (cycle $CYCLE, session ${SESSION_ID:-none})"; fi
|
|
121
134
|
else
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
135
|
+
# Refuse to start over a run that looks active in this folder: a goal.json written or
|
|
136
|
+
# touched in the last three hours whose last_reason is not a finished state. A second
|
|
137
|
+
# supervisor here would clobber the first one's state and flag (MISS-TechieFlow-20260905-21).
|
|
138
|
+
if [[ $DRY -eq 0 && -f "$STATE" ]] && [[ -n "$(find "$STATE" -mmin -180 2>/dev/null)" ]]; then
|
|
139
|
+
_lr="$(state_get last_reason)"
|
|
140
|
+
case "$_lr" in
|
|
141
|
+
done:*|max-cycles|stopped|"") ;;
|
|
142
|
+
*) echo "tf-goal: a run looks active in $APP_DIR (goal.json last_reason=$_lr, touched $(date -u -r "$STATE" +%H:%MZ)). Wait for it, or continue it with --resume." >&2; exit 2 ;;
|
|
143
|
+
esac
|
|
144
|
+
fi
|
|
145
|
+
CYCLE=0; SESSION_ID=""; STALLS=0
|
|
146
|
+
if [[ $DRY -eq 0 ]]; then # a dry run touches nothing: no state, no flag, no log line
|
|
147
|
+
state_set goal "$GOAL" harness "$HARNESS" cycle 0 session_id "" stalls 0 started "$(ts)" last_reason "start"
|
|
148
|
+
rm -f "$DONE"
|
|
149
|
+
fi
|
|
125
150
|
fi
|
|
126
151
|
|
|
127
152
|
# ---------------------------------------------------------------- prompts
|
|
128
153
|
# PREAMBLE IS HARNESS-NEUTRAL. Every claim in it must hold for claude AND opencode
|
|
129
|
-
#
|
|
130
|
-
# one harness goes in that harness's block below
|
|
131
|
-
# FIRST_PROMPT) — never in here. The 2026-08-28 Codex adapter review put Codex's
|
|
154
|
+
# because both are sent this text verbatim. Anything true of only
|
|
155
|
+
# one harness goes in that harness's block below — never in here. The 2026-08-28 review put its
|
|
132
156
|
# strict no-git policy into this shared text, which then told Claude and OpenCode
|
|
133
157
|
# goal runs to avoid read-only git that their own hook allows; nothing written down
|
|
134
158
|
# had said the preamble was shared, so this comment is that rule.
|
|
@@ -137,6 +161,7 @@ UNATTENDED GOAL RUN — YOLO MODE IS ON (TechieFlow rule .tfcore/tasks/_yolo-mod
|
|
|
137
161
|
- Nobody is watching. NEVER ask a question, NEVER pause for confirmation, NEVER end your turn with a plan, options, or "shall I…". Decide the sensible default yourself and record the decision in the checklist Remarks.
|
|
138
162
|
- Permissions: deletes are allowed when they are necessary and precisely scoped. Git WRITES (commit/push/add/reset/checkout/stash/tag) are blocked in every mode — never attempt them; the owner commits. Read-only git (status/log/diff/blame) is decided by the harness policy stated below, if any; where nothing further is stated it is available in this mode as supplementary evidence only — the checklist Requirements Status table and the working tree stay primary.
|
|
139
163
|
- Re-entry: start from PROJECT-STATUS.md + docs/*-Checklist.md (Requirements Status table) — continue from the weakest open REQ; do not redo terminal rows.
|
|
164
|
+
- A build, a test run or any long command runs in the FOREGROUND and you wait for it (a ten-minute timeout is fine). Never start it as a background job and end your turn to wait for it: the turn ending kills the job, and nobody will wake you.
|
|
140
165
|
- A build pass means the WHOLE checklist: every open REQ reaches at least `Implemented` in this pass, then the verifier is chained inline, then FIX mode loops on FAIL rows until they pass. Never stop with "run build-phase again for the remaining REQs".
|
|
141
166
|
- When the goal is met (every in-scope REQ terminal, PROJECT-STATUS.md + .html updated, run record emitted): run
|
|
142
167
|
bash .tfcore/utils/tf-yolo.sh done complete "<one-line summary>"
|
|
@@ -145,19 +170,13 @@ UNATTENDED GOAL RUN — YOLO MODE IS ON (TechieFlow rule .tfcore/tasks/_yolo-mod
|
|
|
145
170
|
Do not run either command before that point — the supervisor stops the moment you do.
|
|
146
171
|
TXT
|
|
147
172
|
|
|
148
|
-
CONTINUE_PROMPT="Continue the UNATTENDED GOAL RUN (YOLO ON). The previous turn ended without the goal-done sentinel — pick up from PROJECT-STATUS.md + the checklist Requirements Status table and keep going. Do not summarise, do not ask; work until the goal is met, then run: bash .tfcore/utils/tf-yolo.sh done complete \"<summary>\". The goal, again:
|
|
173
|
+
CONTINUE_PROMPT="Continue the UNATTENDED GOAL RUN (YOLO ON). The previous turn ended without the goal-done sentinel — pick up from PROJECT-STATUS.md + the checklist Requirements Status table and keep going. Do not summarise, do not ask; work until the goal is met, then run: bash .tfcore/utils/tf-yolo.sh done complete \"<summary>\". Builds and tests run in the FOREGROUND and you wait for them; never end the turn to wait for a background job (the turn ending kills it). The goal, again:
|
|
149
174
|
$GOAL"
|
|
150
175
|
|
|
151
176
|
FIRST_PROMPT="$PREAMBLE
|
|
152
177
|
|
|
153
178
|
THE GOAL:
|
|
154
179
|
$GOAL"
|
|
155
|
-
if [[ "$HARNESS" == codex ]]; then
|
|
156
|
-
FIRST_PROMPT="$FIRST_PROMPT
|
|
157
|
-
|
|
158
|
-
CODEX POLICY NOTE: \`.codex/rules/techieflow.rules\` forbids every git/gh command even in YOLO mode, read-only diagnostics included. Use working-tree files and framework artifacts; do not attempt read-only git."
|
|
159
|
-
fi
|
|
160
|
-
|
|
161
180
|
# ---------------------------------------------------------------- harness command
|
|
162
181
|
harness_cmd() { # $1 = first|resume ; prints the argv via NUL-separated echo
|
|
163
182
|
local kind="$1" prompt
|
|
@@ -181,19 +200,12 @@ harness_cmd() { # $1 = first|resume ; prints the argv via NUL-separated echo
|
|
|
181
200
|
fi
|
|
182
201
|
CMD+=("$prompt")
|
|
183
202
|
else
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
[[ -n "$MODEL" ]] && CMD+=(-m "$MODEL")
|
|
189
|
-
local tier effort
|
|
190
|
-
tier="$(bash "$APP_DIR/.tfcore/utils/tf-harness.sh" tier build-phase)"
|
|
191
|
-
effort="$(bash "$APP_DIR/.tfcore/utils/tf-harness.sh" effort "$tier")"
|
|
192
|
-
[[ -n "$effort" ]] && CMD+=(-c "model_reasoning_effort=\"$effort\"")
|
|
193
|
-
# shellcheck disable=SC2206
|
|
194
|
-
[[ -n "${TF_GOAL_CODEX_FLAGS:-}" ]] && CMD+=($TF_GOAL_CODEX_FLAGS)
|
|
195
|
-
CMD+=("$prompt")
|
|
203
|
+
CMD=(opencode run --auto)
|
|
204
|
+
[[ -n "$MODEL" ]] && CMD+=(-m "$MODEL")
|
|
205
|
+
if [[ "$kind" == resume ]]; then
|
|
206
|
+
if [[ -n "$SESSION_ID" ]]; then CMD+=(-s "$SESSION_ID"); else CMD+=(-c); fi
|
|
196
207
|
fi
|
|
208
|
+
CMD+=("$prompt")
|
|
197
209
|
fi
|
|
198
210
|
}
|
|
199
211
|
|
|
@@ -220,9 +232,35 @@ now = time.time()
|
|
|
220
232
|
def out(kind, detail):
|
|
221
233
|
print(f"{kind}\t{detail}"); sys.exit(0)
|
|
222
234
|
|
|
235
|
+
# ---- 0. Claude Code stream-json: the last `result` line says how the turn ended, and a
|
|
236
|
+
# clean one (is_error false) is a STOP, never a crash. Until 2026-09-06 the crash regex
|
|
237
|
+
# below matched the harmless field "api_error_status":null that every clean result carries,
|
|
238
|
+
# so every early stop was called a harness error and backed off 2m, 4m, 8m … instead of
|
|
239
|
+
# being re-prompted after 30s (MISS-TechieFlow-20260905-23, MISS-TechieFlow-20260906-01).
|
|
240
|
+
last_result = None
|
|
241
|
+
for line in text.splitlines():
|
|
242
|
+
line = line.strip()
|
|
243
|
+
if not line.startswith("{") or '"result"' not in line:
|
|
244
|
+
continue
|
|
245
|
+
try:
|
|
246
|
+
d = json.loads(line)
|
|
247
|
+
except Exception:
|
|
248
|
+
continue
|
|
249
|
+
if isinstance(d, dict) and d.get("type") == "result":
|
|
250
|
+
last_result = d
|
|
251
|
+
if last_result is not None and rc == 0 and not last_result.get("is_error"):
|
|
252
|
+
out("IDLE", "clean stop: result is_error=false, %s turn(s)" % last_result.get("num_turns", "?"))
|
|
253
|
+
# a rejected rate_limit_event carries the exact reset epoch — better than parsing "9am"
|
|
254
|
+
if last_result is not None and last_result.get("is_error"):
|
|
255
|
+
ev = re.findall(r'"rate_limit_info"\s*:\s*\{[^{}]*"status"\s*:\s*"rejected"[^{}]*"resetsAt"\s*:\s*(\d{10})', text)
|
|
256
|
+
if not ev:
|
|
257
|
+
ev = re.findall(r'"status"\s*:\s*"rejected"[^{}]*?"resetsAt"\s*:\s*(\d{10})', text)
|
|
258
|
+
if ev:
|
|
259
|
+
out("LIMIT", f"{int(ev[-1]) + buffer_min*60}\tparsed")
|
|
260
|
+
|
|
223
261
|
# ---- 1. usage limit?
|
|
224
262
|
LIMIT_PAT = re.compile(
|
|
225
|
-
r"(usage limit|hit your limit|exceeded your .*limit|you've hit your (5-hour|weekly|usage) limit|rate[ _-]?limit(ed)
|
|
263
|
+
r"(usage limit|hit your limit|exceeded your .*limit|you've hit your (5-hour|weekly|usage) limit|rate[ _-]?limit(ed)?(?!_event)|"
|
|
226
264
|
r"limit (has been )?(reached|exceeded)|too many requests|\b429\b|overloaded_error|quota exceeded|"
|
|
227
265
|
r"out of extra usage|resets? (at|in)\b|weekly limit|session limit)", re.I)
|
|
228
266
|
m = LIMIT_PAT.search(tail)
|
|
@@ -280,7 +318,7 @@ if m:
|
|
|
280
318
|
out("LIMIT", f"{int(resume_at)}\tparsed")
|
|
281
319
|
|
|
282
320
|
# ---- 2. a crash / API error / auth problem?
|
|
283
|
-
if rc != 0 or re.search(r"(api_error|internal server error|\b5\d\d\b .*error|ECONNRESET|ETIMEDOUT|ENOTFOUND|socket hang up|"
|
|
321
|
+
if rc != 0 or re.search(r"(api_error(?!_status)|internal server error|\b5\d\d\b .*error|ECONNRESET|ETIMEDOUT|ENOTFOUND|socket hang up|"
|
|
284
322
|
r"authentication_error|invalid api key|not logged in|please run /login|error_during_execution|"
|
|
285
323
|
r"\"is_error\"\s*:\s*true|Error: .*(fetch|network|connect))", tail, re.I):
|
|
286
324
|
out("CRASH", f"rc={rc}")
|
|
@@ -290,7 +328,7 @@ out("IDLE", "no sentinel")
|
|
|
290
328
|
PY
|
|
291
329
|
}
|
|
292
330
|
|
|
293
|
-
extract_session_id() { # from a cycle's output file (Claude/OpenCode
|
|
331
|
+
extract_session_id() { # from a cycle's output file (Claude/OpenCode JSONL)
|
|
294
332
|
python3 - "$1" <<'PY' 2>/dev/null
|
|
295
333
|
import sys, json, re
|
|
296
334
|
sid = ""
|
|
@@ -318,10 +356,8 @@ probe_until_clear() {
|
|
|
318
356
|
n=$(( n + 1 ))
|
|
319
357
|
if [[ "$HARNESS" == claude ]]; then
|
|
320
358
|
( cd "$APP_DIR" && claude -p --max-turns 1 --output-format text "Reply with the single word OK." ) > "$pout" 2>&1; prc=$?
|
|
321
|
-
elif [[ "$HARNESS" == opencode ]]; then
|
|
322
|
-
( cd "$APP_DIR" && opencode run --auto "Reply with the single word OK." ) > "$pout" 2>&1; prc=$?
|
|
323
359
|
else
|
|
324
|
-
( cd "$APP_DIR" &&
|
|
360
|
+
( cd "$APP_DIR" && opencode run --auto "Reply with the single word OK." ) > "$pout" 2>&1; prc=$?
|
|
325
361
|
fi
|
|
326
362
|
if [[ $prc -eq 0 ]] && ! grep -qiE 'usage limit|hit your limit|rate[ _-]?limit|limit (has been )?(reached|exceeded)|too many requests|\b429\b|overloaded|weekly limit|resets? (at|in)\b' "$pout"; then
|
|
327
363
|
log "probe #$n OK"; return 0
|
|
@@ -329,7 +365,7 @@ probe_until_clear() {
|
|
|
329
365
|
log "probe #$n still limited (rc=$prc: $(head -c 120 "$pout" | tr '\n' ' ')) — next in ${PROBE_MIN}m"
|
|
330
366
|
state_set resume_at "probe #$((n+1)) at $(date -d "+${PROBE_MIN} min" '+%H:%M' 2>/dev/null)"
|
|
331
367
|
if [[ $(date +%s) -ge $deadline ]]; then log "probe window (${PROBE_MAX_H}h) exhausted — resuming anyway"; return 1; fi
|
|
332
|
-
|
|
368
|
+
nap $(( PROBE_MIN * 60 ))
|
|
333
369
|
done
|
|
334
370
|
}
|
|
335
371
|
|
|
@@ -338,18 +374,95 @@ sleep_until() { # epoch
|
|
|
338
374
|
while :; do
|
|
339
375
|
now=$(date +%s); left=$(( target - now )); [[ $left -le 0 ]] && break
|
|
340
376
|
state_set resume_at "$(date -u -d "@$target" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || echo "$target")"
|
|
341
|
-
if [[ $left -gt 300 ]]; then
|
|
377
|
+
if [[ $left -gt 300 ]]; then nap 300; else nap "$left"; fi
|
|
342
378
|
done
|
|
343
379
|
state_set resume_at ""
|
|
344
380
|
}
|
|
345
381
|
|
|
346
|
-
# Debug: `
|
|
382
|
+
# Debug: `TF_GOAL_CLASSIFY=<output-file> [TF_GOAL_CLASSIFY_RC=n] tf-goal.sh <app-dir> x` prints the classification and exits.
|
|
347
383
|
if [[ -n "${TF_GOAL_CLASSIFY:-}" ]]; then classify_output "$TF_GOAL_CLASSIFY" "${TF_GOAL_CLASSIFY_RC:-0}"; exit 0; fi
|
|
348
384
|
|
|
385
|
+
# ---------------------------------------------------------------- the harness child
|
|
386
|
+
# The harness runs as a background child in its own process group, so the supervisor can
|
|
387
|
+
# (a) watch its output file grow and kill it after STALL_MIN silent minutes — the OpenCode
|
|
388
|
+
# run that printed nothing for 43 minutes while the supervisor waited forever
|
|
389
|
+
# (MISS-TechieFlow-20260905-22) — and (b) take the child down with it on Ctrl-C / kill,
|
|
390
|
+
# instead of leaving an orphaned agent working in the folder (the previous trap only
|
|
391
|
+
# cleared the YOLO flag and did not even exit, so a killed supervisor carried on to the
|
|
392
|
+
# next cycle: MISS-TechieFlow-20260906-02).
|
|
393
|
+
CHILD=""; STALLED=0
|
|
394
|
+
STALL_SEC="${TF_GOAL_STALL_SEC:-$(( STALL_MIN * 60 ))}"; STALL_TICK="${TF_GOAL_STALL_TICK:-30}"
|
|
395
|
+
kill_child() {
|
|
396
|
+
[[ -n "$CHILD" ]] || return 0
|
|
397
|
+
kill -0 "$CHILD" 2>/dev/null || { CHILD=""; return 0; }
|
|
398
|
+
kill -TERM -- "-$CHILD" 2>/dev/null || kill -TERM "$CHILD" 2>/dev/null || true
|
|
399
|
+
local i=0
|
|
400
|
+
while kill -0 "$CHILD" 2>/dev/null && [[ $i -lt 10 ]]; do sleep 1; i=$((i+1)); done
|
|
401
|
+
kill -KILL -- "-$CHILD" 2>/dev/null || kill -KILL "$CHILD" 2>/dev/null || true
|
|
402
|
+
CHILD=""
|
|
403
|
+
}
|
|
404
|
+
run_cycle() { # runs "${CMD[@]}" in $APP_DIR, output to $OUT and $LOG; sets RC, STALLED
|
|
405
|
+
STALLED=0
|
|
406
|
+
local quiet=0 prev=-1 size
|
|
407
|
+
if [[ -n "${TF_GOAL_FAKE_CMD:-}" ]]; then CMD=(bash -c "$TF_GOAL_FAKE_CMD"); fi
|
|
408
|
+
if command -v setsid >/dev/null 2>&1; then
|
|
409
|
+
( cd "$APP_DIR" && TF_YOLO=1 exec setsid "${CMD[@]}" ) > >(tee -a "$LOG" > "$OUT") 2>&1 &
|
|
410
|
+
else
|
|
411
|
+
( cd "$APP_DIR" && TF_YOLO=1 exec "${CMD[@]}" ) > >(tee -a "$LOG" > "$OUT") 2>&1 &
|
|
412
|
+
fi
|
|
413
|
+
CHILD=$!
|
|
414
|
+
while kill -0 "$CHILD" 2>/dev/null; do
|
|
415
|
+
nap "$STALL_TICK"
|
|
416
|
+
kill -0 "$CHILD" 2>/dev/null || break
|
|
417
|
+
size="$(stat -c %s "$OUT" 2>/dev/null || echo 0)"
|
|
418
|
+
if [[ "$size" == "$prev" ]]; then quiet=$(( quiet + STALL_TICK )); else quiet=0; prev="$size"; fi
|
|
419
|
+
if [[ "$STALL_SEC" -gt 0 && $quiet -ge "$STALL_SEC" ]]; then
|
|
420
|
+
log "STALL: no output for $(( quiet / 60 ))m$(( quiet % 60 ))s (cycle $CYCLE, ${size} bytes) — stopping the harness, re-prompting"
|
|
421
|
+
kill_child; STALLED=1; break
|
|
422
|
+
fi
|
|
423
|
+
done
|
|
424
|
+
wait "$CHILD" 2>/dev/null; RC=$?; CHILD=""
|
|
425
|
+
sleep 1 # let tee flush
|
|
426
|
+
}
|
|
427
|
+
# OpenCode prints NOTHING on a provider refusal — `opencode run` shows its header and waits
|
|
428
|
+
# while ~/.local/share/opencode/log/opencode.log says "Monthly usage limit reached. Resets in
|
|
429
|
+
# 6 days" (2026-09-06, three silent 15-minute stalls on MyDiary-oc; MISS-TechieFlow-20260906-11).
|
|
430
|
+
# After a stall the supervisor reads that log for stream errors newer than the cycle start.
|
|
431
|
+
OPENCODE_LOG="${TF_GOAL_OPENCODE_LOG:-$HOME/.local/share/opencode/log/opencode.log}"
|
|
432
|
+
opencode_log_error() { # $1 = cycle start epoch; prints the newest provider error text since then, or nothing
|
|
433
|
+
[[ "$HARNESS" == opencode && -f "$OPENCODE_LOG" ]] || return 0
|
|
434
|
+
python3 - "$OPENCODE_LOG" "$1" <<'PY2' 2>/dev/null
|
|
435
|
+
import re, sys, datetime
|
|
436
|
+
path, since = sys.argv[1], float(sys.argv[2])
|
|
437
|
+
last = ""
|
|
438
|
+
try:
|
|
439
|
+
for line in open(path, errors="replace"):
|
|
440
|
+
if "stream error" not in line: continue
|
|
441
|
+
m = re.search(r"timestamp=(\S+)", line)
|
|
442
|
+
try:
|
|
443
|
+
ts = datetime.datetime.fromisoformat(m.group(1).replace("Z", "+00:00")).timestamp()
|
|
444
|
+
except Exception:
|
|
445
|
+
continue
|
|
446
|
+
if ts < since: continue
|
|
447
|
+
e = re.search(r'error\.error="([^"]*)"', line)
|
|
448
|
+
last = (e.group(1) if e else line.strip())[:300]
|
|
449
|
+
except Exception:
|
|
450
|
+
pass
|
|
451
|
+
print(last)
|
|
452
|
+
PY2
|
|
453
|
+
}
|
|
454
|
+
on_signal() {
|
|
455
|
+
trap - INT TERM
|
|
456
|
+
log "supervisor stopped by signal (cycle $CYCLE) — stopping the harness child; continue with --resume"
|
|
457
|
+
kill_child
|
|
458
|
+
state_set last_reason "stopped" ended "$(ts)"
|
|
459
|
+
exit 130
|
|
460
|
+
}
|
|
461
|
+
|
|
349
462
|
# ---------------------------------------------------------------- main loop
|
|
350
463
|
export TF_YOLO=1
|
|
351
464
|
# Pin the state dir to the APP (not the caller's cwd / a parent repo's CLAUDE_PROJECT_DIR).
|
|
352
|
-
CLAUDE_PROJECT_DIR="$APP_DIR" TF_PROJECT_DIR="$APP_DIR" bash "$YOLO_SH" on --source goal --goal "$GOAL" >/dev/null 2>&1 || true
|
|
465
|
+
[[ $DRY -eq 0 ]] && { CLAUDE_PROJECT_DIR="$APP_DIR" TF_PROJECT_DIR="$APP_DIR" bash "$YOLO_SH" on --source goal --goal "$GOAL" >/dev/null 2>&1 || true; }
|
|
353
466
|
|
|
354
467
|
# The flag we just wrote is OURS — clear it on EVERY exit path (goal done, max
|
|
355
468
|
# cycles, Ctrl-C, kill). Without this it survives the run and silently puts every
|
|
@@ -362,9 +475,9 @@ clear_goal_yolo() {
|
|
|
362
475
|
grep -q '"source":[[:space:]]*"goal"' "$f" 2>/dev/null || return 0
|
|
363
476
|
CLAUDE_PROJECT_DIR="$APP_DIR" TF_PROJECT_DIR="$APP_DIR" bash "$YOLO_SH" off >/dev/null 2>&1 || true
|
|
364
477
|
}
|
|
365
|
-
trap clear_goal_yolo EXIT INT TERM
|
|
478
|
+
[[ $DRY -eq 0 ]] && { trap clear_goal_yolo EXIT; trap on_signal INT TERM; }
|
|
366
479
|
BACKOFF=120
|
|
367
|
-
KIND=first; [[ $RESUME -eq 1 && $CYCLE -gt 0 ]] && KIND=resume
|
|
480
|
+
KIND=first; [[ $RESUME -eq 1 && $CYCLE -gt 0 && $FRESH -eq 0 ]] && KIND=resume
|
|
368
481
|
|
|
369
482
|
while :; do
|
|
370
483
|
if [[ -f "$DONE" ]]; then
|
|
@@ -374,24 +487,42 @@ while :; do
|
|
|
374
487
|
[[ "$OUTCOME" == blocked ]] && exit 3 || exit 0
|
|
375
488
|
fi
|
|
376
489
|
if [[ $CYCLE -ge $MAX_CYCLES ]]; then log "max cycles ($MAX_CYCLES) reached — stopping"; state_set last_reason "max-cycles"; exit 4; fi
|
|
377
|
-
CYCLE=$((CYCLE + 1))
|
|
490
|
+
CYCLE=$((CYCLE + 1))
|
|
378
491
|
harness_cmd "$KIND"
|
|
379
492
|
OUT="$STATE_DIR/goal-cycle-$CYCLE.out"
|
|
493
|
+
if [[ $DRY -eq 1 ]]; then echo "dry run — would launch cycle $CYCLE ($KIND):" >&2; printf ' %q' "${CMD[@]}"; echo; exit 0; fi
|
|
494
|
+
state_set cycle "$CYCLE"; CYCLE_START="$(date +%s)"
|
|
380
495
|
log "cycle $CYCLE ($KIND) → ${CMD[*]:0:6} … (prompt ${#CMD[-1]} chars)"
|
|
381
|
-
if [[ $DRY -eq 1 ]]; then printf ' %q' "${CMD[@]}"; echo; exit 0; fi
|
|
382
496
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
497
|
+
# The run's start is now, not when the agent reaches step 0: write an unclaimed marker the
|
|
498
|
+
# first command's `tf-phase.sh start` claims (MISS-TechieFlow-20260905-20).
|
|
499
|
+
[[ $CYCLE -eq 1 && -f "$APP_DIR/.tfcore/utils/tf-phase.sh" ]] && bash "$APP_DIR/.tfcore/utils/tf-phase.sh" goal "$(basename "$APP_DIR")" >/dev/null 2>&1 || true
|
|
500
|
+
run_cycle
|
|
386
501
|
SID="$(extract_session_id "$OUT")"; [[ -n "$SID" ]] && { SESSION_ID="$SID"; state_set session_id "$SID"; }
|
|
387
|
-
if [[ "$HARNESS" == codex ]]; then
|
|
388
|
-
python3 "$APP_DIR/.tfcore/utils/tf-codex-telemetry.py" "$APP_DIR" "$OUT" || true
|
|
389
|
-
fi
|
|
390
502
|
KIND=resume
|
|
391
503
|
|
|
392
504
|
if [[ -f "$DONE" ]]; then continue; fi
|
|
393
505
|
|
|
394
|
-
|
|
506
|
+
if [[ $STALLED -eq 1 ]]; then
|
|
507
|
+
CLASS=IDLE; DETAIL="stalled ${STALL_MIN}m"; HOW=""
|
|
508
|
+
PERR="$(opencode_log_error "$CYCLE_START")"
|
|
509
|
+
if [[ -n "$PERR" ]] && grep -qiE "usage limit|monthly|balance|quota|rate limit|429|insufficient|billing|unauthorized|api key" <<<"$PERR"; then
|
|
510
|
+
log "the provider refused the model while the harness printed nothing (cycle $CYCLE): $PERR — the owner must act (enable balance, wait for the reset, or pick another model); stopping"
|
|
511
|
+
state_set last_reason "provider-limit" ended "$(ts)"
|
|
512
|
+
exit 5
|
|
513
|
+
fi
|
|
514
|
+
STALLS=$(( STALLS + 1 )); state_set stalls "$STALLS"
|
|
515
|
+
# A resumed session that hangs twice running is not coming back (OpenCode `-c` printed its
|
|
516
|
+
# header and nothing else for 15 minutes, twice, on 2026-09-06): the next cycle starts a
|
|
517
|
+
# fresh session with the full goal; the checklist and PROJECT-STATUS carry the state.
|
|
518
|
+
if [[ "$KIND" == resume && $STALLS -ge 2 ]]; then
|
|
519
|
+
log "two stalled resumes in a row — the next cycle starts a FRESH session (the old one, ${SESSION_ID:-none}, is left behind)"
|
|
520
|
+
KIND=first; SESSION_ID=""; STALLS=0; state_set session_id "" stalls 0
|
|
521
|
+
fi
|
|
522
|
+
else
|
|
523
|
+
IFS=$'\t' read -r CLASS DETAIL HOW < <(classify_output "$OUT" "$RC")
|
|
524
|
+
STALLS=0; state_set stalls 0
|
|
525
|
+
fi
|
|
395
526
|
case "$CLASS" in
|
|
396
527
|
LIMIT)
|
|
397
528
|
if [[ "$HOW" == probe ]]; then
|
|
@@ -400,7 +531,7 @@ while :; do
|
|
|
400
531
|
state_set last_reason "limit-probe" resume_at "probing every ${PROBE_MIN}m"
|
|
401
532
|
probe_until_clear
|
|
402
533
|
log "probe succeeded — resuming session ${SESSION_ID:-(--continue)} after a ${BUFFER_MIN}m buffer"
|
|
403
|
-
|
|
534
|
+
nap $(( BUFFER_MIN * 60 ))
|
|
404
535
|
else
|
|
405
536
|
WHEN="$(date -d "@$DETAIL" '+%Y-%m-%d %H:%M %Z' 2>/dev/null || echo "$DETAIL")"
|
|
406
537
|
log "USAGE LIMIT hit (cycle $CYCLE) — RETRY AT $WHEN (stated reset + ${BUFFER_MIN}m buffer). Sleeping."
|
|
@@ -412,10 +543,10 @@ while :; do
|
|
|
412
543
|
CRASH)
|
|
413
544
|
log "harness/API error (cycle $CYCLE, $DETAIL) — backing off ${BACKOFF}s then resuming"
|
|
414
545
|
state_set last_reason "crash:$DETAIL"
|
|
415
|
-
|
|
546
|
+
nap "$BACKOFF"; BACKOFF=$(( BACKOFF * 2 )); [[ $BACKOFF -gt 1800 ]] && BACKOFF=1800 ;;
|
|
416
547
|
IDLE|*)
|
|
417
|
-
log "agent stopped without the goal-done sentinel (cycle $CYCLE) — re-prompting in ${IDLE_RETRY}s"
|
|
548
|
+
log "agent stopped without the goal-done sentinel (cycle $CYCLE, ${DETAIL:-no sentinel}) — re-prompting in ${IDLE_RETRY}s"
|
|
418
549
|
state_set last_reason "idle"
|
|
419
|
-
|
|
550
|
+
nap "$IDLE_RETRY"; BACKOFF=120 ;;
|
|
420
551
|
esac
|
|
421
552
|
done
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
# scripts call this instead of hard-coding either harness's dialect.
|
|
7
7
|
#
|
|
8
8
|
# USAGE
|
|
9
|
-
# tf-harness.sh detect -> claude-code | opencode |
|
|
9
|
+
# tf-harness.sh detect -> claude-code | opencode | unknown
|
|
10
10
|
# tf-harness.sh root -> project root path
|
|
11
11
|
# tf-harness.sh enabled -> true | false (routing.yaml flag)
|
|
12
12
|
# tf-harness.sh tier <phase|subagent> -> frontier | standard | economy | inherit
|
|
@@ -42,8 +42,7 @@ _root() {
|
|
|
42
42
|
|
|
43
43
|
# --- detect ----------------------------------------------------------------
|
|
44
44
|
_detect() {
|
|
45
|
-
case "${TF_HARNESS:-}" in claude-code|opencode
|
|
46
|
-
if [[ -n "${CODEX_THREAD_ID:-}${CODEX_SESSION_ID:-}" ]]; then printf 'codex'; return; fi
|
|
45
|
+
case "${TF_HARNESS:-}" in claude-code|opencode) printf '%s' "$TF_HARNESS"; return ;; esac
|
|
47
46
|
if [[ -n "${CLAUDECODE:-}${CLAUDE_CODE_ENTRYPOINT:-}${CLAUDE_CODE_SESSION_ID:-}${CLAUDE_PROJECT_DIR:-}" ]]; then
|
|
48
47
|
printf 'claude-code'; return
|
|
49
48
|
fi
|
|
@@ -58,7 +57,6 @@ _detect() {
|
|
|
58
57
|
ppid="$(printf '%s' "${stat##*) }" | awk '{print $2}')"
|
|
59
58
|
case "$name" in
|
|
60
59
|
*opencode*) printf 'opencode'; return ;;
|
|
61
|
-
*codex*) printf 'codex'; return ;;
|
|
62
60
|
*claude*) printf 'claude-code'; return ;;
|
|
63
61
|
esac
|
|
64
62
|
pid="$ppid"
|
|
@@ -92,7 +90,6 @@ _model() {
|
|
|
92
90
|
[[ -f "$f" ]] || { printf 'inherit'; return; }
|
|
93
91
|
local key="claude"
|
|
94
92
|
[[ "$harness" == "opencode" ]] && key="opencode"
|
|
95
|
-
[[ "$harness" == "codex" ]] && key="codex"
|
|
96
93
|
awk -v tier="$tier" -v key="$key" '
|
|
97
94
|
/^tiers:/ { in_tiers=1; next }
|
|
98
95
|
/^[a-z]+:/ { in_tiers=0 }
|
|
@@ -135,13 +132,11 @@ case "$CMD" in
|
|
|
135
132
|
H="$(_detect)"
|
|
136
133
|
case "$KIND" in
|
|
137
134
|
task)
|
|
138
|
-
if [[ "$H" == "
|
|
139
|
-
elif [[ "$H" == "opencode" ]]; then printf '/techieflow:tasks:%s %s\n' "$NAME" "$ARGS"
|
|
135
|
+
if [[ "$H" == "opencode" ]]; then printf '/techieflow:tasks:%s %s\n' "$NAME" "$ARGS"
|
|
140
136
|
else printf '/TechieFlow:tasks:%s %s\n' "$NAME" "$ARGS"; fi
|
|
141
137
|
;;
|
|
142
138
|
agent)
|
|
143
|
-
if [[ "$H" == "
|
|
144
|
-
elif [[ "$H" == "opencode" ]]; then printf 'opencode run --agent %s "%s" (TUI: Tab to %s, then type: %s)\n' "$NAME" "$ARGS" "$NAME" "$ARGS"
|
|
139
|
+
if [[ "$H" == "opencode" ]]; then printf 'opencode run --agent %s "%s" (TUI: Tab to %s, then type: %s)\n' "$NAME" "$ARGS" "$NAME" "$ARGS"
|
|
145
140
|
else printf '/TechieFlow:agents:%s %s\n' "$NAME" "$ARGS"; fi
|
|
146
141
|
;;
|
|
147
142
|
*) echo "usage: tf-harness.sh invoke <task|agent> <name> [args]" >&2; exit 2 ;;
|