@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.
- package/CHANGELOG.md +67 -0
- package/README.md +76 -15
- package/dist/config/queue-policy.d.ts.map +1 -1
- package/dist/config/queue-policy.js +1 -2
- package/dist/config/queue-policy.js.map +1 -1
- package/dist/doctor/index.d.ts.map +1 -1
- package/dist/doctor/index.js +14 -3
- package/dist/doctor/index.js.map +1 -1
- package/dist/doctor/installation.d.ts +1 -1
- package/dist/doctor/installation.d.ts.map +1 -1
- package/dist/doctor/installation.js +10 -1
- package/dist/doctor/installation.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/installer/adaptive-templates.d.ts +4 -1
- package/dist/installer/adaptive-templates.d.ts.map +1 -1
- package/dist/installer/adaptive-templates.js +38 -13
- package/dist/installer/adaptive-templates.js.map +1 -1
- package/dist/installer/android-project.d.ts +3 -1
- package/dist/installer/android-project.d.ts.map +1 -1
- package/dist/installer/android-project.js.map +1 -1
- package/dist/installer/capabilities-script.d.ts +2 -0
- package/dist/installer/capabilities-script.d.ts.map +1 -0
- package/dist/installer/capabilities-script.js +77 -0
- package/dist/installer/capabilities-script.js.map +1 -0
- package/dist/installer/gradle-verification.d.ts.map +1 -1
- package/dist/installer/gradle-verification.js +13 -6
- package/dist/installer/gradle-verification.js.map +1 -1
- package/dist/installer/init.d.ts.map +1 -1
- package/dist/installer/init.js +2 -1
- package/dist/installer/init.js.map +1 -1
- package/dist/installer/opencode-config.d.ts +3 -3
- package/dist/installer/opencode-config.js +1 -1
- package/dist/installer/project-capabilities.d.ts +35 -0
- package/dist/installer/project-capabilities.d.ts.map +1 -0
- package/dist/installer/project-capabilities.js +82 -0
- package/dist/installer/project-capabilities.js.map +1 -0
- package/dist/installer/upgrade.d.ts.map +1 -1
- package/dist/installer/upgrade.js +9 -1
- package/dist/installer/upgrade.js.map +1 -1
- package/dist/queue/cli.d.ts.map +1 -1
- package/dist/queue/cli.js +32 -5
- package/dist/queue/cli.js.map +1 -1
- package/dist/queue/executor.d.ts +1 -0
- package/dist/queue/executor.d.ts.map +1 -1
- package/dist/queue/executor.js +155 -11
- package/dist/queue/executor.js.map +1 -1
- package/dist/queue/process-ownership.d.ts +12 -0
- package/dist/queue/process-ownership.d.ts.map +1 -0
- package/dist/queue/process-ownership.js +58 -0
- package/dist/queue/process-ownership.js.map +1 -0
- package/dist/queue/queue.d.ts +25 -1
- package/dist/queue/queue.d.ts.map +1 -1
- package/dist/queue/queue.js +96 -11
- package/dist/queue/queue.js.map +1 -1
- package/dist/queue/service.d.ts.map +1 -1
- package/dist/queue/service.js +19 -2
- package/dist/queue/service.js.map +1 -1
- package/dist/queue/storage.d.ts +3 -1
- package/dist/queue/storage.d.ts.map +1 -1
- package/dist/queue/storage.js +25 -15
- package/dist/queue/storage.js.map +1 -1
- package/dist/queue/supervision.d.ts +38 -0
- package/dist/queue/supervision.d.ts.map +1 -0
- package/dist/queue/supervision.js +247 -0
- package/dist/queue/supervision.js.map +1 -0
- package/dist/queue/tools.d.ts.map +1 -1
- package/dist/queue/tools.js +4 -2
- package/dist/queue/tools.js.map +1 -1
- package/docs/MIGRATION.md +110 -9
- package/docs/QUEUE.md +350 -16
- package/docs/SECURITY.md +5 -2
- package/docs/TROUBLESHOOTING.md +82 -8
- package/package.json +1 -1
- package/templates/.opencode/agents/scheduled-coder.md +14 -1
- package/templates/.opencode/agents/scheduled-planner.md +64 -3
- package/templates/.opencode/skills/scheduled-quality-coder/SKILL.md +29 -3
- package/templates/.opencode/skills/scheduled-quality-orchestrator/SKILL.md +65 -4
- package/templates/.opencode/skills/scheduled-quality-reviewer/SKILL.md +19 -1
- package/templates/AGENTS.md.fragment +10 -4
- package/templates/README.md +3 -1
- package/templates/automation/config.schema.json +212 -22
- package/templates/automation/task-contract.schema.json +573 -49
- package/templates/automation/tasks/TASK-TEMPLATE.json.example +9 -4
- package/templates/automation/verification/collect.init.gradle +98 -0
- package/templates/automation/verification/contract.cjs +93 -0
- package/templates/automation/verification/inventory.cjs +433 -0
- package/templates/automation/verification/project.cjs +172 -0
- package/templates/automation/verification/recovery.cjs +426 -0
- package/templates/scripts/automation/acceptance-report.sh +17 -0
- package/templates/scripts/automation/claim-task.sh +35 -2
- package/templates/scripts/automation/integration-scope-gate.sh +3 -3
- package/templates/scripts/automation/lib.sh +61 -19
- package/templates/scripts/automation/orchestrate-task.sh +7 -1
- package/templates/scripts/automation/preflight.sh +1 -2
- package/templates/scripts/automation/quality-gate.sh +10 -1
- package/templates/scripts/automation/record-red.sh +13 -1
- package/templates/scripts/automation/resume-task.sh +3 -0
- package/templates/scripts/automation/scope-gate.sh +4 -4
- package/templates/scripts/automation/show-acceptance-review.sh +1 -0
- package/templates/scripts/automation/status.sh +32 -0
- package/templates/scripts/automation/submit-review.sh +10 -1
- package/templates/scripts/automation/validate-contract.sh +10 -1
- package/templates/scripts/automation/verify-integration.sh +3 -0
- 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
|
|
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 |
|
|
153
|
-
($verification.deviceTestTasks |
|
|
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
|
-
|
|
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
|
-
|
|
444
|
-
|
|
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
|
-
(
|
|
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
|
-
|
|
565
|
-
|
|
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
|
-
(
|
|
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
|
-
|
|
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
|
-
"$
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
"$
|
|
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 \
|