@frankzhang2026/opencode-android-orchestrator 1.0.5 → 1.1.0

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 (107) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/README.md +76 -15
  3. package/dist/config/queue-policy.d.ts.map +1 -1
  4. package/dist/config/queue-policy.js +1 -2
  5. package/dist/config/queue-policy.js.map +1 -1
  6. package/dist/doctor/index.d.ts.map +1 -1
  7. package/dist/doctor/index.js +14 -3
  8. package/dist/doctor/index.js.map +1 -1
  9. package/dist/doctor/installation.d.ts +1 -1
  10. package/dist/doctor/installation.d.ts.map +1 -1
  11. package/dist/doctor/installation.js +10 -1
  12. package/dist/doctor/installation.js.map +1 -1
  13. package/dist/index.d.ts +1 -0
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +1 -0
  16. package/dist/index.js.map +1 -1
  17. package/dist/installer/adaptive-templates.d.ts +4 -1
  18. package/dist/installer/adaptive-templates.d.ts.map +1 -1
  19. package/dist/installer/adaptive-templates.js +38 -13
  20. package/dist/installer/adaptive-templates.js.map +1 -1
  21. package/dist/installer/android-project.d.ts +3 -1
  22. package/dist/installer/android-project.d.ts.map +1 -1
  23. package/dist/installer/android-project.js.map +1 -1
  24. package/dist/installer/capabilities-script.d.ts +2 -0
  25. package/dist/installer/capabilities-script.d.ts.map +1 -0
  26. package/dist/installer/capabilities-script.js +77 -0
  27. package/dist/installer/capabilities-script.js.map +1 -0
  28. package/dist/installer/gradle-verification.d.ts.map +1 -1
  29. package/dist/installer/gradle-verification.js +13 -6
  30. package/dist/installer/gradle-verification.js.map +1 -1
  31. package/dist/installer/init.d.ts.map +1 -1
  32. package/dist/installer/init.js +2 -1
  33. package/dist/installer/init.js.map +1 -1
  34. package/dist/installer/opencode-config.d.ts +3 -3
  35. package/dist/installer/opencode-config.js +1 -1
  36. package/dist/installer/project-capabilities.d.ts +35 -0
  37. package/dist/installer/project-capabilities.d.ts.map +1 -0
  38. package/dist/installer/project-capabilities.js +82 -0
  39. package/dist/installer/project-capabilities.js.map +1 -0
  40. package/dist/installer/upgrade.d.ts.map +1 -1
  41. package/dist/installer/upgrade.js +9 -1
  42. package/dist/installer/upgrade.js.map +1 -1
  43. package/dist/queue/cli.d.ts.map +1 -1
  44. package/dist/queue/cli.js +32 -5
  45. package/dist/queue/cli.js.map +1 -1
  46. package/dist/queue/executor.d.ts +1 -0
  47. package/dist/queue/executor.d.ts.map +1 -1
  48. package/dist/queue/executor.js +155 -11
  49. package/dist/queue/executor.js.map +1 -1
  50. package/dist/queue/process-ownership.d.ts +12 -0
  51. package/dist/queue/process-ownership.d.ts.map +1 -0
  52. package/dist/queue/process-ownership.js +58 -0
  53. package/dist/queue/process-ownership.js.map +1 -0
  54. package/dist/queue/queue.d.ts +25 -1
  55. package/dist/queue/queue.d.ts.map +1 -1
  56. package/dist/queue/queue.js +96 -11
  57. package/dist/queue/queue.js.map +1 -1
  58. package/dist/queue/service.d.ts.map +1 -1
  59. package/dist/queue/service.js +19 -2
  60. package/dist/queue/service.js.map +1 -1
  61. package/dist/queue/storage.d.ts +3 -1
  62. package/dist/queue/storage.d.ts.map +1 -1
  63. package/dist/queue/storage.js +25 -15
  64. package/dist/queue/storage.js.map +1 -1
  65. package/dist/queue/supervision.d.ts +38 -0
  66. package/dist/queue/supervision.d.ts.map +1 -0
  67. package/dist/queue/supervision.js +247 -0
  68. package/dist/queue/supervision.js.map +1 -0
  69. package/dist/queue/tools.d.ts.map +1 -1
  70. package/dist/queue/tools.js +4 -2
  71. package/dist/queue/tools.js.map +1 -1
  72. package/docs/MIGRATION.md +110 -9
  73. package/docs/QUEUE.md +350 -16
  74. package/docs/SECURITY.md +5 -2
  75. package/docs/TROUBLESHOOTING.md +82 -8
  76. package/package.json +1 -1
  77. package/templates/.opencode/agents/scheduled-coder.md +14 -1
  78. package/templates/.opencode/agents/scheduled-planner.md +64 -3
  79. package/templates/.opencode/skills/scheduled-quality-coder/SKILL.md +29 -3
  80. package/templates/.opencode/skills/scheduled-quality-orchestrator/SKILL.md +65 -4
  81. package/templates/.opencode/skills/scheduled-quality-reviewer/SKILL.md +19 -1
  82. package/templates/AGENTS.md.fragment +10 -4
  83. package/templates/README.md +3 -1
  84. package/templates/automation/config.schema.json +212 -22
  85. package/templates/automation/task-contract.schema.json +573 -49
  86. package/templates/automation/tasks/TASK-TEMPLATE.json.example +9 -4
  87. package/templates/automation/verification/collect.init.gradle +98 -0
  88. package/templates/automation/verification/contract.cjs +93 -0
  89. package/templates/automation/verification/inventory.cjs +433 -0
  90. package/templates/automation/verification/project.cjs +172 -0
  91. package/templates/automation/verification/recovery.cjs +426 -0
  92. package/templates/scripts/automation/acceptance-report.sh +17 -0
  93. package/templates/scripts/automation/claim-task.sh +35 -2
  94. package/templates/scripts/automation/integration-scope-gate.sh +3 -3
  95. package/templates/scripts/automation/lib.sh +61 -19
  96. package/templates/scripts/automation/orchestrate-task.sh +7 -1
  97. package/templates/scripts/automation/preflight.sh +1 -2
  98. package/templates/scripts/automation/quality-gate.sh +10 -1
  99. package/templates/scripts/automation/record-red.sh +13 -1
  100. package/templates/scripts/automation/resume-task.sh +3 -0
  101. package/templates/scripts/automation/scope-gate.sh +4 -4
  102. package/templates/scripts/automation/show-acceptance-review.sh +1 -0
  103. package/templates/scripts/automation/status.sh +32 -0
  104. package/templates/scripts/automation/submit-review.sh +10 -1
  105. package/templates/scripts/automation/validate-contract.sh +10 -1
  106. package/templates/scripts/automation/verify-integration.sh +3 -0
  107. package/templates/scripts/automation/verify-task.sh +3 -0
