cclaw-cli 0.48.5 → 0.48.7
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/dist/artifact-linter.js +32 -0
- package/dist/config.d.ts +1 -1
- package/dist/config.js +44 -5
- package/dist/content/hooks.d.ts +2 -2
- package/dist/content/hooks.js +293 -89
- package/dist/content/ideate-command.js +11 -0
- package/dist/content/iron-laws.d.ts +142 -0
- package/dist/content/iron-laws.js +191 -0
- package/dist/content/meta-skill.js +1 -0
- package/dist/content/next-command.js +12 -0
- package/dist/content/observe.js +555 -45
- package/dist/content/ops-command.js +11 -0
- package/dist/content/session-hooks.js +3 -1
- package/dist/content/stage-schema.d.ts +16 -0
- package/dist/content/stage-schema.js +82 -5
- package/dist/content/stages/review.js +4 -4
- package/dist/content/stages/tdd.js +7 -7
- package/dist/content/start-command.js +12 -0
- package/dist/content/subagents.js +26 -0
- package/dist/content/templates.js +8 -0
- package/dist/content/view-command.js +11 -0
- package/dist/doctor.js +6 -2
- package/dist/harness-adapters.js +3 -0
- package/dist/install.js +11 -1
- package/dist/internal/advance-stage.js +14 -2
- package/dist/internal/envelope-validate.d.ts +7 -0
- package/dist/internal/envelope-validate.js +66 -0
- package/dist/internal/knowledge-digest.d.ts +7 -0
- package/dist/internal/knowledge-digest.js +93 -0
- package/dist/internal/tdd-red-evidence.d.ts +7 -0
- package/dist/internal/tdd-red-evidence.js +130 -0
- package/dist/knowledge-store.d.ts +8 -0
- package/dist/knowledge-store.js +95 -0
- package/dist/tdd-cycle.d.ts +7 -0
- package/dist/tdd-cycle.js +29 -0
- package/dist/types.d.ts +6 -0
- package/package.json +1 -1
package/dist/content/observe.js
CHANGED
|
@@ -16,15 +16,6 @@ set -uo pipefail
|
|
|
16
16
|
shopt -s globstar 2>/dev/null || true
|
|
17
17
|
PROMPT_GUARD_MODE="${promptGuardMode}"
|
|
18
18
|
|
|
19
|
-
HARNESS="codex"
|
|
20
|
-
if [ -n "\${CLAUDE_PROJECT_DIR:-}" ]; then
|
|
21
|
-
HARNESS="claude"
|
|
22
|
-
elif [ -n "\${CURSOR_PROJECT_DIR:-}" ] || [ -n "\${CURSOR_PROJECT_ROOT:-}" ]; then
|
|
23
|
-
HARNESS="cursor"
|
|
24
|
-
elif [ -n "\${OPENCODE_PROJECT_DIR:-}" ] || [ -n "\${OPENCODE_PROJECT_ROOT:-}" ]; then
|
|
25
|
-
HARNESS="opencode"
|
|
26
|
-
fi
|
|
27
|
-
|
|
28
19
|
${RUNTIME_SHELL_DETECT_ROOT}
|
|
29
20
|
|
|
30
21
|
STATE_DIR="$ROOT/${RUNTIME_ROOT}/state"
|
|
@@ -35,8 +26,12 @@ INPUT=$(cat 2>/dev/null || echo '{}')
|
|
|
35
26
|
[ -n "$INPUT" ] || exit 0
|
|
36
27
|
|
|
37
28
|
TOOL="unknown"
|
|
38
|
-
PAYLOAD=""
|
|
39
|
-
if command -v
|
|
29
|
+
PAYLOAD="$INPUT"
|
|
30
|
+
if command -v cclaw_hook_extract_tool_and_payload >/dev/null 2>&1; then
|
|
31
|
+
cclaw_hook_extract_tool_and_payload "$INPUT"
|
|
32
|
+
TOOL="\${CCLAW_HOOK_TOOL:-unknown}"
|
|
33
|
+
PAYLOAD="\${CCLAW_HOOK_PAYLOAD:-$INPUT}"
|
|
34
|
+
elif command -v jq >/dev/null 2>&1; then
|
|
40
35
|
TOOL=$(printf '%s' "$INPUT" | jq -r '.tool_name // .tool // .toolName // .name // .id // .command // .tool.name // .tool.id // .input.tool_name // .input.tool // .input.toolName // .input.name // .input.id // .input.command // .input.tool.name // .input.tool.id // "unknown"' 2>/dev/null || echo "unknown")
|
|
41
36
|
PAYLOAD=$(printf '%s' "$INPUT" | jq -r '.tool_input // .input // .arguments // .params // .payload // {} | tostring' 2>/dev/null || echo "")
|
|
42
37
|
elif command -v python3 >/dev/null 2>&1; then
|
|
@@ -93,8 +88,13 @@ if [ -z "$PAYLOAD" ]; then
|
|
|
93
88
|
PAYLOAD=$(printf '%s' "$INPUT")
|
|
94
89
|
fi
|
|
95
90
|
|
|
96
|
-
|
|
97
|
-
|
|
91
|
+
if command -v cclaw_hook_lower >/dev/null 2>&1; then
|
|
92
|
+
PAYLOAD_LOWER=$(cclaw_hook_lower "$PAYLOAD")
|
|
93
|
+
TOOL_LOWER=$(cclaw_hook_lower "$TOOL")
|
|
94
|
+
else
|
|
95
|
+
PAYLOAD_LOWER=$(printf '%s' "$PAYLOAD" | tr '[:upper:]' '[:lower:]')
|
|
96
|
+
TOOL_LOWER=$(printf '%s' "$TOOL" | tr '[:upper:]' '[:lower:]')
|
|
97
|
+
fi
|
|
98
98
|
TS=$(date -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || echo "")
|
|
99
99
|
REASONS=""
|
|
100
100
|
|
|
@@ -179,6 +179,8 @@ ${RUNTIME_SHELL_DETECT_ROOT}
|
|
|
179
179
|
STATE_DIR="$ROOT/${RUNTIME_ROOT}/state"
|
|
180
180
|
FLOW_STATE_FILE="$STATE_DIR/flow-state.json"
|
|
181
181
|
TDD_LOG_FILE="$STATE_DIR/tdd-cycle-log.jsonl"
|
|
182
|
+
IRON_LAWS_FILE="$STATE_DIR/iron-laws.json"
|
|
183
|
+
REVIEW_ARMY_FILE="$ROOT/${RUNTIME_ROOT}/artifacts/07-review-army.json"
|
|
182
184
|
GUARD_STATE_FILE="$STATE_DIR/workflow-guard.json"
|
|
183
185
|
GUARD_LOG="$STATE_DIR/workflow-guard.jsonl"
|
|
184
186
|
mkdir -p "$STATE_DIR" 2>/dev/null || true
|
|
@@ -187,8 +189,12 @@ INPUT=$(cat 2>/dev/null || echo '{}')
|
|
|
187
189
|
[ -n "$INPUT" ] || exit 0
|
|
188
190
|
|
|
189
191
|
TOOL="unknown"
|
|
190
|
-
PAYLOAD=""
|
|
191
|
-
if command -v
|
|
192
|
+
PAYLOAD="$INPUT"
|
|
193
|
+
if command -v cclaw_hook_extract_tool_and_payload >/dev/null 2>&1; then
|
|
194
|
+
cclaw_hook_extract_tool_and_payload "$INPUT"
|
|
195
|
+
TOOL="\${CCLAW_HOOK_TOOL:-unknown}"
|
|
196
|
+
PAYLOAD="\${CCLAW_HOOK_PAYLOAD:-$INPUT}"
|
|
197
|
+
elif command -v jq >/dev/null 2>&1; then
|
|
192
198
|
TOOL=$(printf '%s' "$INPUT" | jq -r '.tool_name // .tool // .toolName // .name // .id // .command // .tool.name // .tool.id // .input.tool_name // .input.tool // .input.toolName // .input.name // .input.id // .input.command // .input.tool.name // .input.tool.id // "unknown"' 2>/dev/null || echo "unknown")
|
|
193
199
|
PAYLOAD=$(printf '%s' "$INPUT" | jq -r '.tool_input // .input // .arguments // .params // .payload // {} | tostring' 2>/dev/null || echo "")
|
|
194
200
|
elif command -v python3 >/dev/null 2>&1; then
|
|
@@ -241,15 +247,36 @@ else
|
|
|
241
247
|
fi
|
|
242
248
|
|
|
243
249
|
[ -n "$PAYLOAD" ] || PAYLOAD=$(printf '%s' "$INPUT")
|
|
244
|
-
|
|
245
|
-
|
|
250
|
+
if command -v cclaw_hook_lower >/dev/null 2>&1; then
|
|
251
|
+
TOOL_LOWER=$(cclaw_hook_lower "$TOOL")
|
|
252
|
+
PAYLOAD_LOWER=$(cclaw_hook_lower "$PAYLOAD")
|
|
253
|
+
else
|
|
254
|
+
TOOL_LOWER=$(printf '%s' "$TOOL" | tr '[:upper:]' '[:lower:]')
|
|
255
|
+
PAYLOAD_LOWER=$(printf '%s' "$PAYLOAD" | tr '[:upper:]' '[:lower:]')
|
|
256
|
+
fi
|
|
246
257
|
TS=$(date -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || echo "")
|
|
247
258
|
NOW_EPOCH=$(date +%s 2>/dev/null || echo "0")
|
|
248
259
|
REASONS=""
|
|
249
260
|
|
|
261
|
+
ACTIVE_AGENT="\${CCLAW_ACTIVE_AGENT:-}"
|
|
262
|
+
if [ -z "$ACTIVE_AGENT" ]; then
|
|
263
|
+
if command -v jq >/dev/null 2>&1; then
|
|
264
|
+
ACTIVE_AGENT=$(printf '%s' "$INPUT" | jq -r '.agent_name // .agent // .input.agent_name // .input.agent // .tool_input.agent_name // .tool_input.agent // ""' 2>/dev/null || echo "")
|
|
265
|
+
fi
|
|
266
|
+
fi
|
|
267
|
+
if command -v cclaw_hook_lower >/dev/null 2>&1; then
|
|
268
|
+
ACTIVE_AGENT_LOWER=$(cclaw_hook_lower "$ACTIVE_AGENT")
|
|
269
|
+
else
|
|
270
|
+
ACTIVE_AGENT_LOWER=$(printf '%s' "$ACTIVE_AGENT" | tr '[:upper:]' '[:lower:]')
|
|
271
|
+
fi
|
|
272
|
+
|
|
250
273
|
CURRENT_STAGE="none"
|
|
251
274
|
CURRENT_RUN="active"
|
|
252
|
-
if
|
|
275
|
+
if command -v cclaw_hook_read_flow_state_minimal >/dev/null 2>&1; then
|
|
276
|
+
cclaw_hook_read_flow_state_minimal "$FLOW_STATE_FILE"
|
|
277
|
+
CURRENT_STAGE="\${CCLAW_HOOK_FLOW_STAGE:-none}"
|
|
278
|
+
CURRENT_RUN="\${CCLAW_HOOK_FLOW_RUN_ID:-active}"
|
|
279
|
+
elif [ -f "$FLOW_STATE_FILE" ]; then
|
|
253
280
|
if command -v jq >/dev/null 2>&1; then
|
|
254
281
|
CURRENT_STAGE=$(jq -r '.currentStage // "none"' "$FLOW_STATE_FILE" 2>/dev/null || echo "none")
|
|
255
282
|
CURRENT_RUN=$(jq -r '.activeRunId // "active"' "$FLOW_STATE_FILE" 2>/dev/null || echo "active")
|
|
@@ -287,6 +314,87 @@ PY
|
|
|
287
314
|
fi
|
|
288
315
|
fi
|
|
289
316
|
|
|
317
|
+
SHIP_PREFLIGHT_PASSED="false"
|
|
318
|
+
if [ -f "$FLOW_STATE_FILE" ]; then
|
|
319
|
+
if command -v jq >/dev/null 2>&1; then
|
|
320
|
+
SHIP_PREFLIGHT_PASSED=$(jq -r '
|
|
321
|
+
if ((.stageGateCatalog.ship.passed // []) | index("ship_preflight_passed")) == null
|
|
322
|
+
then "false"
|
|
323
|
+
else "true"
|
|
324
|
+
end
|
|
325
|
+
' "$FLOW_STATE_FILE" 2>/dev/null || echo "false")
|
|
326
|
+
elif command -v python3 >/dev/null 2>&1; then
|
|
327
|
+
SHIP_PREFLIGHT_PASSED=$(python3 - "$FLOW_STATE_FILE" <<'PY'
|
|
328
|
+
import json
|
|
329
|
+
import sys
|
|
330
|
+
value = "false"
|
|
331
|
+
try:
|
|
332
|
+
with open(sys.argv[1], "r", encoding="utf-8") as fh:
|
|
333
|
+
parsed = json.load(fh)
|
|
334
|
+
ship = ((parsed.get("stageGateCatalog") or {}).get("ship") or {}).get("passed") or []
|
|
335
|
+
if isinstance(ship, list) and "ship_preflight_passed" in ship:
|
|
336
|
+
value = "true"
|
|
337
|
+
except Exception:
|
|
338
|
+
value = "false"
|
|
339
|
+
print(value)
|
|
340
|
+
PY
|
|
341
|
+
)
|
|
342
|
+
fi
|
|
343
|
+
fi
|
|
344
|
+
|
|
345
|
+
IRON_LAW_STRICT_ALL="false"
|
|
346
|
+
IRON_LAW_STRICT_IDS=""
|
|
347
|
+
if [ -f "$IRON_LAWS_FILE" ]; then
|
|
348
|
+
if command -v jq >/dev/null 2>&1; then
|
|
349
|
+
IRON_LAW_STRICT_ALL=$(jq -r 'if (.mode // "advisory") == "strict" then "true" else "false" end' "$IRON_LAWS_FILE" 2>/dev/null || echo "false")
|
|
350
|
+
IRON_LAW_STRICT_IDS=$(jq -r '(.laws // []) | map(select(.strict == true) | (.id // "")) | map(select(length > 0)) | join(",")' "$IRON_LAWS_FILE" 2>/dev/null || echo "")
|
|
351
|
+
elif command -v python3 >/dev/null 2>&1; then
|
|
352
|
+
IRON_LAW_STRICT_ALL=$(python3 - "$IRON_LAWS_FILE" <<'PY'
|
|
353
|
+
import json
|
|
354
|
+
import sys
|
|
355
|
+
mode = "false"
|
|
356
|
+
try:
|
|
357
|
+
with open(sys.argv[1], "r", encoding="utf-8") as fh:
|
|
358
|
+
parsed = json.load(fh)
|
|
359
|
+
if str(parsed.get("mode", "advisory")) == "strict":
|
|
360
|
+
mode = "true"
|
|
361
|
+
except Exception:
|
|
362
|
+
mode = "false"
|
|
363
|
+
print(mode)
|
|
364
|
+
PY
|
|
365
|
+
)
|
|
366
|
+
IRON_LAW_STRICT_IDS=$(python3 - "$IRON_LAWS_FILE" <<'PY'
|
|
367
|
+
import json
|
|
368
|
+
import sys
|
|
369
|
+
out = []
|
|
370
|
+
try:
|
|
371
|
+
with open(sys.argv[1], "r", encoding="utf-8") as fh:
|
|
372
|
+
parsed = json.load(fh)
|
|
373
|
+
for row in parsed.get("laws", []):
|
|
374
|
+
if isinstance(row, dict) and row.get("strict") and isinstance(row.get("id"), str):
|
|
375
|
+
out.append(row["id"].strip())
|
|
376
|
+
except Exception:
|
|
377
|
+
out = []
|
|
378
|
+
print(",".join([v for v in out if v]))
|
|
379
|
+
PY
|
|
380
|
+
)
|
|
381
|
+
fi
|
|
382
|
+
fi
|
|
383
|
+
|
|
384
|
+
iron_law_is_strict() {
|
|
385
|
+
local law_id="$1"
|
|
386
|
+
if [ "$IRON_LAW_STRICT_ALL" = "true" ]; then
|
|
387
|
+
return 0
|
|
388
|
+
fi
|
|
389
|
+
if [ -z "$IRON_LAW_STRICT_IDS" ]; then
|
|
390
|
+
return 1
|
|
391
|
+
fi
|
|
392
|
+
case ",$IRON_LAW_STRICT_IDS," in
|
|
393
|
+
*",$law_id,"*) return 0 ;;
|
|
394
|
+
*) return 1 ;;
|
|
395
|
+
esac
|
|
396
|
+
}
|
|
397
|
+
|
|
290
398
|
LAST_FLOW_READ_AT=0
|
|
291
399
|
if [ -f "$GUARD_STATE_FILE" ]; then
|
|
292
400
|
if command -v jq >/dev/null 2>&1; then
|
|
@@ -332,6 +440,14 @@ is_mutating_tool() {
|
|
|
332
440
|
esac
|
|
333
441
|
}
|
|
334
442
|
|
|
443
|
+
is_execution_or_mutating_tool() {
|
|
444
|
+
case "$1" in
|
|
445
|
+
write|edit|multiedit|multi_edit|delete|applypatch|apply_patch) return 0 ;;
|
|
446
|
+
shell|bash|runcommand|run_command|execcommand|exec_command|terminal) return 0 ;;
|
|
447
|
+
*) return 1 ;;
|
|
448
|
+
esac
|
|
449
|
+
}
|
|
450
|
+
|
|
335
451
|
is_plan_mode_safe_tool() {
|
|
336
452
|
case "$1" in
|
|
337
453
|
read|readfile|open|view|cat|head|tail) return 0 ;;
|
|
@@ -576,6 +692,62 @@ is_tdd_production_write_payload() {
|
|
|
576
692
|
return 0
|
|
577
693
|
}
|
|
578
694
|
|
|
695
|
+
collect_tdd_production_paths() {
|
|
696
|
+
local payload_paths="$1"
|
|
697
|
+
local out=""
|
|
698
|
+
[ -n "$payload_paths" ] || {
|
|
699
|
+
printf ''
|
|
700
|
+
return 0
|
|
701
|
+
}
|
|
702
|
+
while IFS= read -r raw_path; do
|
|
703
|
+
[ -n "$raw_path" ] || continue
|
|
704
|
+
local normalized
|
|
705
|
+
normalized=$(normalize_payload_path "$raw_path")
|
|
706
|
+
if is_tdd_production_path "$normalized"; then
|
|
707
|
+
if [ -n "$out" ]; then
|
|
708
|
+
out="$out"$'\n'"$raw_path"
|
|
709
|
+
else
|
|
710
|
+
out="$raw_path"
|
|
711
|
+
fi
|
|
712
|
+
fi
|
|
713
|
+
done <<< "$payload_paths"
|
|
714
|
+
printf '%s' "$out"
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
review_layer_coverage_complete() {
|
|
718
|
+
if [ ! -f "$REVIEW_ARMY_FILE" ]; then
|
|
719
|
+
return 1
|
|
720
|
+
fi
|
|
721
|
+
if command -v jq >/dev/null 2>&1; then
|
|
722
|
+
jq -e '
|
|
723
|
+
((.reconciliation.layerCoverage.spec // false) == true) and
|
|
724
|
+
((.reconciliation.layerCoverage.correctness // false) == true) and
|
|
725
|
+
((.reconciliation.layerCoverage.security // false) == true) and
|
|
726
|
+
((.reconciliation.layerCoverage.performance // false) == true) and
|
|
727
|
+
((.reconciliation.layerCoverage.architecture // false) == true) and
|
|
728
|
+
((.reconciliation.layerCoverage["external-safety"] // false) == true)
|
|
729
|
+
' "$REVIEW_ARMY_FILE" >/dev/null 2>&1
|
|
730
|
+
return $?
|
|
731
|
+
fi
|
|
732
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
733
|
+
python3 - "$REVIEW_ARMY_FILE" <<'PY'
|
|
734
|
+
import json
|
|
735
|
+
import sys
|
|
736
|
+
keys = ["spec", "correctness", "security", "performance", "architecture", "external-safety"]
|
|
737
|
+
try:
|
|
738
|
+
with open(sys.argv[1], "r", encoding="utf-8") as handle:
|
|
739
|
+
parsed = json.load(handle)
|
|
740
|
+
coverage = ((parsed.get("reconciliation") or {}).get("layerCoverage") or {})
|
|
741
|
+
ok = all(coverage.get(key) is True for key in keys)
|
|
742
|
+
except Exception:
|
|
743
|
+
ok = False
|
|
744
|
+
raise SystemExit(0 if ok else 1)
|
|
745
|
+
PY
|
|
746
|
+
return $?
|
|
747
|
+
fi
|
|
748
|
+
return 1
|
|
749
|
+
}
|
|
750
|
+
|
|
579
751
|
tdd_cycle_counts() {
|
|
580
752
|
if [ ! -f "$TDD_LOG_FILE" ] || [ ! -s "$TDD_LOG_FILE" ]; then
|
|
581
753
|
printf '0:0'
|
|
@@ -785,33 +957,145 @@ if is_preimplementation_stage "$CURRENT_STAGE" && is_mutating_tool "$TOOL_LOWER"
|
|
|
785
957
|
fi
|
|
786
958
|
|
|
787
959
|
if [ "$CURRENT_STAGE" = "tdd" ] && is_mutating_tool "$TOOL_LOWER"; then
|
|
960
|
+
TDD_MISSING_RED_PATHS=""
|
|
788
961
|
if is_tdd_production_write_payload "$PAYLOAD_LOWER" "$MUTATION_PATHS"; then
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
962
|
+
PRODUCTION_PATHS=$(collect_tdd_production_paths "$MUTATION_PATHS")
|
|
963
|
+
PER_PATH_RED_CHECKED="false"
|
|
964
|
+
if [ -n "$PRODUCTION_PATHS" ] && command -v cclaw >/dev/null 2>&1; then
|
|
965
|
+
PER_PATH_RED_CHECKED="true"
|
|
966
|
+
while IFS= read -r production_path; do
|
|
967
|
+
[ -n "$production_path" ] || continue
|
|
968
|
+
cclaw internal tdd-red-evidence --path="$production_path" --run-id="$CURRENT_RUN" --quiet >/dev/null 2>&1
|
|
969
|
+
EVIDENCE_STATUS=$?
|
|
970
|
+
if [ "$EVIDENCE_STATUS" -eq 0 ]; then
|
|
971
|
+
continue
|
|
972
|
+
fi
|
|
973
|
+
if [ "$EVIDENCE_STATUS" -eq 2 ]; then
|
|
974
|
+
if [ -n "$TDD_MISSING_RED_PATHS" ]; then
|
|
975
|
+
TDD_MISSING_RED_PATHS="$TDD_MISSING_RED_PATHS, $production_path"
|
|
976
|
+
else
|
|
977
|
+
TDD_MISSING_RED_PATHS="$production_path"
|
|
978
|
+
fi
|
|
979
|
+
continue
|
|
980
|
+
fi
|
|
981
|
+
if [ -n "$REASONS" ]; then
|
|
982
|
+
REASONS="$REASONS,tdd_red_evidence_check_failed"
|
|
983
|
+
else
|
|
984
|
+
REASONS="tdd_red_evidence_check_failed"
|
|
985
|
+
fi
|
|
986
|
+
done <<< "$PRODUCTION_PATHS"
|
|
987
|
+
if [ -n "$TDD_MISSING_RED_PATHS" ]; then
|
|
988
|
+
if [ -n "$REASONS" ]; then
|
|
989
|
+
REASONS="$REASONS,tdd_write_without_red_for_path"
|
|
990
|
+
else
|
|
991
|
+
REASONS="tdd_write_without_red_for_path"
|
|
992
|
+
fi
|
|
993
|
+
fi
|
|
994
|
+
fi
|
|
995
|
+
if [ "$PER_PATH_RED_CHECKED" != "true" ]; then
|
|
996
|
+
if has_open_red_cycle; then
|
|
997
|
+
TDD_CYCLE_STATE="red_open"
|
|
998
|
+
else
|
|
999
|
+
OPEN_RED_STATUS=$?
|
|
1000
|
+
if [ "$OPEN_RED_STATUS" -eq 2 ]; then
|
|
1001
|
+
TDD_CYCLE_STATE="counts_unavailable"
|
|
1002
|
+
if [ -n "$REASONS" ]; then
|
|
1003
|
+
REASONS="$REASONS,tdd_cycle_counts_unavailable"
|
|
1004
|
+
else
|
|
1005
|
+
REASONS="tdd_cycle_counts_unavailable"
|
|
1006
|
+
fi
|
|
1007
|
+
else
|
|
1008
|
+
TDD_CYCLE_STATE=$(tdd_cycle_state)
|
|
1009
|
+
fi
|
|
1010
|
+
fi
|
|
1011
|
+
if [ "$TDD_CYCLE_STATE" = "need_red" ]; then
|
|
1012
|
+
if [ -n "$REASONS" ]; then
|
|
1013
|
+
REASONS="$REASONS,tdd_write_without_open_red"
|
|
1014
|
+
else
|
|
1015
|
+
REASONS="tdd_write_without_open_red"
|
|
1016
|
+
fi
|
|
1017
|
+
elif [ "$TDD_CYCLE_STATE" = "__UNAVAILABLE__" ]; then
|
|
795
1018
|
if [ -n "$REASONS" ]; then
|
|
796
1019
|
REASONS="$REASONS,tdd_cycle_counts_unavailable"
|
|
797
1020
|
else
|
|
798
1021
|
REASONS="tdd_cycle_counts_unavailable"
|
|
799
1022
|
fi
|
|
1023
|
+
fi
|
|
1024
|
+
fi
|
|
1025
|
+
fi
|
|
1026
|
+
fi
|
|
1027
|
+
|
|
1028
|
+
if [ "$CURRENT_STAGE" = "tdd" ] && is_mutating_tool "$TOOL_LOWER"; then
|
|
1029
|
+
ACTIVE_AGENT_EFFECTIVE="$ACTIVE_AGENT_LOWER"
|
|
1030
|
+
if [ -z "$ACTIVE_AGENT_EFFECTIVE" ]; then
|
|
1031
|
+
INFERRED_TDD_PHASE=$(tdd_cycle_state)
|
|
1032
|
+
case "$INFERRED_TDD_PHASE" in
|
|
1033
|
+
need_red) ACTIVE_AGENT_EFFECTIVE="tdd-red" ;;
|
|
1034
|
+
red_open) ACTIVE_AGENT_EFFECTIVE="tdd-green" ;;
|
|
1035
|
+
green_done) ACTIVE_AGENT_EFFECTIVE="tdd-refactor" ;;
|
|
1036
|
+
*) ACTIVE_AGENT_EFFECTIVE="" ;;
|
|
1037
|
+
esac
|
|
1038
|
+
fi
|
|
1039
|
+
if [ "$ACTIVE_AGENT_EFFECTIVE" = "tdd-red" ] && is_tdd_production_write_payload "$PAYLOAD_LOWER" "$MUTATION_PATHS"; then
|
|
1040
|
+
if [ -n "$REASONS" ]; then
|
|
1041
|
+
REASONS="$REASONS,tdd_red_agent_cannot_write_production"
|
|
1042
|
+
else
|
|
1043
|
+
REASONS="tdd_red_agent_cannot_write_production"
|
|
1044
|
+
fi
|
|
1045
|
+
fi
|
|
1046
|
+
if [ "$ACTIVE_AGENT_EFFECTIVE" = "tdd-green" ] && is_tdd_test_payload "$PAYLOAD_LOWER" "$MUTATION_PATHS"; then
|
|
1047
|
+
if [ -n "$REASONS" ]; then
|
|
1048
|
+
REASONS="$REASONS,tdd_green_agent_cannot_write_tests"
|
|
1049
|
+
else
|
|
1050
|
+
REASONS="tdd_green_agent_cannot_write_tests"
|
|
1051
|
+
fi
|
|
1052
|
+
fi
|
|
1053
|
+
if [ "$ACTIVE_AGENT_EFFECTIVE" = "tdd-refactor" ]; then
|
|
1054
|
+
TDD_AGENT_STATE=$(tdd_cycle_state)
|
|
1055
|
+
if [ "$TDD_AGENT_STATE" != "green_done" ]; then
|
|
1056
|
+
if [ -n "$REASONS" ]; then
|
|
1057
|
+
REASONS="$REASONS,tdd_refactor_before_green"
|
|
800
1058
|
else
|
|
801
|
-
|
|
1059
|
+
REASONS="tdd_refactor_before_green"
|
|
802
1060
|
fi
|
|
803
1061
|
fi
|
|
804
|
-
|
|
1062
|
+
fi
|
|
1063
|
+
fi
|
|
1064
|
+
|
|
1065
|
+
if is_mutating_tool "$TOOL_LOWER"; then
|
|
1066
|
+
if [ "$LAST_FLOW_READ_AT" -le 0 ] || [ "$NOW_EPOCH" -le 0 ] || [ $((NOW_EPOCH - LAST_FLOW_READ_AT)) -gt "$MAX_FLOW_READ_AGE_SEC" ]; then
|
|
1067
|
+
if [ -n "$REASONS" ]; then
|
|
1068
|
+
REASONS="$REASONS,mutating_without_recent_flow_read"
|
|
1069
|
+
else
|
|
1070
|
+
REASONS="mutating_without_recent_flow_read"
|
|
1071
|
+
fi
|
|
1072
|
+
fi
|
|
1073
|
+
fi
|
|
1074
|
+
|
|
1075
|
+
if is_mutating_tool "$TOOL_LOWER" && printf '%s' "$PAYLOAD_LOWER" | grep -Eq '\.cclaw/(state|hooks|skills)'; then
|
|
1076
|
+
if ! is_cclaw_cli_payload "$PAYLOAD_LOWER"; then
|
|
1077
|
+
if [ -n "$REASONS" ]; then
|
|
1078
|
+
REASONS="$REASONS,runtime_write_requires_managed_only"
|
|
1079
|
+
else
|
|
1080
|
+
REASONS="runtime_write_requires_managed_only"
|
|
1081
|
+
fi
|
|
1082
|
+
fi
|
|
1083
|
+
fi
|
|
1084
|
+
|
|
1085
|
+
if [ "$CURRENT_STAGE" = "ship" ] && is_execution_or_mutating_tool "$TOOL_LOWER"; then
|
|
1086
|
+
if printf '%s' "$PAYLOAD_LOWER" | grep -Eq '(npm publish|pnpm publish|yarn publish|gh release create|git push[[:space:]].*--tags|npm version)'; then
|
|
1087
|
+
if [ "$SHIP_PREFLIGHT_PASSED" != "true" ]; then
|
|
805
1088
|
if [ -n "$REASONS" ]; then
|
|
806
|
-
REASONS="$REASONS,
|
|
1089
|
+
REASONS="$REASONS,ship_preflight_required"
|
|
807
1090
|
else
|
|
808
|
-
REASONS="
|
|
1091
|
+
REASONS="ship_preflight_required"
|
|
809
1092
|
fi
|
|
810
|
-
|
|
1093
|
+
fi
|
|
1094
|
+
if ! review_layer_coverage_complete; then
|
|
811
1095
|
if [ -n "$REASONS" ]; then
|
|
812
|
-
REASONS="$REASONS,
|
|
1096
|
+
REASONS="$REASONS,ship_review_coverage_required"
|
|
813
1097
|
else
|
|
814
|
-
REASONS="
|
|
1098
|
+
REASONS="ship_review_coverage_required"
|
|
815
1099
|
fi
|
|
816
1100
|
fi
|
|
817
1101
|
fi
|
|
@@ -882,12 +1166,28 @@ PY
|
|
|
882
1166
|
fi
|
|
883
1167
|
|
|
884
1168
|
if [ -n "$REASONS" ]; then
|
|
885
|
-
if printf '%s' "$REASONS" | grep -Eq '
|
|
1169
|
+
if printf '%s' "$REASONS" | grep -Eq 'tdd_write_without_red_for_path'; then
|
|
1170
|
+
NOTE="Cclaw workflow guard: missing failing RED evidence for production path(s): \${TDD_MISSING_RED_PATHS:-unknown}. Log failing tests before touching these files."
|
|
1171
|
+
elif printf '%s' "$REASONS" | grep -Eq 'tdd_write_without_open_red'; then
|
|
886
1172
|
NOTE="Cclaw workflow guard: Write a failing test first before editing production files during tdd stage (state=\${TDD_CYCLE_STATE})."
|
|
1173
|
+
elif printf '%s' "$REASONS" | grep -Eq 'tdd_red_evidence_check_failed'; then
|
|
1174
|
+
NOTE="Cclaw workflow guard: failed to validate per-path RED evidence via \`cclaw internal tdd-red-evidence\`; refusing write until evidence check succeeds."
|
|
1175
|
+
elif printf '%s' "$REASONS" | grep -Eq 'tdd_red_agent_cannot_write_production'; then
|
|
1176
|
+
NOTE="Cclaw workflow guard: tdd-red agent is limited to test-side RED work and cannot edit production files."
|
|
1177
|
+
elif printf '%s' "$REASONS" | grep -Eq 'tdd_green_agent_cannot_write_tests'; then
|
|
1178
|
+
NOTE="Cclaw workflow guard: tdd-green agent can implement production fixes but should not author new RED tests."
|
|
1179
|
+
elif printf '%s' "$REASONS" | grep -Eq 'tdd_refactor_before_green'; then
|
|
1180
|
+
NOTE="Cclaw workflow guard: tdd-refactor requires a green_done cycle state before refactor edits."
|
|
1181
|
+
elif printf '%s' "$REASONS" | grep -Eq 'ship_preflight_required'; then
|
|
1182
|
+
NOTE="Cclaw workflow guard: ship finalization command detected before ship_preflight_passed gate. Run preflight and record evidence first."
|
|
1183
|
+
elif printf '%s' "$REASONS" | grep -Eq 'ship_review_coverage_required'; then
|
|
1184
|
+
NOTE="Cclaw workflow guard: ship finalization requires review layer coverage for spec/correctness/security/performance/architecture/external-safety in 07-review-army.json."
|
|
887
1185
|
elif printf '%s' "$REASONS" | grep -Eq 'tdd_cycle_counts_unavailable'; then
|
|
888
1186
|
NOTE="Cclaw workflow guard: unable to inspect run-scoped tdd-cycle counts (missing usable jq/python3/awk). Install one of these tools before writing production code in tdd."
|
|
889
|
-
elif printf '%s' "$REASONS" | grep -Eq 'direct_flow_state_edit'; then
|
|
890
|
-
NOTE="Cclaw workflow guard:
|
|
1187
|
+
elif printf '%s' "$REASONS" | grep -Eq 'runtime_write_requires_managed_only|direct_flow_state_edit'; then
|
|
1188
|
+
NOTE="Cclaw workflow guard: runtime write to managed ${RUNTIME_ROOT} internals detected (\${REASONS}). Prefer cclaw-managed helpers (stage-complete, sync, command contracts) instead of ad-hoc edits."
|
|
1189
|
+
elif printf '%s' "$REASONS" | grep -Eq 'mutating_without_recent_flow_read'; then
|
|
1190
|
+
NOTE="Cclaw workflow guard: mutating action requires a fresh read of ${RUNTIME_ROOT}/state/flow-state.json before edits."
|
|
891
1191
|
else
|
|
892
1192
|
NOTE="Cclaw workflow guard: detected potential flow violation (\${REASONS}). Re-read ${RUNTIME_ROOT}/state/flow-state.json, avoid source edits before tdd stage, and enforce RED -> GREEN -> REFACTOR discipline inside tdd."
|
|
893
1193
|
fi
|
|
@@ -910,10 +1210,31 @@ if [ -n "$REASONS" ]; then
|
|
|
910
1210
|
if printf '%s' "$REASONS" | grep -Eq 'implementation_write_before_'; then
|
|
911
1211
|
SHOULD_BLOCK="true"
|
|
912
1212
|
fi
|
|
913
|
-
if printf '%s' "$REASONS" | grep -Eq 'tdd_write_without_open_red' && [ "$TDD_ENFORCEMENT_MODE" = "strict" ]; then
|
|
1213
|
+
if printf '%s' "$REASONS" | grep -Eq 'tdd_write_without_open_red|tdd_write_without_red_for_path' && [ "$TDD_ENFORCEMENT_MODE" = "strict" ]; then
|
|
1214
|
+
SHOULD_BLOCK="true"
|
|
1215
|
+
fi
|
|
1216
|
+
if printf '%s' "$REASONS" | grep -Eq 'tdd_write_without_open_red|tdd_write_without_red_for_path' && iron_law_is_strict "tdd-red-before-write"; then
|
|
1217
|
+
SHOULD_BLOCK="true"
|
|
1218
|
+
fi
|
|
1219
|
+
if printf '%s' "$REASONS" | grep -Eq 'runtime_write_requires_managed_only|direct_flow_state_edit' && iron_law_is_strict "runtime-writes-managed-only"; then
|
|
1220
|
+
SHOULD_BLOCK="true"
|
|
1221
|
+
fi
|
|
1222
|
+
if printf '%s' "$REASONS" | grep -Eq 'mutating_without_recent_flow_read|stage_invocation_without_recent_flow_read' && iron_law_is_strict "flow-state-read-fresh"; then
|
|
1223
|
+
SHOULD_BLOCK="true"
|
|
1224
|
+
fi
|
|
1225
|
+
if printf '%s' "$REASONS" | grep -Eq 'ship_preflight_required' && iron_law_is_strict "ship-preflight-required"; then
|
|
1226
|
+
SHOULD_BLOCK="true"
|
|
1227
|
+
fi
|
|
1228
|
+
if printf '%s' "$REASONS" | grep -Eq 'ship_review_coverage_required' && iron_law_is_strict "review-coverage-complete-before-ship"; then
|
|
914
1229
|
SHOULD_BLOCK="true"
|
|
915
1230
|
fi
|
|
916
|
-
if printf '%s' "$REASONS" | grep -Eq '
|
|
1231
|
+
if printf '%s' "$REASONS" | grep -Eq 'implementation_write_before_plan_completion' && iron_law_is_strict "plan-requires-approval"; then
|
|
1232
|
+
SHOULD_BLOCK="true"
|
|
1233
|
+
fi
|
|
1234
|
+
if printf '%s' "$REASONS" | grep -Eq 'tdd_cycle_counts_unavailable|tdd_red_evidence_check_failed'; then
|
|
1235
|
+
SHOULD_BLOCK="true"
|
|
1236
|
+
fi
|
|
1237
|
+
if printf '%s' "$REASONS" | grep -Eq 'tdd_red_agent_cannot_write_production|tdd_green_agent_cannot_write_tests|tdd_refactor_before_green'; then
|
|
917
1238
|
SHOULD_BLOCK="true"
|
|
918
1239
|
fi
|
|
919
1240
|
if [ "$WORKFLOW_GUARD_MODE" = "strict" ] || [ "$SHOULD_BLOCK" = "true" ]; then
|
|
@@ -932,25 +1253,214 @@ export function contextMonitorScript() {
|
|
|
932
1253
|
# Advisory-only context pressure warnings (best effort).
|
|
933
1254
|
set -uo pipefail
|
|
934
1255
|
|
|
935
|
-
HARNESS="codex"
|
|
936
|
-
if [ -n "\${CLAUDE_PROJECT_DIR:-}" ]; then
|
|
937
|
-
HARNESS="claude"
|
|
938
|
-
elif [ -n "\${CURSOR_PROJECT_DIR:-}" ] || [ -n "\${CURSOR_PROJECT_ROOT:-}" ]; then
|
|
939
|
-
HARNESS="cursor"
|
|
940
|
-
elif [ -n "\${OPENCODE_PROJECT_DIR:-}" ] || [ -n "\${OPENCODE_PROJECT_ROOT:-}" ]; then
|
|
941
|
-
HARNESS="opencode"
|
|
942
|
-
fi
|
|
943
|
-
|
|
944
1256
|
${RUNTIME_SHELL_DETECT_ROOT}
|
|
945
1257
|
|
|
946
1258
|
STATE_DIR="$ROOT/${RUNTIME_ROOT}/state"
|
|
947
1259
|
MONITOR_STATE="$STATE_DIR/context-monitor.json"
|
|
948
1260
|
WARNINGS_FILE="$STATE_DIR/context-warnings.jsonl"
|
|
1261
|
+
FLOW_STATE_FILE="$STATE_DIR/flow-state.json"
|
|
1262
|
+
TDD_AUTO_EVIDENCE_FILE="$STATE_DIR/tdd-red-evidence.jsonl"
|
|
949
1263
|
mkdir -p "$STATE_DIR" 2>/dev/null || true
|
|
950
1264
|
|
|
951
1265
|
INPUT=$(cat 2>/dev/null || echo '{}')
|
|
952
1266
|
[ -n "$INPUT" ] || exit 0
|
|
953
1267
|
|
|
1268
|
+
CURRENT_STAGE="none"
|
|
1269
|
+
CURRENT_RUN="active"
|
|
1270
|
+
if command -v cclaw_hook_read_flow_state_minimal >/dev/null 2>&1; then
|
|
1271
|
+
cclaw_hook_read_flow_state_minimal "$FLOW_STATE_FILE"
|
|
1272
|
+
CURRENT_STAGE="\${CCLAW_HOOK_FLOW_STAGE:-none}"
|
|
1273
|
+
CURRENT_RUN="\${CCLAW_HOOK_FLOW_RUN_ID:-active}"
|
|
1274
|
+
elif [ -f "$FLOW_STATE_FILE" ]; then
|
|
1275
|
+
if command -v jq >/dev/null 2>&1; then
|
|
1276
|
+
CURRENT_STAGE=$(jq -r '.currentStage // "none"' "$FLOW_STATE_FILE" 2>/dev/null || echo "none")
|
|
1277
|
+
CURRENT_RUN=$(jq -r '.activeRunId // "active"' "$FLOW_STATE_FILE" 2>/dev/null || echo "active")
|
|
1278
|
+
elif command -v python3 >/dev/null 2>&1; then
|
|
1279
|
+
FLOW_META=$(python3 - "$FLOW_STATE_FILE" <<'PY'
|
|
1280
|
+
import json
|
|
1281
|
+
import sys
|
|
1282
|
+
stage = "none"
|
|
1283
|
+
run_id = "active"
|
|
1284
|
+
try:
|
|
1285
|
+
with open(sys.argv[1], "r", encoding="utf-8") as fh:
|
|
1286
|
+
payload = json.load(fh)
|
|
1287
|
+
stage_value = payload.get("currentStage")
|
|
1288
|
+
run_value = payload.get("activeRunId")
|
|
1289
|
+
if isinstance(stage_value, str) and stage_value:
|
|
1290
|
+
stage = stage_value
|
|
1291
|
+
if isinstance(run_value, str) and run_value:
|
|
1292
|
+
run_id = run_value
|
|
1293
|
+
except Exception:
|
|
1294
|
+
pass
|
|
1295
|
+
print(stage)
|
|
1296
|
+
print(run_id)
|
|
1297
|
+
PY
|
|
1298
|
+
)
|
|
1299
|
+
{
|
|
1300
|
+
IFS= read -r CURRENT_STAGE
|
|
1301
|
+
IFS= read -r CURRENT_RUN
|
|
1302
|
+
} <<EOF
|
|
1303
|
+
$FLOW_META
|
|
1304
|
+
EOF
|
|
1305
|
+
fi
|
|
1306
|
+
fi
|
|
1307
|
+
|
|
1308
|
+
AUTO_TOOL=""
|
|
1309
|
+
AUTO_COMMAND=""
|
|
1310
|
+
AUTO_EXIT_CODE=""
|
|
1311
|
+
AUTO_PATHS_CSV=""
|
|
1312
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
1313
|
+
AUTO_META=$(INPUT_JSON="$INPUT" python3 - <<'PY'
|
|
1314
|
+
import json
|
|
1315
|
+
import os
|
|
1316
|
+
import re
|
|
1317
|
+
from typing import Any, Iterator
|
|
1318
|
+
|
|
1319
|
+
raw = os.environ.get("INPUT_JSON", "{}")
|
|
1320
|
+
try:
|
|
1321
|
+
payload = json.loads(raw)
|
|
1322
|
+
except Exception:
|
|
1323
|
+
payload = {}
|
|
1324
|
+
|
|
1325
|
+
def walk(node: Any) -> Iterator[Any]:
|
|
1326
|
+
if isinstance(node, dict):
|
|
1327
|
+
yield node
|
|
1328
|
+
for value in node.values():
|
|
1329
|
+
yield from walk(value)
|
|
1330
|
+
elif isinstance(node, list):
|
|
1331
|
+
for value in node:
|
|
1332
|
+
yield from walk(value)
|
|
1333
|
+
|
|
1334
|
+
def first_string(keys: list[str]) -> str:
|
|
1335
|
+
for node in walk(payload):
|
|
1336
|
+
if not isinstance(node, dict):
|
|
1337
|
+
continue
|
|
1338
|
+
for key in keys:
|
|
1339
|
+
value = node.get(key)
|
|
1340
|
+
if isinstance(value, str) and value.strip():
|
|
1341
|
+
return value.strip()
|
|
1342
|
+
return ""
|
|
1343
|
+
|
|
1344
|
+
tool = first_string(["tool_name", "tool", "toolName", "name", "id"])
|
|
1345
|
+
command = ""
|
|
1346
|
+
for node in walk(payload):
|
|
1347
|
+
if not isinstance(node, dict):
|
|
1348
|
+
continue
|
|
1349
|
+
for key in ("command", "cmd"):
|
|
1350
|
+
value = node.get(key)
|
|
1351
|
+
if isinstance(value, str) and value.strip():
|
|
1352
|
+
command = value.strip()
|
|
1353
|
+
break
|
|
1354
|
+
if command:
|
|
1355
|
+
break
|
|
1356
|
+
|
|
1357
|
+
exit_code = ""
|
|
1358
|
+
for node in walk(payload):
|
|
1359
|
+
if not isinstance(node, dict):
|
|
1360
|
+
continue
|
|
1361
|
+
for key in ("exitCode", "exit_code", "code", "status"):
|
|
1362
|
+
value = node.get(key)
|
|
1363
|
+
if isinstance(value, bool):
|
|
1364
|
+
exit_code = "0" if value else "1"
|
|
1365
|
+
break
|
|
1366
|
+
if isinstance(value, (int, float)):
|
|
1367
|
+
exit_code = str(int(value))
|
|
1368
|
+
break
|
|
1369
|
+
if exit_code:
|
|
1370
|
+
break
|
|
1371
|
+
|
|
1372
|
+
blob_parts: list[str] = []
|
|
1373
|
+
for node in walk(payload):
|
|
1374
|
+
if not isinstance(node, dict):
|
|
1375
|
+
continue
|
|
1376
|
+
for key in ("stderr", "stdout", "output", "text", "message"):
|
|
1377
|
+
value = node.get(key)
|
|
1378
|
+
if isinstance(value, str) and value:
|
|
1379
|
+
blob_parts.append(value)
|
|
1380
|
+
blob_parts.append(command)
|
|
1381
|
+
blob = "\\n".join(blob_parts)
|
|
1382
|
+
path_pattern = re.compile(r"(?:[A-Za-z0-9_.-]+/)+[A-Za-z0-9_.-]+\.(?:ts|tsx|js|jsx|mjs|cjs|py|go|rs|java|kt|rb|php|cs|swift)")
|
|
1383
|
+
seen: set[str] = set()
|
|
1384
|
+
paths: list[str] = []
|
|
1385
|
+
for match in path_pattern.findall(blob):
|
|
1386
|
+
normalized = match.strip().strip("\\"'.,:;()[]{}<>")
|
|
1387
|
+
if not normalized or normalized in seen:
|
|
1388
|
+
continue
|
|
1389
|
+
seen.add(normalized)
|
|
1390
|
+
paths.append(normalized)
|
|
1391
|
+
|
|
1392
|
+
print(tool.replace("\\t", " ").replace("\\n", " "))
|
|
1393
|
+
print(command.replace("\\t", " ").replace("\\n", " "))
|
|
1394
|
+
print(exit_code)
|
|
1395
|
+
print(",".join(paths[:20]).replace("\\t", " ").replace("\\n", " "))
|
|
1396
|
+
PY
|
|
1397
|
+
)
|
|
1398
|
+
{
|
|
1399
|
+
IFS= read -r AUTO_TOOL
|
|
1400
|
+
IFS= read -r AUTO_COMMAND
|
|
1401
|
+
IFS= read -r AUTO_EXIT_CODE
|
|
1402
|
+
IFS= read -r AUTO_PATHS_CSV
|
|
1403
|
+
} <<EOF
|
|
1404
|
+
$AUTO_META
|
|
1405
|
+
EOF
|
|
1406
|
+
fi
|
|
1407
|
+
|
|
1408
|
+
if [ "$CURRENT_STAGE" = "tdd" ] && [ -n "$AUTO_COMMAND" ] && [ -n "$AUTO_EXIT_CODE" ]; then
|
|
1409
|
+
if command -v cclaw_hook_lower >/dev/null 2>&1; then
|
|
1410
|
+
AUTO_COMMAND_LOWER=$(cclaw_hook_lower "$AUTO_COMMAND")
|
|
1411
|
+
else
|
|
1412
|
+
AUTO_COMMAND_LOWER=$(printf '%s' "$AUTO_COMMAND" | tr '[:upper:]' '[:lower:]')
|
|
1413
|
+
fi
|
|
1414
|
+
if printf '%s' "$AUTO_COMMAND_LOWER" | grep -Eq '(npm test|npm run test|pnpm test|pnpm run test|yarn test|bun test|vitest|jest|pytest|go test|cargo test|mvn test|gradle test|dotnet test)'; then
|
|
1415
|
+
if printf '%s' "$AUTO_EXIT_CODE" | grep -Eq '^-?[0-9]+$' && [ "$AUTO_EXIT_CODE" -ne 0 ]; then
|
|
1416
|
+
TS_AUTO=$(date -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || echo "")
|
|
1417
|
+
if command -v jq >/dev/null 2>&1; then
|
|
1418
|
+
AUTO_ENTRY=$(jq -n -c \
|
|
1419
|
+
--arg ts "$TS_AUTO" \
|
|
1420
|
+
--arg run "$CURRENT_RUN" \
|
|
1421
|
+
--arg command "$AUTO_COMMAND" \
|
|
1422
|
+
--arg tool "$AUTO_TOOL" \
|
|
1423
|
+
--argjson exitCode "$AUTO_EXIT_CODE" \
|
|
1424
|
+
--arg paths "$AUTO_PATHS_CSV" \
|
|
1425
|
+
'{
|
|
1426
|
+
ts: $ts,
|
|
1427
|
+
runId: $run,
|
|
1428
|
+
stage: "tdd",
|
|
1429
|
+
source: "posttool-auto",
|
|
1430
|
+
command: $command,
|
|
1431
|
+
tool: $tool,
|
|
1432
|
+
exitCode: $exitCode,
|
|
1433
|
+
paths: ($paths | split(",") | map(select(length > 0)))
|
|
1434
|
+
}' 2>/dev/null || echo "")
|
|
1435
|
+
elif command -v python3 >/dev/null 2>&1; then
|
|
1436
|
+
AUTO_ENTRY=$(python3 - "$TS_AUTO" "$CURRENT_RUN" "$AUTO_COMMAND" "$AUTO_TOOL" "$AUTO_EXIT_CODE" "$AUTO_PATHS_CSV" <<'PY'
|
|
1437
|
+
import json
|
|
1438
|
+
import sys
|
|
1439
|
+
ts, run_id, command, tool, exit_code, paths_csv = sys.argv[1:7]
|
|
1440
|
+
paths = [value for value in paths_csv.split(",") if value]
|
|
1441
|
+
entry = {
|
|
1442
|
+
"ts": ts,
|
|
1443
|
+
"runId": run_id,
|
|
1444
|
+
"stage": "tdd",
|
|
1445
|
+
"source": "posttool-auto",
|
|
1446
|
+
"command": command,
|
|
1447
|
+
"tool": tool,
|
|
1448
|
+
"exitCode": int(exit_code),
|
|
1449
|
+
"paths": paths
|
|
1450
|
+
}
|
|
1451
|
+
print(json.dumps(entry, ensure_ascii=False))
|
|
1452
|
+
PY
|
|
1453
|
+
)
|
|
1454
|
+
else
|
|
1455
|
+
AUTO_ENTRY=""
|
|
1456
|
+
fi
|
|
1457
|
+
if [ -n "$AUTO_ENTRY" ]; then
|
|
1458
|
+
printf '%s\n' "$AUTO_ENTRY" >> "$TDD_AUTO_EVIDENCE_FILE" 2>/dev/null || true
|
|
1459
|
+
fi
|
|
1460
|
+
fi
|
|
1461
|
+
fi
|
|
1462
|
+
fi
|
|
1463
|
+
|
|
954
1464
|
REMAINING_PERCENT=""
|
|
955
1465
|
if command -v python3 >/dev/null 2>&1; then
|
|
956
1466
|
REMAINING_PERCENT=$(INPUT_JSON="$INPUT" python3 - <<'PY'
|