@frankzhang2026/opencode-android-orchestrator 0.9.0 → 1.0.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 (127) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +92 -55
  3. package/dist/cli.js +14 -1
  4. package/dist/cli.js.map +1 -1
  5. package/dist/config/android-sdk.d.ts +10 -0
  6. package/dist/config/android-sdk.d.ts.map +1 -0
  7. package/dist/config/android-sdk.js +50 -0
  8. package/dist/config/android-sdk.js.map +1 -0
  9. package/dist/config/commit-message-prefix.d.ts +31 -0
  10. package/dist/config/commit-message-prefix.d.ts.map +1 -0
  11. package/dist/config/commit-message-prefix.js +155 -0
  12. package/dist/config/commit-message-prefix.js.map +1 -0
  13. package/dist/config/queue-policy.d.ts +12 -0
  14. package/dist/config/queue-policy.d.ts.map +1 -0
  15. package/dist/config/queue-policy.js +18 -0
  16. package/dist/config/queue-policy.js.map +1 -0
  17. package/dist/config/verification-policy.d.ts +10 -3
  18. package/dist/config/verification-policy.d.ts.map +1 -1
  19. package/dist/config/verification-policy.js +66 -13
  20. package/dist/config/verification-policy.js.map +1 -1
  21. package/dist/doctor/index.d.ts.map +1 -1
  22. package/dist/doctor/index.js +3 -49
  23. package/dist/doctor/index.js.map +1 -1
  24. package/dist/doctor/installation.d.ts.map +1 -1
  25. package/dist/doctor/installation.js +81 -0
  26. package/dist/doctor/installation.js.map +1 -1
  27. package/dist/index.d.ts +1 -0
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +1 -0
  30. package/dist/index.js.map +1 -1
  31. package/dist/installer/adaptive-templates.d.ts +8 -2
  32. package/dist/installer/adaptive-templates.d.ts.map +1 -1
  33. package/dist/installer/adaptive-templates.js +10 -0
  34. package/dist/installer/adaptive-templates.js.map +1 -1
  35. package/dist/installer/index.d.ts +1 -0
  36. package/dist/installer/index.d.ts.map +1 -1
  37. package/dist/installer/index.js +1 -0
  38. package/dist/installer/index.js.map +1 -1
  39. package/dist/installer/init.d.ts +4 -1
  40. package/dist/installer/init.d.ts.map +1 -1
  41. package/dist/installer/init.js +60 -9
  42. package/dist/installer/init.js.map +1 -1
  43. package/dist/installer/install-manifest.d.ts +1 -0
  44. package/dist/installer/install-manifest.d.ts.map +1 -1
  45. package/dist/installer/install-manifest.js +6 -1
  46. package/dist/installer/install-manifest.js.map +1 -1
  47. package/dist/installer/opencode-config.d.ts +3 -3
  48. package/dist/installer/opencode-config.js +1 -1
  49. package/dist/installer/uninstall.d.ts.map +1 -1
  50. package/dist/installer/uninstall.js +5 -0
  51. package/dist/installer/uninstall.js.map +1 -1
  52. package/dist/installer/upgrade.d.ts +5 -1
  53. package/dist/installer/upgrade.d.ts.map +1 -1
  54. package/dist/installer/upgrade.js +94 -11
  55. package/dist/installer/upgrade.js.map +1 -1
  56. package/dist/plugin/index.d.ts.map +1 -1
  57. package/dist/plugin/index.js +11 -5
  58. package/dist/plugin/index.js.map +1 -1
  59. package/dist/queue/approvals.d.ts +49 -0
  60. package/dist/queue/approvals.d.ts.map +1 -0
  61. package/dist/queue/approvals.js +68 -0
  62. package/dist/queue/approvals.js.map +1 -0
  63. package/dist/queue/cli.d.ts +3 -0
  64. package/dist/queue/cli.d.ts.map +1 -0
  65. package/dist/queue/cli.js +109 -0
  66. package/dist/queue/cli.js.map +1 -0
  67. package/dist/queue/executor.d.ts +24 -0
  68. package/dist/queue/executor.d.ts.map +1 -0
  69. package/dist/queue/executor.js +445 -0
  70. package/dist/queue/executor.js.map +1 -0
  71. package/dist/queue/lifecycle.d.ts +3 -0
  72. package/dist/queue/lifecycle.d.ts.map +1 -0
  73. package/dist/queue/lifecycle.js +24 -0
  74. package/dist/queue/lifecycle.js.map +1 -0
  75. package/dist/queue/queue.d.ts +154 -0
  76. package/dist/queue/queue.d.ts.map +1 -0
  77. package/dist/queue/queue.js +411 -0
  78. package/dist/queue/queue.js.map +1 -0
  79. package/dist/queue/service.d.ts +15 -0
  80. package/dist/queue/service.d.ts.map +1 -0
  81. package/dist/queue/service.js +152 -0
  82. package/dist/queue/service.js.map +1 -0
  83. package/dist/queue/storage.d.ts +30 -0
  84. package/dist/queue/storage.d.ts.map +1 -0
  85. package/dist/queue/storage.js +171 -0
  86. package/dist/queue/storage.js.map +1 -0
  87. package/dist/queue/tools.d.ts +12 -0
  88. package/dist/queue/tools.d.ts.map +1 -0
  89. package/dist/queue/tools.js +144 -0
  90. package/dist/queue/tools.js.map +1 -0
  91. package/docs/MIGRATION.md +47 -14
  92. package/docs/QUEUE.md +146 -0
  93. package/docs/SECURITY.md +48 -32
  94. package/docs/TROUBLESHOOTING.md +36 -11
  95. package/package.json +1 -1
  96. package/templates/.opencode/agents/scheduled-planner.md +60 -131
  97. package/templates/.opencode/commands/abort-task.md +6 -15
  98. package/templates/.opencode/commands/acceptance.md +6 -15
  99. package/templates/.opencode/commands/change.md +3 -1
  100. package/templates/.opencode/commands/resume-review.md +6 -14
  101. package/templates/.opencode/commands/resume-task.md +6 -23
  102. package/templates/.opencode/skills/scheduled-quality-orchestrator/SKILL.md +69 -248
  103. package/templates/AGENTS.md.fragment +8 -0
  104. package/templates/README.md +9 -1
  105. package/templates/automation/config.json +9 -2
  106. package/templates/automation/config.schema.json +222 -45
  107. package/templates/scripts/automation/abort-task.sh +4 -2
  108. package/templates/scripts/automation/accept-and-integrate.sh +9 -2
  109. package/templates/scripts/automation/acceptance-report.sh +4 -2
  110. package/templates/scripts/automation/approve-and-run.sh +5 -0
  111. package/templates/scripts/automation/begin-review.sh +1 -0
  112. package/templates/scripts/automation/block-task.sh +1 -0
  113. package/templates/scripts/automation/claim-task.sh +1 -0
  114. package/templates/scripts/automation/lib.sh +186 -2
  115. package/templates/scripts/automation/orchestrate-task.sh +12 -0
  116. package/templates/scripts/automation/preflight.sh +25 -4
  117. package/templates/scripts/automation/prepare-contract-review.sh +4 -0
  118. package/templates/scripts/automation/quality-gate.sh +1 -0
  119. package/templates/scripts/automation/record-red.sh +1 -0
  120. package/templates/scripts/automation/resume-review-fix.sh +1 -0
  121. package/templates/scripts/automation/resume-review.sh +1 -0
  122. package/templates/scripts/automation/resume-task.sh +1 -0
  123. package/templates/scripts/automation/scope-gate.sh +10 -2
  124. package/templates/scripts/automation/show-acceptance-review.sh +3 -0
  125. package/templates/scripts/automation/submit-review.sh +8 -2
  126. package/templates/scripts/automation/tests/run-tests.sh +59 -4
  127. package/templates/scripts/automation/verify-task.sh +13 -0
