@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.
Files changed (178) hide show
  1. package/.claude/commands/TechieFlow/agents/analyst.md +0 -5
  2. package/.claude/commands/TechieFlow/agents/architect.md +0 -6
  3. package/.claude/commands/TechieFlow/agents/flow-master.md +41 -215
  4. package/.claude/commands/TechieFlow/agents/verifier.md +23 -64
  5. package/.claude/commands/TechieFlow/tasks/_metrics-emit-gate.md +24 -117
  6. package/.claude/commands/TechieFlow/tasks/_smoke-test-policy.md +20 -84
  7. package/.claude/commands/TechieFlow/tasks/_status-update-gate.md +10 -74
  8. package/.claude/commands/TechieFlow/tasks/_yolo-mode.md +20 -58
  9. package/.claude/commands/TechieFlow/tasks/amend-docs.md +20 -143
  10. package/.claude/commands/TechieFlow/tasks/build-phase.md +14 -224
  11. package/.claude/commands/TechieFlow/tasks/day1-brownfield.md +31 -505
  12. package/.claude/commands/TechieFlow/tasks/day1-greenfield.md +28 -213
  13. package/.claude/commands/TechieFlow/tasks/deploy-checklist.md +19 -0
  14. package/.claude/commands/TechieFlow/tasks/devguide.md +10 -219
  15. package/.claude/commands/TechieFlow/tasks/fix-issues.md +10 -129
  16. package/.claude/commands/TechieFlow/tasks/log-miss.md +19 -158
  17. package/.claude/commands/TechieFlow/tasks/metrics-report.md +1 -1
  18. package/.claude/commands/TechieFlow/tasks/mockups.md +12 -117
  19. package/.claude/commands/TechieFlow/tasks/refresh-status.md +9 -130
  20. package/.claude/commands/TechieFlow/tasks/split-brd.md +10 -126
  21. package/.claude/commands/TechieFlow/tasks/triage-and-fix.md +14 -0
  22. package/.claude/commands/TechieFlow/tasks/triage-issues.md +11 -152
  23. package/.claude/commands/TechieFlow/tasks/verify-phase.md +36 -572
  24. package/.opencode/plugin/techieflow.js +39 -12
  25. package/.tfcore/agents/analyst.md +0 -5
  26. package/.tfcore/agents/architect.md +0 -6
  27. package/.tfcore/agents/flow-master.md +41 -215
  28. package/.tfcore/agents/verifier.md +23 -64
  29. package/.tfcore/core-config.yaml +10 -0
  30. package/.tfcore/hooks/block-git.sh +4 -4
  31. package/.tfcore/hooks/guard-artifacts.sh +2 -2
  32. package/.tfcore/hooks/guard-build.sh +76 -0
  33. package/.tfcore/hooks/guard-db.sh +90 -0
  34. package/.tfcore/hooks/guard-metrics.sh +56 -0
  35. package/.tfcore/hooks/guard-status-html.sh +116 -71
  36. package/.tfcore/hooks/guard-status.sh +43 -16
  37. package/.tfcore/hooks/guard-verify.sh +70 -48
  38. package/.tfcore/hooks/sweep-artifacts.sh +2 -2
  39. package/.tfcore/install-manifest.yaml +0 -18
  40. package/.tfcore/routing.yaml +25 -14
  41. package/.tfcore/standards/coding-standards-core.md +61 -0
  42. package/.tfcore/standards/coding-standards-dotnet.md +93 -0
  43. package/.tfcore/tasks/_metrics-emit-gate.md +24 -117
  44. package/.tfcore/tasks/_smoke-test-policy.md +20 -84
  45. package/.tfcore/tasks/_status-update-gate.md +10 -74
  46. package/.tfcore/tasks/_yolo-mode.md +20 -58
  47. package/.tfcore/tasks/amend-docs.md +20 -143
  48. package/.tfcore/tasks/build-phase.md +14 -224
  49. package/.tfcore/tasks/day1-brownfield.md +31 -505
  50. package/.tfcore/tasks/day1-greenfield.md +28 -213
  51. package/.tfcore/tasks/deploy-checklist.md +19 -0
  52. package/.tfcore/tasks/devguide.md +10 -219
  53. package/.tfcore/tasks/fix-issues.md +10 -129
  54. package/.tfcore/tasks/log-miss.md +19 -158
  55. package/.tfcore/tasks/metrics-report.md +1 -1
  56. package/.tfcore/tasks/mockups.md +12 -117
  57. package/.tfcore/tasks/refresh-status.md +9 -130
  58. package/.tfcore/tasks/split-brd.md +10 -126
  59. package/.tfcore/tasks/triage-and-fix.md +14 -0
  60. package/.tfcore/tasks/triage-issues.md +11 -152
  61. package/.tfcore/tasks/verify-phase.md +36 -572
  62. package/.tfcore/telemetry/SCHEMA.md +43 -7
  63. package/.tfcore/telemetry/tf-metrics.sh +152 -17
  64. package/.tfcore/templates/project-brief-tmpl.yaml +1 -1
  65. package/.tfcore/templates/stack-defaults/dotnet.md +35 -0
  66. package/.tfcore/templates/stack-questions.md +77 -0
  67. package/.tfcore/templates/v4custom/app-agents-md-tmpl.md +3 -4
  68. package/.tfcore/templates/v4custom/app-architecture-tmpl.md +107 -115
  69. package/.tfcore/templates/v4custom/app-brd-tmpl.md +108 -184
  70. package/.tfcore/templates/v4custom/app-checklist-tmpl.md +48 -73
  71. package/.tfcore/templates/v4custom/app-claude-md-tmpl.md +1 -1
  72. package/.tfcore/templates/v4custom/app-coding-standards-tmpl.md +52 -0
  73. package/.tfcore/templates/v4custom/app-deployment-checklist-tmpl.md +86 -0
  74. package/.tfcore/templates/v4custom/app-devguide-tmpl.md +69 -146
  75. package/.tfcore/templates/v4custom/app-phases-tmpl.md +35 -0
  76. package/.tfcore/templates/v4custom/app-productguide-tmpl.md +38 -35
  77. package/.tfcore/templates/v4custom/app-project-status-tmpl.md +63 -73
  78. package/.tfcore/templates/v4custom/app-uidesign-tmpl.md +62 -37
  79. package/.tfcore/templates/v4custom/app-usageguide-tmpl.md +69 -54
  80. package/.tfcore/templates/v4custom/build-invocation-ladder.md +17 -180
  81. package/.tfcore/templates/v4custom/build-subagent-prompt.md +17 -0
  82. package/.tfcore/templates/v4custom/metrics-report-template.md +2 -2
  83. package/.tfcore/user-guide.md +0 -23
  84. package/.tfcore/utils/__pycache__/tf-brd-status.cpython-312.pyc +0 -0
  85. package/.tfcore/utils/__pycache__/tf-checklist-edit.cpython-312.pyc +0 -0
  86. package/.tfcore/utils/__pycache__/tf-codex-bind.cpython-312.pyc +0 -0
  87. package/.tfcore/utils/__pycache__/tf-codex-telemetry.cpython-312.pyc +0 -0
  88. package/.tfcore/utils/__pycache__/tf-day1-files.cpython-312.pyc +0 -0
  89. package/.tfcore/utils/__pycache__/tf-devguide-list.cpython-312.pyc +0 -0
  90. package/.tfcore/utils/__pycache__/tf-doc-check.cpython-312.pyc +0 -0
  91. package/.tfcore/utils/__pycache__/tf-log-miss.cpython-312.pyc +0 -0
  92. package/.tfcore/utils/__pycache__/tf-misses-md.cpython-312.pyc +0 -0
  93. package/.tfcore/utils/__pycache__/tf-mockups-locate.cpython-312.pyc +0 -0
  94. package/.tfcore/utils/__pycache__/tf-opencode-audit.cpython-312.pyc +0 -0
  95. package/.tfcore/utils/__pycache__/tf-perf-grade.cpython-312.pyc +0 -0
  96. package/.tfcore/utils/__pycache__/tf-render-html.cpython-312.pyc +0 -0
  97. package/.tfcore/utils/__pycache__/tf-split-brd.cpython-312.pyc +0 -0
  98. package/.tfcore/utils/__pycache__/tf-status-facts.cpython-312.pyc +0 -0
  99. package/.tfcore/utils/__pycache__/tf-triage.cpython-312.pyc +0 -0
  100. package/.tfcore/utils/__pycache__/tf-verify-list.cpython-312.pyc +0 -0
  101. package/.tfcore/utils/__pycache__/tf-verify-verdict.cpython-312.pyc +0 -0
  102. package/.tfcore/utils/tf-brd-status.py +176 -0
  103. package/.tfcore/utils/tf-brd-status.sh +19 -0
  104. package/.tfcore/utils/tf-build-list.py +191 -0
  105. package/.tfcore/utils/tf-build-list.sh +20 -0
  106. package/.tfcore/utils/tf-build.sh +143 -0
  107. package/.tfcore/utils/tf-cdp-relay.ps1 +64 -0
  108. package/.tfcore/utils/tf-checklist-edit.py +133 -0
  109. package/.tfcore/utils/tf-day1-files.py +208 -0
  110. package/.tfcore/utils/tf-day1-files.sh +20 -0
  111. package/.tfcore/utils/tf-devguide-list.py +249 -0
  112. package/.tfcore/utils/tf-devguide-list.sh +18 -0
  113. package/.tfcore/utils/tf-doc-check.py +1267 -0
  114. package/.tfcore/utils/tf-doc-check.sh +29 -0
  115. package/.tfcore/utils/tf-emit.sh +203 -13
  116. package/.tfcore/utils/tf-fix-close.sh +83 -0
  117. package/.tfcore/utils/tf-goal.sh +193 -62
  118. package/.tfcore/utils/tf-harness.sh +4 -9
  119. package/.tfcore/utils/tf-log-miss.py +197 -0
  120. package/.tfcore/utils/tf-log-miss.sh +14 -0
  121. package/.tfcore/utils/tf-misses-md.py +211 -0
  122. package/.tfcore/utils/tf-misses-md.sh +13 -0
  123. package/.tfcore/utils/tf-mockups-locate.py +66 -0
  124. package/.tfcore/utils/tf-mockups-locate.sh +18 -0
  125. package/.tfcore/utils/tf-opencode-audit.py +23 -7
  126. package/.tfcore/utils/tf-perf-grade.py +97 -0
  127. package/.tfcore/utils/tf-perf-grade.sh +12 -0
  128. package/.tfcore/utils/tf-phase.sh +74 -0
  129. package/.tfcore/utils/tf-routing-bind.sh +10 -5
  130. package/.tfcore/utils/tf-routing.sh +6 -7
  131. package/.tfcore/utils/tf-split-brd.py +304 -0
  132. package/.tfcore/utils/tf-split-brd.sh +22 -0
  133. package/.tfcore/utils/tf-status-evidence.sh +68 -0
  134. package/.tfcore/utils/tf-status-facts.py +309 -0
  135. package/.tfcore/utils/tf-status-facts.sh +20 -0
  136. package/.tfcore/utils/tf-triage.py +243 -0
  137. package/.tfcore/utils/tf-triage.sh +16 -0
  138. package/.tfcore/utils/tf-verify-boot.sh +250 -0
  139. package/.tfcore/utils/tf-verify-emit.sh +110 -0
  140. package/.tfcore/utils/tf-verify-env.sh +88 -0
  141. package/.tfcore/utils/tf-verify-list.py +270 -0
  142. package/.tfcore/utils/tf-verify-list.sh +13 -0
  143. package/.tfcore/utils/tf-verify-screens.mjs +283 -0
  144. package/.tfcore/utils/tf-verify-screens.sh +22 -0
  145. package/.tfcore/utils/tf-verify-tests.sh +123 -0
  146. package/.tfcore/utils/tf-verify-verdict.py +283 -0
  147. package/.tfcore/utils/tf-verify-verdict.sh +15 -0
  148. package/.tfcore/utils/tf-yolo.sh +59 -0
  149. package/README.md +114 -1149
  150. package/docs/TechieFlow-Installation.md +27 -7
  151. package/opencode.jsonc +13 -33
  152. package/package.json +4 -7
  153. package/scripts/install.mjs +73 -46
  154. package/scripts/npm-cleanup.mjs +137 -0
  155. package/scripts/npm-postinstall.mjs +49 -0
  156. package/.claude/commands/TechieFlow/tasks/advanced-elicitation.md +0 -117
  157. package/.claude/commands/TechieFlow/tasks/author-brd.md +0 -79
  158. package/.claude/commands/TechieFlow/tasks/document-project.md +0 -343
  159. package/.claude/commands/TechieFlow/tasks/execute-checklist.md +0 -86
  160. package/.claude/commands/TechieFlow/tasks/index-docs.md +0 -173
  161. package/.claude/commands/TechieFlow/tasks/kb-mode-interaction.md +0 -75
  162. package/.claude/commands/TechieFlow/tasks/shard-doc.md +0 -185
  163. package/.codex/config.toml +0 -46
  164. package/.codex/hooks.json +0 -63
  165. package/.codex/rules/techieflow.rules +0 -8
  166. package/.tfcore/hooks/codex-adapter.py +0 -248
  167. package/.tfcore/tasks/advanced-elicitation.md +0 -117
  168. package/.tfcore/tasks/author-brd.md +0 -79
  169. package/.tfcore/tasks/document-project.md +0 -343
  170. package/.tfcore/tasks/execute-checklist.md +0 -86
  171. package/.tfcore/tasks/index-docs.md +0 -173
  172. package/.tfcore/tasks/kb-mode-interaction.md +0 -75
  173. package/.tfcore/tasks/shard-doc.md +0 -185
  174. package/.tfcore/utils/tf-codex-bind.py +0 -196
  175. package/.tfcore/utils/tf-codex-telemetry.py +0 -60
  176. package/WORKFLOW.html +0 -1986
  177. package/scripts/test-install.mjs +0 -505
  178. package/scripts/validate.mjs +0 -163
