@walwal-harness/cli 7.1.54 → 7.1.55

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.
@@ -130,7 +130,7 @@ start_headless_tick() {
130
130
  local idle="$2"
131
131
  local force="$3"
132
132
  local review_path="$4"
133
- local mode executor wake_model agent_bin stamp ops_dir activity_dir prompt_scratch log_rel log_path pid_file old_pid pid status tmux_session session_name session_safe wake_prompt log_note
133
+ local mode executor wake_model agent_bin auto_approve_flag stamp ops_dir activity_dir prompt_scratch log_rel log_path pid_file old_pid pid status tmux_session session_name session_safe wake_prompt log_note
134
134
 
135
135
  if command -v jq >/dev/null 2>&1 && [ -f "$project_root/.harness/config.json" ]; then
136
136
  mode="${HARNESS_WAKE_MODE:-$(jq -r '.company_mode.hourly_wake_mode // "headless"' "$project_root/.harness/config.json" 2>/dev/null || echo headless)}"
@@ -141,6 +141,13 @@ start_headless_tick() {
141
141
  executor="${HARNESS_WAKE_EXECUTOR:-claude}"
142
142
  wake_model="${HARNESS_WAKE_MODEL:-}"
143
143
  fi
144
+ # Autonomous wake agents run unattended: accept all tool/MCP usage so the tick
145
+ # never blocks on a permission prompt nobody is there to answer (default = accept all).
146
+ if [ "$executor" = "codex" ]; then
147
+ auto_approve_flag="--dangerously-bypass-approvals-and-sandbox"
148
+ else
149
+ auto_approve_flag="--dangerously-skip-permissions"
150
+ fi
144
151
  stamp="$(date -u "+%Y%m%dT%H%M%SZ")"
145
152
  ops_dir="$project_root/.harness/ops/wake"
146
153
  activity_dir="$project_root/.harness/activity"
@@ -237,12 +244,12 @@ EOF
237
244
  (
238
245
  cd "$project_root" || exit 1
239
246
  if [ "$executor" = "codex" ]; then
240
- "$agent_bin" exec -C "$project_root" "$wake_prompt" > "$log_path" 2>&1
247
+ "$agent_bin" exec $auto_approve_flag -C "$project_root" "$wake_prompt" > "$log_path" 2>&1
241
248
  else
242
249
  if [ -n "$wake_model" ]; then
243
- "$agent_bin" -p "$wake_prompt" --model "$wake_model" > "$log_path" 2>&1
250
+ "$agent_bin" $auto_approve_flag -p "$wake_prompt" --model "$wake_model" > "$log_path" 2>&1
244
251
  else
245
- "$agent_bin" -p "$wake_prompt" > "$log_path" 2>&1
252
+ "$agent_bin" $auto_approve_flag -p "$wake_prompt" > "$log_path" 2>&1
246
253
  fi
247
254
  fi
248
255
  ) &
@@ -262,12 +269,12 @@ EOF
262
269
  session_safe="$(basename "$project_root" | tr -c '[:alnum:]_' '_')"
263
270
  session_name="walwal_${session_safe}_${stamp}"
264
271
  if [ "$executor" = "codex" ]; then
265
- tmux new-session -d -s "$session_name" "cd '$project_root' && '$agent_bin' exec -C '$project_root' - < '$prompt_scratch' 2>&1 | tee '$log_path'"
272
+ tmux new-session -d -s "$session_name" "cd '$project_root' && '$agent_bin' exec $auto_approve_flag -C '$project_root' - < '$prompt_scratch' 2>&1 | tee '$log_path'"
266
273
  else
267
274
  if [ -n "$wake_model" ]; then
268
- tmux new-session -d -s "$session_name" "cd '$project_root' && '$agent_bin' -p \"\$(cat '$prompt_scratch')\" --model '$wake_model' 2>&1 | tee '$log_path'"
275
+ tmux new-session -d -s "$session_name" "cd '$project_root' && '$agent_bin' $auto_approve_flag -p \"\$(cat '$prompt_scratch')\" --model '$wake_model' 2>&1 | tee '$log_path'"
269
276
  else
270
- tmux new-session -d -s "$session_name" "cd '$project_root' && '$agent_bin' -p \"\$(cat '$prompt_scratch')\" 2>&1 | tee '$log_path'"
277
+ tmux new-session -d -s "$session_name" "cd '$project_root' && '$agent_bin' $auto_approve_flag -p \"\$(cat '$prompt_scratch')\" 2>&1 | tee '$log_path'"
271
278
  fi
272
279
  fi
273
280
  status="spawned-tmux executor=$executor session=$session_name"
@@ -62,12 +62,28 @@ acquire_dispatch_lock() {
62
62
  acquire_dispatch_lock
63
63
 
64
64
  spawn_mode="$(jq -r '.company_mode.worker_spawn // "claude"' "$CONFIG" 2>/dev/null || echo claude)"
65
+
66
+ # AGENTS.md Hard Rule 21 — every worker spawn declares its model explicitly.
67
+ # Never inherit the CLI default: a worker terminated by a usage limit looks
68
+ # exactly like a worker that finished, and without a declared model there is
69
+ # nothing to check the limit against. Falls back to the phase model from
70
+ # flow.team, then to opus.
71
+ resolve_worker_model() {
72
+ local phase="$1" model
73
+ model="$(jq -r --arg p "$phase" '
74
+ .company_mode.worker_model
75
+ // (if $p == "eval" then .flow.team.eval_model else .flow.team.gen_model end)
76
+ // empty' "$CONFIG" 2>/dev/null || true)"
77
+ [ -n "$model" ] && [ "$model" != "null" ] || model="opus"
78
+ printf '%s' "$model"
79
+ }
80
+
65
81
  pipeline="$(jq -r '.pipeline // "FULLSTACK"' "$PROGRESS" 2>/dev/null || echo FULLSTACK)"
66
82
  ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
67
83
  stamp="$(date -u +%Y%m%dT%H%M%SZ)"
68
84
 
69
85
  build_prompt() {
70
- local team="$1" fid="$2" agent="$3" prompt_path="$4" log_path="$5"
86
+ local team="$1" fid="$2" agent="$3" prompt_path="$4" log_path="$5" model="${6:-}"
71
87
  cat > "$prompt_path" <<EOF
72
88
  You are running as walwal-harness company worker.
73
89
 
@@ -75,9 +91,10 @@ Agent: harness-${agent}
75
91
  Worker slot: ${team}
76
92
  Feature ID: ${fid}
77
93
  Project root: ${PROJECT_ROOT}
94
+ Declared model: ${model:-unspecified}
78
95
 
79
96
  Follow these rules exactly:
80
- 1. Read AGENTS.md, CONVENTIONS.md if present, .harness/conventions/shared.md, .harness/conventions/${agent}.md if present, .harness/gotchas/${agent}.md if present, .harness/memory.md, .harness/progress.json, .harness/actions/feature-list.json, .harness/actions/api-contract.json, and the relevant harness skill for ${agent}.
97
+ 1. Lessons before plan. FIRST — before any source edit and before any measurement — read AGENTS.md, CONVENTIONS.md if present, .harness/conventions/shared.md, .harness/conventions/${agent}.md if present, .harness/gotchas/shared.md, .harness/gotchas/${agent}.md if present, .harness/memory.md, .harness/progress.json, .harness/actions/feature-list.json, .harness/actions/api-contract.json, and the relevant harness skill for ${agent}. Then write down which convention/gotcha items apply to FEATURE_ID=${fid} and why, and only then start work.
81
98
  2. Work only on FEATURE_ID=${fid}. Do not broaden scope.
82
99
  3. Respect IA ownership and do not edit files outside your department.
83
100
  4. Write a report to .harness/actions/gen-report-${fid}.md if you are a generator, or .harness/actions/evaluation-${fid}.md if you are an evaluator.
@@ -85,6 +102,8 @@ Follow these rules exactly:
85
102
  6. On failure, write the reason and run: bash scripts/harness-queue-manager.sh fail ${fid} "<short reason>"
86
103
  7. Use partial progress updates only via scripts/harness-progress-set.sh.
87
104
  8. Append real timestamped progress to .harness/progress.log. Never write future timestamps.
105
+ 9. End the report with a one-line tally naming which of the convention/gotcha items from step 1 actually fired. \`0 fired\` is a valid tally and must be stated, not omitted.
106
+ 10. Write the report incrementally as the work happens — create the file with its sections present up front and fill them in. If this session is killed mid-round, the report must still be a valid partial report, never a stub.
88
107
 
89
108
  This worker log is: ${log_path#$PROJECT_ROOT/}
90
109
  EOF
@@ -138,7 +157,8 @@ while [ "$i" -lt "$count" ]; do
138
157
  continue
139
158
  fi
140
159
 
141
- build_prompt "$team" "$fid" "$agent" "$prompt_path" "$log_path"
160
+ worker_model="$(resolve_worker_model "$phase")"
161
+ build_prompt "$team" "$fid" "$agent" "$prompt_path" "$log_path" "$worker_model"
142
162
 
143
163
  pid="null"
144
164
  tmux_session="null"
@@ -146,7 +166,7 @@ while [ "$i" -lt "$count" ]; do
146
166
  if [ "$spawn_mode" = "tmux" ] && command -v tmux >/dev/null 2>&1 && command -v claude >/dev/null 2>&1; then
147
167
  session_safe="$(basename "$PROJECT_ROOT" | tr -c '[:alnum:]_' '_')"
148
168
  session_name="walwal_${session_safe}_w${team}_${stamp}"
149
- if tmux new-session -d -s "$session_name" "cd '$PROJECT_ROOT' && claude -p \"\$(cat '$prompt_path')\" > '$log_path' 2>&1"; then
169
+ if tmux new-session -d -s "$session_name" "cd '$PROJECT_ROOT' && claude --dangerously-skip-permissions --model '$worker_model' -p \"\$(cat '$prompt_path')\" > '$log_path' 2>&1"; then
150
170
  pane_pid="$(tmux list-panes -t "$session_name" -F '#{pane_pid}' 2>/dev/null | head -n1)"
151
171
  if [ -n "$pane_pid" ]; then
152
172
  pid="$pane_pid"
@@ -160,22 +180,23 @@ while [ "$i" -lt "$count" ]; do
160
180
  # tmux requested but unavailable: fall back to background claude spawn
161
181
  (
162
182
  cd "$PROJECT_ROOT" || exit 1
163
- claude -p "$(cat "$prompt_path")" > "$log_path" 2>&1
183
+ claude --dangerously-skip-permissions --model "$worker_model" -p "$(cat "$prompt_path")" > "$log_path" 2>&1
164
184
  ) &
165
185
  pid="$!"
166
186
  status="spawned tmux-fallback-claude"
167
187
  elif [ "$spawn_mode" = "claude" ] && command -v claude >/dev/null 2>&1; then
168
188
  (
169
189
  cd "$PROJECT_ROOT" || exit 1
170
- claude -p "$(cat "$prompt_path")" > "$log_path" 2>&1
190
+ claude --dangerously-skip-permissions --model "$worker_model" -p "$(cat "$prompt_path")" > "$log_path" 2>&1
171
191
  ) &
172
192
  pid="$!"
173
193
  status="spawned"
174
194
  fi
175
195
 
176
196
  tmpq="${QUEUE}.tmp.$$.$i"
177
- jq --arg tid "$team" --arg agent "$agent" --arg phase "$phase" --arg prompt "$prompt_rel" --arg log "$log_rel" --arg status "$status" --argjson pid "$pid" --argjson tmux_session "$tmux_session" --arg ts "$ts" '
197
+ jq --arg tid "$team" --arg agent "$agent" --arg phase "$phase" --arg prompt "$prompt_rel" --arg log "$log_rel" --arg status "$status" --argjson pid "$pid" --argjson tmux_session "$tmux_session" --arg ts "$ts" --arg model "$worker_model" '
178
198
  .teams[$tid].pid = $pid |
199
+ .teams[$tid].model = $model |
179
200
  .teams[$tid].tmux_session = $tmux_session |
180
201
  .teams[$tid].agent = $agent |
181
202
  .teams[$tid].phase = $phase |
@@ -184,6 +205,7 @@ while [ "$i" -lt "$count" ]; do
184
205
  .teams[$tid].spawn_status = $status |
185
206
  .teams[$tid].started_at = $ts |
186
207
  .queue.in_progress[.teams[$tid].feature].agent = $agent |
208
+ .queue.in_progress[.teams[$tid].feature].model = $model |
187
209
  .queue.in_progress[.teams[$tid].feature].phase = $phase |
188
210
  .queue.in_progress[.teams[$tid].feature].prompt = $prompt |
189
211
  .queue.in_progress[.teams[$tid].feature].log = $log |
@@ -191,15 +213,15 @@ while [ "$i" -lt "$count" ]; do
191
213
  .queue.in_progress[.teams[$tid].feature].tmux_session = $tmux_session
192
214
  ' "$QUEUE" > "$tmpq" && mv "$tmpq" "$QUEUE"
193
215
 
194
- jq -n --argjson team "$team" --arg feature "$fid" --arg agent "$agent" --arg phase "$phase" --arg prompt "$prompt_rel" --arg log "$log_rel" --arg status "$status" --argjson pid "$pid" --argjson tmux_session "$tmux_session" \
195
- '{team:$team, feature:$feature, agent:$agent, phase:$phase, prompt:$prompt, log:$log, status:$status, pid:$pid, tmux_session:$tmux_session}' >> "$dispatches_file"
196
- echo "$ts | conductor | worker-dispatch | $status | T${team}/${fid} | $agent phase=$phase pid=$pid" >> "$PROJECT_ROOT/.harness/progress.log"
216
+ jq -n --argjson team "$team" --arg feature "$fid" --arg agent "$agent" --arg phase "$phase" --arg prompt "$prompt_rel" --arg log "$log_rel" --arg status "$status" --argjson pid "$pid" --argjson tmux_session "$tmux_session" --arg model "$worker_model" \
217
+ '{team:$team, feature:$feature, agent:$agent, phase:$phase, model:$model, prompt:$prompt, log:$log, status:$status, pid:$pid, tmux_session:$tmux_session}' >> "$dispatches_file"
218
+ echo "$ts | conductor | worker-dispatch | $status | T${team}/${fid} | $agent phase=$phase model=$worker_model pid=$pid" >> "$PROJECT_ROOT/.harness/progress.log"
197
219
  i=$((i + 1))
198
220
  done
199
221
 
200
222
  dispatches="$(jq -s '.' "$dispatches_file")"
201
223
  rm -f "$dispatches_file"
202
- active_workers="$(jq '[.teams | to_entries[] | select(.value.status == "busy") | {team:(.key|tonumber), feature:.value.feature, agent:.value.agent, pid:.value.pid, tmux_session:.value.tmux_session, phase:(.value.phase // "gen"), prompt:.value.prompt, log:.value.log, spawn_status:.value.spawn_status}]' "$QUEUE")"
224
+ active_workers="$(jq '[.teams | to_entries[] | select(.value.status == "busy") | {team:(.key|tonumber), feature:.value.feature, agent:.value.agent, model:.value.model, pid:.value.pid, tmux_session:.value.tmux_session, phase:(.value.phase // "gen"), prompt:.value.prompt, log:.value.log, spawn_status:.value.spawn_status}]' "$QUEUE")"
203
225
 
204
226
  jq --arg ts "$ts" --argjson dispatches "$dispatches" --argjson workers "$active_workers" '
205
227
  .company_state.active_workers = ($workers | length) |
@@ -11,14 +11,21 @@ mode="${2:-text}"
11
11
  scope="${3:-all}"
12
12
  violations=()
13
13
 
14
+ # Section-scoped reading (conventions/shared.md): match `^>?\s*#{1,6}` and take
15
+ # every hit — blockquoted or plain, at any depth, with no content filter. A
16
+ # heading anchored on `^#` alone misses the same heading quoted one level in,
17
+ # which is how in-place retractions and continuation lines get written.
18
+ HEADING2='^[[:space:]]*>?[[:space:]]*##[[:space:]]+'
19
+ HEADING3='^[[:space:]]*>?[[:space:]]*###[[:space:]]+'
20
+
14
21
  has_implementation_notes() {
15
22
  local file="$1"
16
23
  [ -s "$file" ] || return 1
17
- grep -Eq '^##[[:space:]]+Implementation Notes[[:space:]]*$' "$file" &&
18
- grep -Eq '^###[[:space:]]+Design Decisions[[:space:]]*$' "$file" &&
19
- grep -Eq '^###[[:space:]]+Deviations[[:space:]]*$' "$file" &&
20
- grep -Eq '^###[[:space:]]+Tradeoffs[[:space:]]*$' "$file" &&
21
- grep -Eq '^###[[:space:]]+Open Questions[[:space:]]*$' "$file"
24
+ grep -Eq "${HEADING2}Implementation Notes[[:space:]]*$" "$file" &&
25
+ grep -Eq "${HEADING3}Design Decisions[[:space:]]*$" "$file" &&
26
+ grep -Eq "${HEADING3}Deviations[[:space:]]*$" "$file" &&
27
+ grep -Eq "${HEADING3}Tradeoffs[[:space:]]*$" "$file" &&
28
+ grep -Eq "${HEADING3}Open Questions[[:space:]]*$" "$file"
22
29
  }
23
30
 
24
31
  has_worker_report() {