@@ -109,14 +109,14 @@ automation_validate_config() {
109
109
  def gradle_task:
110
110
  type == "string" and
111
111
  test("^(?:[A-Za-z][A-Za-z0-9_.-]*|(?::[A-Za-z0-9_.-]+)+)$");
112
- def gradle_task_list:
112
+ def optional_gradle_task_list:
113
113
  type == "array" and
114
- length > 0 and
115
114
  length == (unique | length) and
116
115
  all(.[]; gradle_task);
116
+ def gradle_task_list:
117
+ optional_gradle_task_list and length > 0;
117
118
  (.schemaVersion == 5 or .schemaVersion == 6) and
118
119
  ((.commitPolicy // "humanApproval") == "humanApproval" or .commitPolicy == "autoCommit") and
119
- (.workspaceStrategy != "isolatedWorktree" or (.commitPolicy // "humanApproval") == "humanApproval") and
120
120
  (.enabled | type == "boolean") and
121
121
  (.mode == "shadow" or .mode == "orchestrated") and
122
122
  (.workspaceStrategy == "inPlaceExclusive" or .workspaceStrategy == "isolatedWorktree") and
@@ -149,8 +149,9 @@ automation_validate_config() {
149
149
  ($verification.fullUnitTestTasks | gradle_task_list) and
150
150
  ($verification.focusedTestTasks | gradle_task_list) and
151
151
  ($verification.assembleTasks | gradle_task_list) and
152
- ($verification.lintTasks | gradle_task_list) and
153
- ($verification.deviceTestTasks | gradle_task_list)) and
152
+ ($verification.lintTasks | optional_gradle_task_list) and
153
+ ($verification.deviceTestTasks | optional_gradle_task_list)) and
154
+ ((.lintEnabled | not) or (.gradleVerification.lintTasks | length > 0)) and
154
155
  (.androidProject as $project |
155
156
  ($project | type == "object") and
156
157
  ($project.name | type == "string" and length > 0) and
@@ -163,16 +164,19 @@ automation_validate_config() {
163
164
  (.directory | repository_path) and
164
165
  (.buildFile | repository_path) and
165
166
  (.dsl == "kotlin" or .dsl == "groovy") and
166
- (.type == "application" or .type == "library" or .type == "dynamic-feature" or .type == "test" or .type == "asset-pack") and
167
+ (.type == "application" or .type == "library" or .type == "dynamic-feature" or .type == "test" or .type == "asset-pack" or ($project.capabilities.version == 1 and .type == "jvm-library")) and
167
168
  (.namespace == null or (.namespace | type == "string" and length > 0)) and
168
169
  (.applicationId == null or (.applicationId | type == "string" and length > 0)))) and
169
170
  any($project.modules[]; .gradlePath == $project.primaryModule) and
170
171
  ($project.productionPaths | type == "array" and length > 0 and all(.[];
171
- repository_path and test("(^|/)src/main/\\*\\*$"))) and
172
+ repository_path and ($project.capabilities.version == 1 or test("(^|/)src/main/\\*\\*$")))) and
172
173
  ($project.testPaths | type == "array" and length > 0 and all(.[];
173
- repository_path and test("(^|/)src/(?:test|androidTest)/\\*\\*$")))) and
174
+ repository_path and ($project.capabilities.version == 1 or test("(^|/)src/(?:test|androidTest)/\\*\\*$"))))) and
174
175
  (.protectedPaths | type == "array" and length > 0 and all(.[]; repository_path))