@@ -50,6 +50,9 @@ AUTOMATION_WORKSPACES_DIR="$AUTOMATION_RUNTIME_ROOT/workspaces"
50
50
  AUTOMATION_WORKTREE_ALLOWLIST_RELATIVE_PATH=".automation-worktree-allowlist"
51
51
  AUTOMATION_WORKTREE_ALLOWLIST_MAX_BYTES=65536
52
52
  AUTOMATION_WORKTREE_ALLOWLIST_MAX_ENTRIES=256
53
+ AUTOMATION_COMMIT_MESSAGE_PREFIX_RELATIVE_PATH="automation/automation-commit-prefix"
54
+ AUTOMATION_COMMIT_MESSAGE_PREFIX_FILE_MAX_BYTES=4096
55
+ AUTOMATION_COMMIT_MESSAGE_PREFIX_MAX_BYTES=256
53
56
 
54
57
  automation_info() {
55
58
  printf '[automation] %s\n' "$*"
@@ -111,7 +114,9 @@ automation_validate_config() {
111
114
  length > 0 and
112
115
  length == (unique | length) and
113
116
  all(.[]; gradle_task);
114
- .schemaVersion == 4 and
117
+ (.schemaVersion == 5 or .schemaVersion == 6) and
118
+ ((.commitPolicy // "humanApproval") == "humanApproval" or .commitPolicy == "autoCommit") and
119
+ (.workspaceStrategy != "isolatedWorktree" or (.commitPolicy // "humanApproval") == "humanApproval") and
115
120
  (.enabled | type == "boolean") and
116
121
  (.mode == "shadow" or .mode == "orchestrated") and
117
122
  (.workspaceStrategy == "inPlaceExclusive" or .workspaceStrategy == "isolatedWorktree") and
@@ -122,6 +127,7 @@ automation_validate_config() {
122
127
  (.maxReviewerRestarts | type == "number" and . >= 0 and . <= 3 and floor == .) and
123
128
  (.unitTestsEnabled | type == "boolean") and
124
129
  (.lintEnabled | type == "boolean") and
130
+ (.commitMessagePrefixMode == "required" or .commitMessagePrefixMode == "disabled") and
125
131
  (.longCommandTimeoutMs | type == "number" and . >= 120000 and . <= 7200000 and floor == .) and
126
132
  (.autoCleanupWorktrees | type == "boolean") and
127
133
  .pushAfterAcceptance == false and
@@ -213,6 +219,26 @@ automation_workspace_path() {
213
219
  printf '%s/%s.json\n' "$AUTOMATION_WORKSPACES_DIR" "$task_id"
214
220
  }
215
221
 
222
+ automation_require_queue_execution() {
223
+ local task_id="$1"
224
+ local workspace_file queue_key group
225
+ workspace_file="$(automation_workspace_path "$task_id")"
226
+ [[ -f "$workspace_file" ]] || return 0
227
+ queue_key="$(jq -r '.queueKey // empty' "$workspace_file")"
228
+ [[ -n "$queue_key" ]] || return 0
229
+ [[ -n "${AUTOMATION_QUEUE_RUN_ID:-}" ]] || {
230
+ automation_die "queued tasks must use the repository queue for execution, integration and recovery"
231
+ return 1
232
+ }
233
+ group="$(ps -o pgid= -p "$$" | tr -d ' ')"
234
+ jq -e --arg key "$queue_key" --arg run "$AUTOMATION_QUEUE_RUN_ID" --argjson group "$group" \
235
+ '.active.key == $key and .active.id == $run and .active.worker.pid == $group' \
236
+ "$AUTOMATION_RUNTIME_ROOT/inbox/queue.json" >/dev/null || {
237
+ automation_die "current process does not own this task queue execution"
238
+ return 1
239
+ }
240
+ }
241
+
216
242
  automation_workspace_strategy() {
217
243
  local workspace_file="$1"
218
244
  jq -er '.workspaceStrategy // "isolatedWorktree"' "$workspace_file"
@@ -246,6 +272,92 @@ automation_config_value() {
246
272
  jq -r "$query" "$AUTOMATION_CONFIG"
247
273
  }
248
274
 
275
+ automation_read_commit_message_prefix_at() {
276
+ local root="$1"
277
+ local config_file="$root/automation/config.json"
278
+ local prefix_file="$root/$AUTOMATION_COMMIT_MESSAGE_PREFIX_RELATIVE_PATH"
279
+ local mode byte_count line prefix prefix_bytes
280
+ local active_count=0
281
+
282
+ if [[ -L "$config_file" || ! -f "$config_file" ]]; then
283
+ automation_die "missing or unsafe automation configuration: $config_file"
284
+ return 1
285
+ fi
286
+ mode="$(jq -er '.commitMessagePrefixMode' "$config_file")" || {
287
+ automation_die "commitMessagePrefixMode is missing from $config_file"
288
+ return 1
289
+ }
290
+ case "$mode" in
291
+ disabled) return 0 ;;
292
+ required) ;;
293
+ *)
294
+ automation_die "unsupported commitMessagePrefixMode: $mode"
295
+ return 1
296
+ ;;
297
+ esac
298
+
299
+ if [[ ! -e "$prefix_file" && ! -L "$prefix_file" ]]; then
300
+ automation_die "commit-message prefix is required; fill $prefix_file before starting a task"
301
+ return 1
302
+ fi
303
+ if [[ -L "$prefix_file" || ! -f "$prefix_file" ]]; then
304
+ automation_die "commit-message prefix must be a regular file, not a symlink: $prefix_file"
305
+ return 1
306
+ fi
307
+ byte_count="$(wc -c < "$prefix_file" | tr -d '[:space:]')"
308
+ if [[ ! "$byte_count" =~ ^[0-9]+$ ]] || \
309
+ [[ "$byte_count" -gt "$AUTOMATION_COMMIT_MESSAGE_PREFIX_FILE_MAX_BYTES" ]]; then
310
+ automation_die "commit-message prefix file exceeds $AUTOMATION_COMMIT_MESSAGE_PREFIX_FILE_MAX_BYTES bytes: $prefix_file"
311
+ return 1
312
+ fi
313
+
314
+ while IFS= read -r line || [[ -n "$line" ]]; do
315
+ line="${line%$'\r'}"
316
+ [[ -z "$line" || "$line" =~ ^[[:space:]]*$ || "$line" =~ ^[[:space:]]*# ]] && continue
317
+ active_count=$((active_count + 1))
318
+ if [[ "$active_count" -gt 1 ]]; then
319
+ automation_die "commit-message prefix must contain exactly one non-comment line: $prefix_file"
320
+ return 1
321
+ fi
322
+ if [[ "$line" == [[:space:]]* || "$line" == *[[:space:]] ]]; then
323
+ automation_die "commit-message prefix must not have leading or trailing whitespace"
324
+ return 1
325
+ fi
326
+ case "$line" in
327
+ *[[:cntrl:]]*)
328
+ automation_die "commit-message prefix must not contain control characters"
329
+ return 1
330
+ ;;
331
+ esac
332
+ prefix="$line"
333
+ done < "$prefix_file"
334
+
335
+ if [[ "$active_count" -eq 0 ]]; then
336
+ automation_die "commit-message prefix is not configured; fill $prefix_file before starting a task"
337
+ return 1
338
+ fi
339
+ prefix_bytes="$(printf '%s' "$prefix" | wc -c | tr -d '[:space:]')"
340
+ if [[ ! "$prefix_bytes" =~ ^[0-9]+$ ]] || \
341
+ [[ "$prefix_bytes" -gt "$AUTOMATION_COMMIT_MESSAGE_PREFIX_MAX_BYTES" ]]; then
342
+ automation_die "commit-message prefix exceeds $AUTOMATION_COMMIT_MESSAGE_PREFIX_MAX_BYTES UTF-8 bytes"
343
+ return 1
344
+ fi
345
+ printf '%s\n' "$prefix"
346
+ }
347
+
348
+ automation_commit_message_at() {
349
+ local root="$1"
350
+ local base_message="$2"
351
+ local prefix
352
+
353
+ prefix="$(automation_read_commit_message_prefix_at "$root")" || return 1
354
+ if [[ -n "$prefix" ]]; then
355
+ printf '%s %s\n' "$prefix" "$base_message"
356
+ else
357
+ printf '%s\n' "$base_message"
358
+ fi
359
+ }
360
+
249
361
  automation_validate_gradle_task() {
250
362
  local task="${1:-}"
251
363
  local safe_task_pattern='^([A-Za-z][A-Za-z0-9_.-]*|(:[A-Za-z0-9_.-]+)+)$'
@@ -296,12 +408,75 @@ automation_run_gradle_group() {
296
408
  return 1
297
409
  }
298
410
 
411
+ if [[ "$group" == "fullUnitTestTasks" && -n "${AUTOMATION_QUEUE_RUN_ID:-}" ]]; then
412
+ automation_run_fresh_unit_tests "$root" "${tasks[@]}"
413
+ return $?
414
+ fi
299
415
  (
300
416
  cd "$root"
301
417
  ./gradlew "${tasks[@]}"
302
418
  )
303
419
  }
304
420
 
421
+ # Queue execution reruns Test tasks while preserving compilation/build caches.
422
+ # The init script disables only Test output reuse, including FROM-CACHE, and
423
+ # emits actual test counts. No clean task or model polling is involved.
424
+ automation_run_fresh_unit_tests() {
425
+ local root="$1"
426
+ shift
427
+ local init_file log_file result elapsed start
428
+ mkdir -p "$AUTOMATION_RUNTIME_ROOT/gradle"
429
+ init_file="$(mktemp "$AUTOMATION_RUNTIME_ROOT/gradle/fresh-tests.XXXXXX")"
430
+ log_file="${init_file}.log"
431
+ cat > "$init_file" <<'GRADLE'
432
+ gradle.allprojects { project ->
433
+ project.tasks.withType(org.gradle.api.tasks.testing.Test).configureEach { testTask ->
434
+ outputs.upToDateWhen { false }
435
+ outputs.doNotCacheIf('Orchestrator requires fresh unit-test execution') { true }
436
+ if (testTask.hasProperty('dryRun')) testTask.dryRun = false
437
+ afterSuite { descriptor, result ->
438
+ if (descriptor.parent == null) {
439
+ logger.lifecycle("ORCHESTRATOR_TEST_RESULT|${testTask.path}|${result.testCount}|${result.failedTestCount}|${result.skippedTestCount}")
440
+ }
441
+ }
442
+ }
443
+ }
444
+ gradle.taskGraph.whenReady { graph ->
445
+ graph.allTasks.findAll { it instanceof org.gradle.api.tasks.testing.Test }.each { task ->
446
+ task.logger.lifecycle("ORCHESTRATOR_TEST_EXPECTED|${task.path}")
447
+ }
448
+ }
449
+ gradle.taskGraph.afterTask { task, state ->
450
+ if (task instanceof org.gradle.api.tasks.testing.Test && state.noSource) {
451
+ task.logger.lifecycle("ORCHESTRATOR_TEST_NO_SOURCE|${task.path}")
452
+ }
453
+ }
454
+ GRADLE
455
+ start="$(date +%s)"
456
+ set +e
457
+ (cd "$root" && ./gradlew "$@" --no-configuration-cache --console=plain --init-script "$init_file") 2>&1 | tee "$log_file"
458
+ result=${PIPESTATUS[0]}
459
+ set -e
460
+ elapsed=$(( $(date +%s) - start ))
461
+ [[ "$result" -eq 0 ]] || return "$result"
462
+ # A successful build with only cached, skipped or empty suites is not proof
463
+ # that the required regression tests actually executed.
464
+ awk -F '|' '
465
+ /^ORCHESTRATOR_TEST_EXPECTED\|/ { expected[$2] = 1; count++ }
466
+ /^ORCHESTRATOR_TEST_RESULT\|/ { verified[$2] = 1; total += $3 - $5; failed += $4 }
467
+ /^ORCHESTRATOR_TEST_NO_SOURCE\|/ { verified[$2] = 1 }
468
+ END {
469
+ for (task in expected) if (!verified[task]) missing++
470
+ exit !(count > 0 && total > 0 && failed == 0 && missing == 0)
471
+ }' "$log_file" || {
472
+ automation_die "full unit tests produced no fresh successful test results"
473
+ return 1
474
+ }
475
+ AUTOMATION_FULL_TEST_LOG="$log_file"
476
+ AUTOMATION_FULL_TEST_ELAPSED="$elapsed"
477
+ automation_info "fresh full unit tests executed in ${elapsed}s; log: $log_file"
478
+ }
479
+
305
480
  automation_run_configured_unit_tests() {
306
481
  local contract="${1:-}"
307
482
  local root="${2:-$AUTOMATION_ROOT}"
@@ -309,6 +484,12 @@ automation_run_configured_unit_tests() {
309
484
 
310
485
  [[ -n "$contract" && -f "$contract" ]] || automation_die "unit-test contract does not exist: $contract"
311
486
  automation_validate_config || return 1
487
+ if [[ -n "${AUTOMATION_QUEUE_RUN_ID:-}" ]]; then
488
+ [[ "$(automation_config_value '.unitTestsEnabled')" == "true" ]] || {
489
+ automation_die "queued execution requires full unit tests"
490
+ return 1
491
+ }
492
+ fi
312
493
  if [[ "$(automation_config_value '.unitTestsEnabled')" != "true" ]]; then
313
494
  automation_info "skipping ${context}unit tests (unitTestsEnabled=false)"
314
495
  return 0
@@ -453,7 +634,7 @@ automation_validate_worktree_allowlist_entry_at() {
453
634
  ;;
454
635
  esac
455
636
  case "$path" in
456
- /*|./*|.|..|.git|.git/*|docs/plans|docs/plans/*|"$AUTOMATION_WORKTREE_ALLOWLIST_RELATIVE_PATH")
637
+ /*|./*|.|..|.git|.git/*|docs/plans|docs/plans/*|"$AUTOMATION_WORKTREE_ALLOWLIST_RELATIVE_PATH"|"$AUTOMATION_COMMIT_MESSAGE_PREFIX_RELATIVE_PATH")
457
638
  automation_die "worktree allowlist entry is reserved or unsafe: $path"
458
639
  return 1
459
640
  ;;
@@ -608,6 +789,7 @@ automation_worktree_path_is_allowlisted() {
608
789
  local entry
609
790
 
610
791
  [[ "$path" == "$AUTOMATION_WORKTREE_ALLOWLIST_RELATIVE_PATH" ]] && return 0
792
+ [[ "$path" == "$AUTOMATION_COMMIT_MESSAGE_PREFIX_RELATIVE_PATH" ]] && return 0
611
793
  while IFS= read -r entry; do
612
794
  [[ -n "$entry" ]] || continue
613
795
  [[ "$path" == "$entry" ]] && return 0
@@ -971,6 +1153,7 @@ automation_transition_allowed() {
971
1153
  READY_FOR_REVIEW:REVIEWING:orchestrator) return 0 ;;
972
1154
  READY_FOR_REVIEW:BLOCKED:orchestrator) return 0 ;;
973
1155
  REVIEWING:AWAITING_HUMAN:reviewer) return 0 ;;
1156
+ REVIEWING:READY_TO_COMMIT:reviewer) return 0 ;;
974
1157
  REVIEWING:CHANGES_REQUESTED:reviewer) return 0 ;;
975
1158
  REVIEWING:BLOCKED:orchestrator) return 0 ;;
976
1159
  BLOCKED:REVIEWING:human) return 0 ;;
@@ -979,6 +1162,7 @@ automation_transition_allowed() {
979
1162
  AWAITING_HUMAN:INTEGRATING:integrator) return 0 ;;
980
1163
  INTEGRATING:COMPLETED:integrator) return 0 ;;
981
1164
  INTEGRATING:INTEGRATION_BLOCKED:integrator) return 0 ;;
1165
+ READY_TO_COMMIT:ABORTED:human) return 0 ;;
982
1166
  PREPARING:ABORTED:human|PENDING:ABORTED:human|CODING:ABORTED:human|READY_FOR_REVIEW:ABORTED:human|REVIEWING:ABORTED:human|CHANGES_REQUESTED:ABORTED:human|AWAITING_HUMAN:ABORTED:human|BLOCKED:ABORTED:human|TEST_FAILED:ABORTED:human|NEEDS_HUMAN:ABORTED:human|INTEGRATION_BLOCKED:ABORTED:human) return 0 ;;
983
1167
  *) return 1 ;;
984
1168
  esac
@@ -8,6 +8,7 @@ source "$SCRIPT_DIR/lib.sh"
8
8
  task_id="${1:-}"
9
9
  [[ "$#" -eq 1 ]] || { printf 'Usage: %s TASK-ID\n' "$0" >&2; exit 2; }
10
10
  automation_validate_task_id "$task_id"
11
+ automation_require_queue_execution "$task_id"
11
12
  automation_require_orchestrated
12
13
 
13
14
  workspace_file="$(automation_workspace_path "$task_id")"
@@ -19,6 +20,7 @@ source_root="$(jq -er '.sourceRoot' "$workspace_file")"
19
20
  if [[ "$(jq -r '.repositoryLeaseRequired // false' "$workspace_file")" == "true" ]]; then
20
21
  automation_require_repository_lease "$task_id" "$source_root" "$workspace_strategy"
21
22
  fi
23
+ automation_read_commit_message_prefix_at "$source_root" >/dev/null
22
24
 
23
25
  automation_acquire_run_lock "$task_id"
24
26
  trap 'automation_release_run_lock' EXIT
@@ -62,6 +64,12 @@ run_agent() {
62
64
  }
63
65
 
64
66
  for _step in 1 2 3 4 5 6 7 8; do
67
+ if [[ -n "${AUTOMATION_QUEUE_RUN_ID:-}" ]] && jq -e --arg id "$task_id" \
68
+ 'any(.items[]; .taskId == $id and .request.kind == "abort")' \
69
+ "$AUTOMATION_RUNTIME_ROOT/inbox/queue.json" >/dev/null; then
70
+ automation_info "$task_id has an approved abort request; returning at a safe agent boundary"
71
+ exit 0
72
+ fi
65
73
  state="$(automation_read_state "$task_id")"
66
74
  case "$state" in
67
75
  PENDING|CODING)
@@ -98,6 +106,10 @@ for _step in 1 2 3 4 5 6 7 8; do
98
106
  ./scripts/automation/resume-review-fix.sh "$task_id"
99
107
  ) || true
100
108
  ;;
109
+ READY_TO_COMMIT)
110
+ automation_info "$task_id reached READY_TO_COMMIT; queue executor will verify the sealed authorization"
111
+ exit 0
112
+ ;;
101
113
  AWAITING_HUMAN)
102
114
  automation_info "$task_id reached AWAITING_HUMAN"
103
115
  (
@@ -47,6 +47,17 @@ mode="$(automation_config_value '.mode')"
47
47
 
48
48
  [[ "$enabled" == "true" ]] || fail_or_warn "automation is disabled"
49
49
  [[ "$mode" == "orchestrated" ]] || fail_or_warn "automation mode is $mode, not orchestrated"
50
+ prefix_validation_error=""
51
+ prefix_root="$AUTOMATION_ROOT"
52
+ if [[ -n "$task_id" ]]; then
53
+ prefix_workspace_file="$(automation_workspace_path "$task_id")"
54
+ if [[ -f "$prefix_workspace_file" ]]; then
55
+ prefix_root="$(jq -er '.sourceRoot' "$prefix_workspace_file")"
56
+ fi
57
+ fi
58
+ if ! prefix_validation_error="$(automation_read_commit_message_prefix_at "$prefix_root" 2>&1 >/dev/null)"; then
59
+ fail_or_warn "$prefix_validation_error"
60
+ fi
50
61
 
51
62
  if [[ "${AUTOMATION_TEST_MODE:-0}" != "1" ]]; then
52
63
  [[ -n "${ANDROID_HOME:-}" ]] || fail_or_warn "ANDROID_HOME is not set; configure the Android SDK environment before running OpenCode"
@@ -55,11 +66,15 @@ if [[ "${AUTOMATION_TEST_MODE:-0}" != "1" ]]; then
55
66
  fi
56
67
  fi
57
68
 
58
- if [[ -z "$task_id" ]] && ! automation_worktree_is_clean; then
69
+ intake_mode=0
70
+ if [[ "$source_mode" == "1" && "$(automation_config_value '.schemaVersion')" == "6" ]]; then
71
+ intake_mode=1
72
+ fi
73
+ if [[ -z "$task_id" && "$intake_mode" != "1" ]] && ! automation_worktree_is_clean; then
59
74
  fail_or_warn "Git worktree is dirty"
60
75
  fi
61
76
 
62
- if [[ "$source_mode" == "1" ]]; then
77
+ if [[ "$source_mode" == "1" && "$intake_mode" != "1" ]]; then
63
78
  repository_lease_file="$(automation_repository_lease_dir)/lease.json"
64
79
  if [[ -f "$repository_lease_file" ]]; then
65
80
  fail_or_warn "repository workspace is leased by $(jq -r '.taskId // "an unknown task"' "$repository_lease_file")"
@@ -147,7 +162,7 @@ else
147
162
  def last_rule($permission; $pattern):
148
163
  [.permission[] | select(.permission == $permission and .pattern == $pattern) | .action][-1];
149
164
  (last_rule("*"; "*") == "deny") and
150
- (last_rule("edit"; "docs/plans/**") == "allow") and
165
+ (((last_rule("edit"; "docs/plans/**") == "allow") and
151
166
  (last_rule("edit"; "automation/tasks/**") == "allow") and
152
167
  (last_rule("edit"; "**/src/**") == "deny") and
153
168
  (last_rule("bash"; "./scripts/automation/prepare-contract-review.sh *") == "allow") and
@@ -155,7 +170,13 @@ else
155
170
  (last_rule("bash"; "./scripts/automation/resume-task.sh *") == "allow") and
156
171
  (last_rule("bash"; "./scripts/automation/resume-review.sh *") == "allow") and
157
172
  (last_rule("bash"; "./scripts/automation/accept-and-integrate.sh *") == "allow") and
158
- (last_rule("bash"; "./scripts/automation/abort-task.sh *") == "allow") and
173
+ (last_rule("bash"; "./scripts/automation/abort-task.sh *") == "allow")) or
174
+ ((last_rule("read"; "*") == "deny") and
175
+ (last_rule("edit"; "*") == "deny") and
176
+ (last_rule("bash"; "*") == "deny") and
177
+ (last_rule("android_orchestrator_snapshot"; "*") == "allow") and
178
+ (last_rule("android_orchestrator_intake"; "*") == "allow") and
179
+ (last_rule("android_orchestrator_queue"; "*") == "allow"))) and
159
180
  (last_rule("android_orchestrator_status"; "*") == "allow") and
160
181
  (last_rule("android_orchestrator_doctor"; "*") == "allow") and
161
182
  (last_rule("schedule_job"; "*") == "deny") and
@@ -14,6 +14,10 @@ fi
14
14
 
15
15
  automation_validate_task_id "$task_id"
16
16
  automation_require_orchestrated
17
+ [[ "$(automation_config_value '.schemaVersion')" != "6" ]] || {
18
+ automation_die "use android_orchestrator_intake draft to seal artifacts in the independent inbox"
19
+ exit 1
20
+ }
17
21
  automation_require_approval proposal "$approval"
18
22
  automation_assert_repository_lease_available "$task_id"
19
23
  "$SCRIPT_DIR/validate-contract.sh" "$task_id" >/dev/null
@@ -8,6 +8,7 @@ source "$SCRIPT_DIR/lib.sh"
8
8
  task_id="${1:-}"
9
9
  [[ -n "$task_id" ]] || { printf 'Usage: %s TASK-ID\n' "$0" >&2; exit 2; }
10
10
  automation_validate_task_id "$task_id"
11
+ automation_require_queue_execution "$task_id"
11
12
  [[ "$(automation_read_state "$task_id")" == "CODING" ]] || automation_die "$task_id is not CODING"
12
13
  "$SCRIPT_DIR/validate-contract.sh" "$task_id" >/dev/null
13
14
 
@@ -16,6 +16,7 @@ if [[ -z "$task_id" || -z "$expected" || "$separator" != "--" || -z "$filter" ||
16
16
  fi
17
17
 
18
18
  automation_validate_task_id "$task_id"
19
+ automation_require_queue_execution "$task_id"
19
20
  automation_validate_test_filter "$filter"
20
21
  [[ ${#expected} -ge 3 ]] || automation_die "expected failure text is too short"
21
22
  [[ "$(automation_read_state "$task_id")" == "CODING" ]] || automation_die "$task_id is not CODING"
@@ -8,6 +8,7 @@ source "$SCRIPT_DIR/lib.sh"
8
8
  task_id="${1:-}"
9
9
  [[ "$#" -eq 1 ]] || { printf 'Usage: %s TASK-ID\n' "$0" >&2; exit 2; }
10
10
  automation_validate_task_id "$task_id"
11
+ automation_require_queue_execution "$task_id"
11
12
  [[ "$(automation_read_state "$task_id")" == "CHANGES_REQUESTED" ]] || automation_die "$task_id is not CHANGES_REQUESTED"
12
13
 
13
14
  workspace_file="$(automation_workspace_path "$task_id")"
@@ -8,6 +8,7 @@ source "$SCRIPT_DIR/lib.sh"
8
8
  task_id="${1:-}"
9
9
  [[ "$#" -eq 1 ]] || { printf 'Usage: %s TASK-ID\n' "$0" >&2; exit 2; }
10
10
  automation_validate_task_id "$task_id"
11
+ automation_require_queue_execution "$task_id"
11
12
  automation_require_orchestrated
12
13
 
13
14
  workspace_file="$(automation_workspace_path "$task_id")"
@@ -13,6 +13,7 @@ if [[ "$#" -ne 2 ]]; then
13
13
  fi
14
14
 
15
15
  automation_validate_task_id "$task_id"
16
+ automation_require_queue_execution "$task_id"
16
17
  automation_require_orchestrated
17
18
  automation_require_approval resume "$approval"
18
19
 
@@ -17,9 +17,17 @@ baseline_meta="$evidence_dir/baseline.json"
17
17
 
18
18
  baseline_head="$(jq -er '.head' "$baseline_meta")"
19
19
  current_head="$(git -C "$AUTOMATION_ROOT" rev-parse HEAD)"
20
- [[ "$baseline_head" == "$current_head" ]] || { automation_die "HEAD changed after baseline"; exit 40; }
21
-
22
20
  workspace_file="$(automation_workspace_path "$task_id")"
21
+ if [[ "$baseline_head" != "$current_head" ]]; then
22
+ automation_require_queue_execution "$task_id"
23
+ jq -e --arg original "$baseline_head" --arg current "$current_head" \
24
+ '.queueKey != null and .workspaceStrategy == "isolatedWorktree" and
25
+ .originalBaselineHead == $original and .baselineHead == $current and
26
+ .revalidationRunId != null' "$workspace_file" >/dev/null || {
27
+ automation_die "HEAD changed after baseline"
28
+ exit 40
29
+ }
30
+ fi
23
31
  planning_commit_policy="$(jq -r '.planningArtifactsCommitPolicy // "legacyCommittedSeparately"' "$workspace_file")"
24
32
  if [[ "$planning_commit_policy" == "withProductChanges" ]]; then
25
33
  automation_assert_planning_artifacts_sealed "$task_id" "$AUTOMATION_ROOT" || exit 40
@@ -45,6 +45,8 @@ report_diff_sha="$(jq -er '.sealedDiffSha256' "$report_file")"
45
45
  changed_count="$(jq -er '.changedPaths | length' "$report_file")"
46
46
  max_changed_files="$(jq -er '.maxChangedFiles' "$report_file")"
47
47
  device_tests_required="$(jq -r '.deviceTestsRequired' "$report_file")"
48
+ title="$(jq -er '.title' "$report_file")"
49
+ commit_message="$(automation_commit_message_at "$source_root" "Implement $title ($task_id)")"
48
50
 
49
51
  printf '# 🔔 人工验收提醒\n\n'
50
52
  printf '自动执行已停在 `AWAITING_HUMAN`。下面内容已重新核对 sealed diff;此时代码、计划与任务合同均尚未提交或集成。\n\n'
@@ -58,6 +60,7 @@ printf '| 任务分支收尾 | 成功集成后自动删除;失败或阻塞时
58
60
  printf '| sealed diff SHA | `%s` |\n' "$report_diff_sha"
59
61
  printf '| 变更范围 | 实际 %s 个 / 合同上限 %s 个 |\n' "$changed_count" "$max_changed_files"
60
62
  printf '| 提交策略 | 代码、测试、计划与任务合同合并为一个提交 |\n'
63
+ printf '| 预计提交信息 | `%s` |\n' "$commit_message"
61
64
  printf '| 自动证据 | Baseline ✓ · RED ✓ · G1–G6 ✓ · Reviewer APPROVED ✓ |\n'
62
65
  printf '\n## 必须重点复核\n\n'
63
66
  printf '### P0 · 真实行为是否满足合同\n\n'
@@ -16,6 +16,7 @@ if [[ -z "$task_id" || -z "$decision" || -z "$summary" ]]; then
16
16
  fi
17
17
 
18
18
  automation_validate_task_id "$task_id"
19
+ automation_require_queue_execution "$task_id"
19
20
  [[ "$decision" == "APPROVED" || "$decision" == "CHANGES_REQUESTED" ]] || automation_die "invalid review decision: $decision"
20
21
  [[ ${#summary} -ge 20 ]] || automation_die "review summary must contain at least 20 characters"
21
22
  [[ "$(automation_read_state "$task_id")" == "REVIEWING" ]] || automation_die "$task_id is not REVIEWING"
@@ -64,8 +65,13 @@ jq -nc \
64
65
  | automation_append_json "$evidence_dir/reviews.jsonl"
65
66
 
66
67
  if [[ "$decision" == "APPROVED" ]]; then
67
- automation_transition_state "$task_id" "REVIEWING" "AWAITING_HUMAN" "reviewer" "independent review approved; human acceptance required"
68
- automation_info "$task_id approved and awaiting human acceptance"
68
+ workspace_file="$(automation_workspace_path "$task_id")"
69
+ if [[ -n "${AUTOMATION_QUEUE_RUN_ID:-}" && "$(jq -r '.commitPolicy // "humanApproval"' "$workspace_file")" == "autoCommit" ]]; then
70
+ automation_transition_state "$task_id" "REVIEWING" "READY_TO_COMMIT" "reviewer" "independent review approved; sealed automatic local commit authorization must be verified"
71
+ else
72
+ automation_transition_state "$task_id" "REVIEWING" "AWAITING_HUMAN" "reviewer" "independent review approved; human acceptance required"
73
+ automation_info "$task_id approved and awaiting human acceptance"
74
+ fi
69
75
  else
70
76
  automation_transition_state "$task_id" "REVIEWING" "CHANGES_REQUESTED" "reviewer" "$summary"
71
77
  automation_warn "$task_id requires changes"