@@ -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|codex default: claude
14
- # --model <id> claude: --model; opencode: -m; codex: -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 · 2 usage error.
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|codex) ;; *) echo "--harness must be claude|opencode|codex" >&2; exit 2 ;; esac
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
- log "resuming goal (cycle $CYCLE, session ${SESSION_ID:-none})"
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
- CYCLE=0; SESSION_ID=""
123
- state_set goal "$GOAL" harness "$HARNESS" cycle 0 session_id "" started "$(ts)" last_reason "start"
124
- rm -f "$DONE"
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
- # AND codex, because all three are sent this text verbatim. Anything true of only
130
- # one harness goes in that harness's block below (see the `codex` note after
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
- if [[ "$kind" == resume && -n "$SESSION_ID" ]]; then
185
- CMD=(codex exec resume "$SESSION_ID" "$prompt" --json)
186
- else
187
- CMD=(codex exec --json --sandbox workspace-write -c approval_policy="never")
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/Codex JSONL)
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" && codex exec --json --sandbox read-only -c approval_policy="never" "Reply with the single word OK." ) > "$pout" 2>&1; prc=$?
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
- sleep $(( PROBE_MIN * 60 ))
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 sleep 300; else sleep "$left"; fi
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: `tf-goal.sh --classify <output-file> [rc] <app-dir> x` prints the classification and exits.
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)); state_set cycle "$CYCLE"
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
- ( cd "$APP_DIR" && TF_YOLO=1 "${CMD[@]}" ) > >(tee -a "$LOG" > "$OUT") 2>&1
384
- RC=$?
385
- sleep 1 # let tee flush
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
- IFS=$'\t' read -r CLASS DETAIL HOW < <(classify_output "$OUT" "$RC")
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
- sleep $(( BUFFER_MIN * 60 ))
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
- sleep "$BACKOFF"; BACKOFF=$(( BACKOFF * 2 )); [[ $BACKOFF -gt 1800 ]] && BACKOFF=1800 ;;
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
- sleep "$IDLE_RETRY"; BACKOFF=120 ;;
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 | codex | unknown
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|codex) printf '%s' "$TF_HARNESS"; return ;; esac
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" == "codex" ]]; then printf 'Use the $techieflow-%s skill with arguments: %s\n' "${NAME%-phase}" "$ARGS"
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" == "codex" ]]; then printf 'Delegate to the `%s` Codex subagent with: %s\n' "$NAME" "$ARGS"
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 ;;