175
176
  ' "$AUTOMATION_CONFIG" >/dev/null || automation_die "automation/config.json is invalid"
177
+ if jq -e '.androidProject | has("capabilities")' "$AUTOMATION_CONFIG" >/dev/null; then
178
+ node "$AUTOMATION_ROOT/automation/verification/project.cjs" "$AUTOMATION_CONFIG" "$AUTOMATION_ROOT" || automation_die "Android capability snapshot is invalid"
179
+ fi
176
180
  }
177
181
 
178
182
  automation_require_orchestrated() {
@@ -250,7 +254,7 @@ automation_require_queue_execution() {
250
254
  return 1
251
255
  }
252
256
  if ! worker_pid="$(jq -er --arg key "$queue_key" --arg run "$AUTOMATION_QUEUE_RUN_ID" \
253
- 'select(.active.key == $key and .active.id == $run) | .active.worker.pid' \
257
+ 'select(.active.key == $key and .active.id == $run and (.active.supervision == null or .active.supervision.state == "RUNNING")) | .active.worker.pid' \
254
258
  "$AUTOMATION_RUNTIME_ROOT/inbox/queue.json")"; then
255
259
  automation_die "current process does not own this task queue execution"
256
260
  return 1
@@ -414,14 +418,26 @@ automation_gradle_group_command_json() {
414
418
  automation_validate_config || return 1
415
419
  jq -ce \
416
420
  --arg group "$group" \
417
- '["./gradlew"] + .gradleVerification[$group]' \
421
+ --arg supervised "${AUTOMATION_WORKER_TOKEN:+yes}" \
422
+ '["./gradlew"] + .gradleVerification[$group] + (if $supervised == "yes" then ["--no-daemon"] else [] end)' \
418
423
  "$AUTOMATION_CONFIG"
419
424
  }
420
425
 
426
+ # Keep Git/scope/evidence operations at the repository root. Only Gradle changes
427
+ # cwd, resolving the persisted relative build selection in the current worktree.
428
+ automation_gradle_build_root() {
429
+ local root="${1:-$AUTOMATION_ROOT}"
430
+ if jq -e '.androidProject.capabilities != null' "$AUTOMATION_CONFIG" >/dev/null; then
431
+ node "$AUTOMATION_ROOT/automation/verification/project.cjs" "$AUTOMATION_CONFIG" "$root" --build-root
432
+ else
433
+ printf '%s\n' "$root"
434
+ fi
435
+ }
436
+
421
437
  automation_run_gradle_group() {
422
438
  local group="$1"
423
439
  local root="${2:-$AUTOMATION_ROOT}"
424
- local task
440
+ local task build_root
425
441
  local -a tasks=()
426
442
 
427
443
  automation_validate_gradle_group "$group" || return 1
@@ -440,8 +456,10 @@ automation_run_gradle_group() {
440
456
  return $?
441
457
  fi
442
458
  (
443
- cd "$root"
444
- ./gradlew "${tasks[@]}"
459
+ local build_root
460
+ build_root="$(automation_gradle_build_root "$root")" || exit 1
461
+ cd "$build_root" || exit 1
462
+ ./gradlew "${tasks[@]}" ${AUTOMATION_WORKER_TOKEN:+--no-daemon}
445
463
  )
446
464
  }
447
465
 
@@ -451,7 +469,7 @@ automation_run_gradle_group() {
451
469
  automation_run_fresh_unit_tests() {
452
470
  local root="$1"
453
471
  shift
454
- local init_file log_file result elapsed start
472
+ local init_file log_file result elapsed start build_root
455
473
  mkdir -p "$AUTOMATION_RUNTIME_ROOT/gradle"
456
474
  init_file="$(mktemp "$AUTOMATION_RUNTIME_ROOT/gradle/fresh-tests.XXXXXX")"
457
475
  log_file="${init_file}.log"
@@ -481,7 +499,7 @@ gradle.taskGraph.afterTask { task, state ->
481
499
  GRADLE
482
500
  start="$(date +%s)"
483
501
  set +e
484
- (cd "$root" && ./gradlew "$@" --no-configuration-cache --console=plain --init-script "$init_file") 2>&1 | tee "$log_file"
502
+ (build_root="$(automation_gradle_build_root "$root")" && cd "$build_root" && ./gradlew "$@" ${AUTOMATION_WORKER_TOKEN:+--no-daemon} --no-configuration-cache --console=plain --init-script "$init_file") 2>&1 | tee "$log_file"
485
503
  result=${PIPESTATUS[0]}
486
504
  set -e
487
505
  elapsed=$(( $(date +%s) - start ))
@@ -522,10 +540,14 @@ automation_run_configured_unit_tests() {
522
540
  return 0
523
541
  fi
524
542
 
543
+ if [[ "$(jq -r '.schemaVersion' "$contract")" -ge "4" ]]; then
544
+ automation_run_inventory green "$(jq -er '.id' "$contract")" "$root" || return 1
545
+ else
525
546
  while IFS=$'\t' read -r gradle_task filter; do
526
547
  automation_info "running ${context}focused test ($gradle_task): $filter"
527
548
  automation_run_focused_test "$gradle_task" "$filter" "$root"
528
549
  done < <(jq -r '.targetTests[] | [.gradleTask, .filter] | @tsv' "$contract")
550
+ fi
529
551
 
530
552
  automation_info "running ${context}full unit tests"
531
553
  automation_run_gradle_group "fullUnitTestTasks" "$root"
@@ -561,8 +583,10 @@ automation_run_focused_test() {
561
583
  }
562
584
 
563
585
  (
564
- cd "$root"
565
- ./gradlew "$task" --tests "$filter"
586
+ local build_root
587
+ build_root="$(automation_gradle_build_root "$root")" || exit 1
588
+ cd "$build_root" || exit 1
589
+ ./gradlew "$task" --tests "$filter" ${AUTOMATION_WORKER_TOKEN:+--no-daemon}
566
590
  )
567
591
  }
568
592
 
@@ -575,7 +599,7 @@ automation_run_classified_focused_test() {
575
599
  local root="$3"
576
600
  local result_file="$4"
577
601
  local log_file="$5"
578
- local init_file status
602
+ local init_file status build_root
579
603
 
580
604
  automation_validate_config || return 1
581
605
  automation_validate_gradle_task "$task" || return 1
@@ -644,7 +668,7 @@ gradle.allprojects { project ->
644
668
  GRADLE
645
669
 
646
670
  set +e
647
- (cd "$root" && ./gradlew "$task" --tests "$filter" \
671
+ (build_root="$(automation_gradle_build_root "$root")" && cd "$build_root" && ./gradlew "$task" --tests "$filter" ${AUTOMATION_WORKER_TOKEN:+--no-daemon} \
648
672
  --no-configuration-cache --console=plain --init-script "$init_file" \
649
673
  "-Dorchestrator.caseResultFile=$result_file") 2>&1 | tee "$log_file"
650
674
  status=${PIPESTATUS[0]}
@@ -674,6 +698,24 @@ automation_test_diff_sha() {
674
698
  } | shasum -a 256 | awk '{print $1}'
675
699
  }
676
700
 
701
+ automation_run_inventory() {
702
+ local phase="$1" task_id="$2" root="${3:-$AUTOMATION_ROOT}"
703
+ node "$AUTOMATION_ROOT/automation/verification/inventory.cjs" "$phase" \
704
+ "$(automation_contract_path "$task_id")" "$AUTOMATION_CONFIG" "$root" "$(automation_evidence_path "$task_id")"
705
+ }
706
+
707
+ # Discovery only classifies evidence inputs. Scope gates still enforce the
708
+ # approved paths, forbidden paths, protected files and the file-count limit.
709
+ automation_is_task_test_path() {
710
+ local task_id="$1" path="$2"
711
+ if [[ "$(jq -r '.schemaVersion' "$(automation_contract_path "$task_id")")" -ge "4" ]]; then
712
+ node "$AUTOMATION_ROOT/automation/verification/inventory.cjs" is-test \
713
+ "$(automation_evidence_path "$task_id")/baseline-inventory.json" "$AUTOMATION_CONFIG" "$AUTOMATION_ROOT" "$path"
714
+ else
715
+ automation_array_matches_path "$AUTOMATION_CONFIG" '.androidProject.testPaths' "$path"
716
+ fi
717
+ }
718
+
677
719
  automation_require_approval() {
678
720
  local kind="$1"
679
721
  local supplied="$2"
@@ -63,7 +63,13 @@ run_agent() {
63
63
  return "$exit_code"
64
64
  }
65
65
 
66
- for _step in 1 2 3 4 5 6 7 8; do
66
+ # Each review correction consumes four transitions: resume, code, begin review,
67
+ # and review. Include the initial three transitions, budget-exhaustion
68
+ # transition, and the final state check.
69
+ # The bound must agree with the approved number of review correction cycles.
70
+ max_review_cycles="$(automation_config_value '.maxReviewCycles')"
71
+ max_steps=$((4 * (max_review_cycles + 1) + 1))
72
+ for ((_step = 0; _step < max_steps; _step++)); do
67
73
  if [[ -n "${AUTOMATION_QUEUE_RUN_ID:-}" ]] && jq -e --arg id "$task_id" \
68
74
  'any(.items[]; .taskId == $id and .request.kind == "abort")' \
69
75
  "$AUTOMATION_RUNTIME_ROOT/inbox/queue.json" >/dev/null; then
@@ -34,9 +34,8 @@ automation_require_command shasum
34
34
  if [[ "${AUTOMATION_TEST_MODE:-0}" != "1" ]]; then
35
35
  automation_require_command opencode
36
36
  fi
37
- [[ -x "$AUTOMATION_ROOT/gradlew" ]] || automation_die "gradlew is missing or not executable"
38
-
39
37
  automation_validate_config
38
+ [[ -x "$(automation_gradle_build_root)/gradlew" ]] || automation_die "selected Gradle build wrapper is missing or not executable"
40
39
  workspace_strategy="$(automation_config_value '.workspaceStrategy')"
41
40
  if [[ "$workspace_strategy" == "isolatedWorktree" ]]; then
42
41
  automation_worktree_base >/dev/null
@@ -33,10 +33,19 @@ max_attempts=$((max_fix_loops + 1))
33
33
  attempt_log="$evidence_dir/gate-cycle-$coding_cycle-attempt-$attempt.log"
34
34
  started_at="$(automation_now)"
35
35
  set +e
36
- "$SCRIPT_DIR/verify-task.sh" "$task_id" 2>&1 | tee "$attempt_log"
36
+ if [[ "$(jq -r '.schemaVersion' "$contract")" -ge "7" ]]; then
37
+ node "$AUTOMATION_ROOT/automation/verification/recovery.cjs" --stage green "$contract" "$AUTOMATION_CONFIG" "$AUTOMATION_ROOT" "$evidence_dir" 2>&1 | tee "$attempt_log"
38
+ else
39
+ "$SCRIPT_DIR/verify-task.sh" "$task_id" 2>&1 | tee "$attempt_log"
40
+ fi
37
41
  gate_status=${PIPESTATUS[0]}
38
42
  set -e
39
43
 
44
+ if [[ "$(jq -r '.schemaVersion' "$contract")" -ge "7" && ( "$gate_status" -eq 75 || "$gate_status" -eq 76 ) ]]; then
45
+ automation_transition_state "$task_id" "CODING" "BLOCKED" "quality-gate" "verification stopped; inspect stage-recovery/green.json; implementation budget retained"
46
+ exit "$gate_status"
47
+ fi
48
+
40
49
  jq -n \
41
50
  --arg taskId "$task_id" \
42
51
  --argjson attempts "$attempt" \
@@ -19,6 +19,17 @@ mkdir -p "$evidence_dir"
19
19
  red_meta="$evidence_dir/red.json"
20
20
  [[ ! -e "$red_meta" ]] || automation_die "RED evidence already exists for $task_id"
21
21
 
22
+ if [[ "$schema_version" -ge "4" ]]; then
23
+ [[ "$#" -eq 1 ]] || automation_die "Schema V4/V5 usage: record-red.sh TASK-ID"
24
+ "$SCRIPT_DIR/scope-gate.sh" "$task_id" >/dev/null
25
+ if [[ "$schema_version" -ge "7" ]]; then
26
+ node "$AUTOMATION_ROOT/automation/verification/recovery.cjs" --stage red "$contract" "$AUTOMATION_CONFIG" "$AUTOMATION_ROOT" "$evidence_dir"
27
+ exit $?
28
+ fi
29
+ automation_run_inventory red "$task_id"
30
+ exit 0
31
+ fi
32
+
22
33
  # Preserve the exact V1/V2 behavior for already approved contracts.
23
34
  if [[ "$schema_version" != "3" ]]; then
24
35
  expected="${2:-}"
@@ -43,9 +54,10 @@ if [[ "$schema_version" != "3" ]]; then
43
54
  rg -F "$expected" "$red_log" >/dev/null || automation_die "RED output does not contain the expected failure text"
44
55
  jq -n --arg taskId "$task_id" --arg startedAt "$started_at" --arg finishedAt "$(automation_now)" \
45
56
  --arg expectedFailure "$expected" --arg gradleTask "$gradle_task" --arg testFilter "$filter" \
57
+ --arg cwd "$(automation_gradle_build_root)" \
46
58
  --argjson exitCode "$red_status" \
47
59
  '{taskId: $taskId, startedAt: $startedAt, finishedAt: $finishedAt,
48
- command: ["./gradlew", $gradleTask, "--tests", $testFilter],
60
+ command: ["./gradlew", $gradleTask, "--tests", $testFilter], cwd: $cwd,
49
61
  expectedFailure: $expectedFailure, exitCode: $exitCode}' | automation_record_json "$red_meta"
50
62
  automation_info "$task_id legacy RED evidence recorded"
51
63
  exit 0
@@ -55,6 +55,9 @@ source_root="$(cd "$source_root" && pwd -P)"
55
55
  git -C "$source_root" show-ref --verify --quiet "refs/heads/$original_branch" || automation_die "recorded original branch no longer exists"
56
56
  [[ "$(git -C "$source_root" rev-parse "refs/heads/$original_branch")" == "$baseline_head" ]] || automation_die "original branch drifted after task preparation"
57
57
 
58
+ if [[ "$(jq -r '.schemaVersion' "$(automation_contract_path "$task_id")")" == "4" && -e "$evidence_dir/baseline.json" ]]; then
59
+ automation_die "V4 baseline.json already exists; /resume-task cannot retry inventory capture. Inspect the retained attempt, request approved abort/archive, then approve a new task; do not delete or overwrite baseline evidence."
60
+ fi
58
61
  for sealed_evidence in baseline.json red.json ready.json review.json reviews.jsonl; do
59
62
  [[ ! -e "$evidence_dir/$sealed_evidence" ]] || \
60
63
  automation_die "$sealed_evidence already exists; baseline-only recovery is forbidden"
@@ -66,7 +66,7 @@ while IFS= read -r path; do
66
66
  if automation_array_matches_path "$AUTOMATION_CONFIG" '.androidProject.productionPaths' "$path"; then
67
67
  production_changed=1
68
68
  fi
69
- if automation_array_matches_path "$AUTOMATION_CONFIG" '.androidProject.testPaths' "$path"; then
69
+ if automation_is_task_test_path "$task_id" "$path"; then
70
70
  test_changed=1
71
71
  fi
72
72
  done <<< "$changed_file_list"
@@ -75,7 +75,7 @@ deleted_tests="$(
75
75
  while IFS= read -r path; do
76
76
  [[ -n "$path" ]] || continue
77
77
  if [[ -n "$(git -C "$AUTOMATION_ROOT" diff --no-renames --name-only --diff-filter=D HEAD -- "$path")" ]] && \
78
- automation_array_matches_path "$AUTOMATION_CONFIG" '.androidProject.testPaths' "$path"; then
78
+ automation_is_task_test_path "$task_id" "$path"; then
79
79
  printf '%s\n' "$path"
80
80
  fi
81
81
  done <<< "$changed_file_list"
@@ -91,7 +91,7 @@ fi
91
91
  added_test_lines="$(
92
92
  while IFS= read -r path; do
93
93
  [[ -n "$path" ]] || continue
94
- if automation_array_matches_path "$AUTOMATION_CONFIG" '.androidProject.testPaths' "$path"; then
94
+ if automation_is_task_test_path "$task_id" "$path"; then
95
95
  git -C "$AUTOMATION_ROOT" diff --no-renames --unified=0 HEAD -- "$path"
96
96
  fi
97
97
  done <<< "$changed_file_list" \
@@ -106,7 +106,7 @@ fi
106
106
  while IFS= read -r untracked_test; do
107
107
  [[ -n "$untracked_test" ]] || continue
108
108
  if ! git -C "$AUTOMATION_ROOT" ls-files --error-unmatch -- "$untracked_test" >/dev/null 2>&1 && \
109
- automation_array_matches_path "$AUTOMATION_CONFIG" '.androidProject.testPaths' "$untracked_test" && \
109
+ automation_is_task_test_path "$task_id" "$untracked_test" && \
110
110
  rg -n "$weakening_pattern" "$AUTOMATION_ROOT/$untracked_test" >/dev/null; then
111
111
  automation_die "potential test weakening detected in new test: $untracked_test"
112
112
  exit 40
@@ -95,6 +95,7 @@ printf -- '- Reviewer:`%s`,独立复验退出码 `%s`\n' \
95
95
  "$(jq -r '.evidence.reviewerVerificationExitCode' "$report_file")"
96
96
  printf -- '- Reviewer 摘要:%s\n' "$(jq -r '.reviewSummary' "$report_file")"
97
97
  printf -- '- 聚焦测试:\n'
98
+ printf -- ' - Gradle 工作目录:`%s`\n' "$(automation_gradle_build_root)"
98
99
  jq -r '.targetTests[] | " - `./gradlew \(.gradleTask) --tests \(.filter)`"' "$report_file"
99
100
 
100
101
  printf '\n### P1 · 绑定与剩余风险\n\n'
@@ -23,6 +23,26 @@ baseline_json="$(if [[ -f "$evidence_dir/baseline.json" ]]; then jq -c . "$evide
23
23
  red_json="$(if [[ -f "$evidence_dir/red.json" ]]; then jq -c . "$evidence_dir/red.json"; else printf 'null'; fi)"
24
24
  preflight_json="$(if [[ -f "$evidence_dir/test-preflight.json" ]]; then jq -c . "$evidence_dir/test-preflight.json"; else printf 'null'; fi)"
25
25
  manifest_json="$(if [[ -f "$evidence_dir/test-manifest.json" ]]; then jq -c . "$evidence_dir/test-manifest.json"; else printf 'null'; fi)"
26
+ inventory_baseline_json="$(if [[ -f "$evidence_dir/baseline-inventory.json" ]]; then jq -c . "$evidence_dir/baseline-inventory.json"; else printf 'null'; fi)"
27
+ inventory_green_json="$(if [[ -f "$evidence_dir/green-inventory.json" ]]; then jq -c . "$evidence_dir/green-inventory.json"; else printf 'null'; fi)"
28
+ inventory_status_json="$(if [[ -f "$evidence_dir/inventory-status.json" ]]; then jq -c . "$evidence_dir/inventory-status.json"; else printf 'null'; fi)"
29
+ baseline_recovery_json="$(if [[ -f "$evidence_dir/baseline-recovery.json" ]]; then jq -c . "$evidence_dir/baseline-recovery.json"; else printf 'null'; fi)"
30
+ stage_recovery_json='{}'
31
+ for phase in red green review; do
32
+ if [[ -f "$evidence_dir/stage-recovery/$phase.json" ]]; then
33
+ stage_recovery_json="$(jq -nc --argjson current "$stage_recovery_json" --arg phase "$phase" \
34
+ --slurpfile record "$evidence_dir/stage-recovery/$phase.json" '$current + {($phase): $record[0]}')"
35
+ fi
36
+ done
37
+ supervision_json=null
38
+ if [[ -f "$AUTOMATION_RUNTIME_ROOT/inbox/queue.json" ]]; then
39
+ supervision_json="$(jq -c --arg task "$task_id" '
40
+ ([.items[]? | select(.taskId == $task)] | last | .key) as $key |
41
+ ([.runs[]?, .active] | map(select(.key == $key)) | last | .supervision) |
42
+ if . == null then null else {state, stage, deadline, stageDeadline, stopReason, termAt, killAt} end
43
+ ' "$AUTOMATION_RUNTIME_ROOT/inbox/queue.json")"
44
+ supervision_json="${supervision_json:-null}"
45
+ fi
26
46
  ready_json="$(if [[ -f "$evidence_dir/ready.json" ]]; then jq -c . "$evidence_dir/ready.json"; else printf 'null'; fi)"
27
47
  review_json="$(if [[ -f "$evidence_dir/review.json" ]]; then jq -c . "$evidence_dir/review.json"; else printf 'null'; fi)"
28
48
  gate_json=null
@@ -97,6 +117,12 @@ jq -n \
97
117
  --argjson red "$red_json" \
98
118
  --argjson testPreflight "$preflight_json" \
99
119
  --argjson testManifest "$manifest_json" \
120
+ --argjson baselineInventory "$inventory_baseline_json" \
121
+ --argjson greenInventory "$inventory_green_json" \
122
+ --argjson inventoryStatus "$inventory_status_json" \
123
+ --argjson baselineRecovery "$baseline_recovery_json" \
124
+ --argjson stageRecovery "$stage_recovery_json" \
125
+ --argjson workerSupervision "$supervision_json" \
100
126
  --argjson ready "$ready_json" \
101
127
  --argjson gate "$gate_json" \
102
128
  --argjson review "$review_json" \
@@ -124,6 +150,12 @@ jq -n \
124
150
  red: $red,
125
151
  testPreflight: $testPreflight,
126
152
  testManifest: $testManifest,
153
+ baselineInventory: $baselineInventory,
154
+ greenInventory: $greenInventory,
155
+ inventoryStatus: $inventoryStatus,
156
+ baselineRecovery: $baselineRecovery,
157
+ stageRecovery: $stageRecovery,
158
+ workerSupervision: $workerSupervision,
127
159
  ready: $ready,
128
160
  latestGate: $gate,
129
161
  review: $review,
@@ -36,9 +36,18 @@ review_log="$evidence_dir/review-verification.log"
36
36
  verification_status=0
37
37
  if [[ "$decision" == "APPROVED" ]]; then
38
38
  set +e
39
- "$SCRIPT_DIR/verify-task.sh" "$task_id" 2>&1 | tee "$review_log"
39
+ contract="$(automation_contract_path "$task_id")"
40
+ if [[ "$(jq -r '.schemaVersion' "$contract")" -ge "7" ]]; then
41
+ node "$AUTOMATION_ROOT/automation/verification/recovery.cjs" --stage review "$contract" "$AUTOMATION_CONFIG" "$AUTOMATION_ROOT" "$evidence_dir" 2>&1 | tee "$review_log"
42
+ else
43
+ "$SCRIPT_DIR/verify-task.sh" "$task_id" 2>&1 | tee "$review_log"
44
+ fi
40
45
  verification_status=${PIPESTATUS[0]}
41
46
  set -e
47
+ if [[ "$(jq -r '.schemaVersion' "$contract")" -ge "7" && ( "$verification_status" -eq 75 || "$verification_status" -eq 76 ) ]]; then
48
+ automation_transition_state "$task_id" "REVIEWING" "BLOCKED" "reviewer" "independent verification stopped; inspect stage-recovery/review.json; no implementation change requested"
49
+ exit "$verification_status"
50
+ fi
42
51
  if [[ "$verification_status" -ne 0 ]]; then
43
52
  decision="CHANGES_REQUESTED"
44
53
  summary="Independent verification failed with exit $verification_status. $summary"
@@ -26,6 +26,10 @@ fi
26
26
  [[ -f "$contract" ]] || automation_die "contract not found: $contract"
27
27
  jq -e . "$contract" >/dev/null || automation_die "contract is not valid JSON: $contract"
28
28
 
29
+ if [[ "$(jq -r '.schemaVersion' "$contract")" -ge "4" ]]; then
30
+ node "$AUTOMATION_ROOT/automation/verification/contract.cjs" "$contract" "$AUTOMATION_CONFIG" || \
31
+ automation_die "V4/V5 contract validation failed"
32
+ else
29
33
  jq -e '
30
34
  (.schemaVersion == 1 or .schemaVersion == 2 or .schemaVersion == 3) and
31
35
  (.id | type == "string" and test("^TASK-[A-Z0-9-]+$")) and
@@ -99,9 +103,14 @@ jq -e '
99
103
  else false end)))
100
104
  else true end)
101
105
  ' "$contract" >/dev/null || automation_die "contract is missing required fields or violates limits"
106
+ fi
102
107
 
103
108
  task_id="$(jq -r '.id' "$contract")"
104
109
  automation_validate_task_id "$task_id"
110
+ if [[ "$(jq -r '.deviceTestsRequired' "$contract")" == "true" ]]; then
111
+ jq -e '.gradleVerification.deviceTestTasks | length > 0' "$AUTOMATION_CONFIG" >/dev/null || \
112
+ automation_die "Device tests are required by this contract but no device test tasks are available"
113
+ fi
105
114
 
106
115
  if [[ "$contract" == "$AUTOMATION_TASKS_DIR/"* ]]; then
107
116
  expected_contract="$(automation_contract_path "$task_id")"
@@ -133,7 +142,7 @@ while IFS=$'\t' read -r gradle_task filter; do
133
142
  automation_die "target test Gradle task is not allowed by automation/config.json: $gradle_task"
134
143
  done < <(jq -r '.targetTests[] | [.gradleTask, .filter] | @tsv' "$contract")
135
144
 
136
- if rg -n -i 'replace with|TASK-EXAMPLE|todo|tbd|placeholder' "$contract" >/dev/null; then
145
+ if [[ "$(jq -r '.schemaVersion' "$contract")" -lt "4" ]] && rg -n -i 'replace with|TASK-EXAMPLE|todo|tbd|placeholder' "$contract" >/dev/null; then
137
146
  automation_die "contract still contains template placeholders"
138
147
  fi
139
148
 
@@ -28,4 +28,7 @@ if [[ "$(jq -r '.deviceTestsRequired' "$contract")" == "true" ]]; then
28
28
  automation_run_gradle_group "deviceTestTasks" "$AUTOMATION_ROOT"
29
29
  fi
30
30
 
31
+ if [[ "$(jq -r '.schemaVersion' "$contract")" -ge "4" ]]; then
32
+ automation_run_inventory check "$task_id"
33
+ fi
31
34
  automation_info "integration candidate passed all deterministic verification"
@@ -44,6 +44,9 @@ if [[ "$(jq -r '.deviceTestsRequired' "$contract")" == "true" ]]; then
44
44
  fi
45
45
 
46
46
  automation_info "all deterministic verification commands passed"
47
+ if [[ "$contract_schema" -ge "4" ]]; then
48
+ automation_run_inventory check "$task_id"
49
+ fi
47
50
  if [[ -n "${AUTOMATION_QUEUE_RUN_ID:-}" ]]; then
48
51
  [[ -n "${AUTOMATION_FULL_TEST_LOG:-}" ]] || automation_die "fresh full unit-test evidence is missing"
49
52
  jq -n \