@frankzhang2026/opencode-android-orchestrator 0.1.0 → 0.2.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 (124) hide show
  1. package/CHANGELOG.md +75 -2
  2. package/LICENSE +21 -0
  3. package/README.md +391 -6
  4. package/THIRD_PARTY_NOTICES.md +119 -0
  5. package/dist/cli.js +243 -1
  6. package/dist/cli.js.map +1 -1
  7. package/dist/compatibility/hooks.d.ts +23 -0
  8. package/dist/compatibility/hooks.d.ts.map +1 -0
  9. package/dist/compatibility/hooks.js +44 -0
  10. package/dist/compatibility/hooks.js.map +1 -0
  11. package/dist/compatibility/versions.d.ts +19 -0
  12. package/dist/compatibility/versions.d.ts.map +1 -1
  13. package/dist/compatibility/versions.js +116 -0
  14. package/dist/compatibility/versions.js.map +1 -1
  15. package/dist/doctor/index.d.ts +36 -0
  16. package/dist/doctor/index.d.ts.map +1 -0
  17. package/dist/doctor/index.js +289 -0
  18. package/dist/doctor/index.js.map +1 -0
  19. package/dist/doctor/installation.d.ts +4 -0
  20. package/dist/doctor/installation.d.ts.map +1 -0
  21. package/dist/doctor/installation.js +472 -0
  22. package/dist/doctor/installation.js.map +1 -0
  23. package/dist/index.d.ts +13 -2
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +13 -2
  26. package/dist/index.js.map +1 -1
  27. package/dist/installer/adaptive-templates.d.ts +56 -0
  28. package/dist/installer/adaptive-templates.d.ts.map +1 -0
  29. package/dist/installer/adaptive-templates.js +167 -0
  30. package/dist/installer/adaptive-templates.js.map +1 -0
  31. package/dist/installer/agents-config.d.ts +19 -0
  32. package/dist/installer/agents-config.d.ts.map +1 -0
  33. package/dist/installer/agents-config.js +147 -0
  34. package/dist/installer/agents-config.js.map +1 -0
  35. package/dist/installer/android-project.d.ts +36 -0
  36. package/dist/installer/android-project.d.ts.map +1 -0
  37. package/dist/installer/android-project.js +418 -0
  38. package/dist/installer/android-project.js.map +1 -0
  39. package/dist/installer/index.d.ts +8 -0
  40. package/dist/installer/index.d.ts.map +1 -1
  41. package/dist/installer/index.js +8 -1
  42. package/dist/installer/index.js.map +1 -1
  43. package/dist/installer/init.d.ts +67 -0
  44. package/dist/installer/init.d.ts.map +1 -0
  45. package/dist/installer/init.js +389 -0
  46. package/dist/installer/init.js.map +1 -0
  47. package/dist/installer/install-manifest.d.ts +132 -0
  48. package/dist/installer/install-manifest.d.ts.map +1 -0
  49. package/dist/installer/install-manifest.js +1047 -0
  50. package/dist/installer/install-manifest.js.map +1 -0
  51. package/dist/installer/opencode-config.d.ts +31 -0
  52. package/dist/installer/opencode-config.d.ts.map +1 -0
  53. package/dist/installer/opencode-config.js +296 -0
  54. package/dist/installer/opencode-config.js.map +1 -0
  55. package/dist/installer/uninstall.d.ts +70 -0
  56. package/dist/installer/uninstall.d.ts.map +1 -0
  57. package/dist/installer/uninstall.js +770 -0
  58. package/dist/installer/uninstall.js.map +1 -0
  59. package/dist/installer/upgrade.d.ts +96 -0
  60. package/dist/installer/upgrade.d.ts.map +1 -0
  61. package/dist/installer/upgrade.js +1106 -0
  62. package/dist/installer/upgrade.js.map +1 -0
  63. package/dist/opencode-plugin.d.ts +2 -0
  64. package/dist/opencode-plugin.d.ts.map +1 -0
  65. package/dist/opencode-plugin.js +4 -0
  66. package/dist/opencode-plugin.js.map +1 -0
  67. package/dist/plugin/index.d.ts +7 -2
  68. package/dist/plugin/index.d.ts.map +1 -1
  69. package/dist/plugin/index.js +23 -6
  70. package/dist/plugin/index.js.map +1 -1
  71. package/dist/tools/index.d.ts +35 -1
  72. package/dist/tools/index.d.ts.map +1 -1
  73. package/dist/tools/index.js +197 -1
  74. package/dist/tools/index.js.map +1 -1
  75. package/docs/MIGRATION.md +173 -0
  76. package/docs/SECURITY.md +212 -0
  77. package/docs/TROUBLESHOOTING.md +172 -0
  78. package/package.json +23 -5
  79. package/templates/.opencode/agents/scheduled-coder.md +115 -0
  80. package/templates/.opencode/agents/scheduled-planner.md +166 -0
  81. package/templates/.opencode/agents/scheduled-reviewer.md +88 -0
  82. package/templates/.opencode/commands/abort-task.md +20 -0
  83. package/templates/.opencode/commands/acceptance.md +20 -0
  84. package/templates/.opencode/commands/change.md +23 -0
  85. package/templates/.opencode/commands/resume-review.md +19 -0
  86. package/templates/.opencode/skills/scheduled-quality-coder/SKILL.md +93 -0
  87. package/templates/.opencode/skills/scheduled-quality-orchestrator/SKILL.md +216 -0
  88. package/templates/.opencode/skills/scheduled-quality-reviewer/SKILL.md +68 -0
  89. package/templates/AGENTS.md.fragment +19 -0
  90. package/templates/README.md +52 -9
  91. package/templates/automation/config.json +50 -0
  92. package/templates/automation/config.schema.json +163 -0
  93. package/templates/automation/task-contract.schema.json +71 -0
  94. package/templates/automation/tasks/TASK-TEMPLATE.json.example +53 -0
  95. package/templates/docs/plans/README.md +26 -0
  96. package/templates/installation-manifest.schema.json +201 -0
  97. package/templates/scripts/automation/abort-task.sh +171 -0
  98. package/templates/scripts/automation/accept-and-integrate.sh +263 -0
  99. package/templates/scripts/automation/acceptance-report.sh +132 -0
  100. package/templates/scripts/automation/approve-and-run.sh +175 -0
  101. package/templates/scripts/automation/begin-review.sh +18 -0
  102. package/templates/scripts/automation/block-task.sh +26 -0
  103. package/templates/scripts/automation/claim-task.sh +74 -0
  104. package/templates/scripts/automation/integration-scope-gate.sh +103 -0
  105. package/templates/scripts/automation/lib.sh +697 -0
  106. package/templates/scripts/automation/orchestrate-task.sh +119 -0
  107. package/templates/scripts/automation/preflight.sh +206 -0
  108. package/templates/scripts/automation/prepare-contract-review.sh +101 -0
  109. package/templates/scripts/automation/quality-gate.sh +72 -0
  110. package/templates/scripts/automation/queue-task.sh +35 -0
  111. package/templates/scripts/automation/record-red.sh +57 -0
  112. package/templates/scripts/automation/resume-review-fix.sh +32 -0
  113. package/templates/scripts/automation/resume-review.sh +120 -0
  114. package/templates/scripts/automation/scope-gate.sh +110 -0
  115. package/templates/scripts/automation/select-task.sh +34 -0
  116. package/templates/scripts/automation/shadow-run.sh +24 -0
  117. package/templates/scripts/automation/show-acceptance-review.sh +111 -0
  118. package/templates/scripts/automation/status.sh +125 -0
  119. package/templates/scripts/automation/submit-review.sh +73 -0
  120. package/templates/scripts/automation/tests/run-tests.sh +699 -0
  121. package/templates/scripts/automation/transition-state.sh +21 -0
  122. package/templates/scripts/automation/validate-contract.sh +87 -0
  123. package/templates/scripts/automation/verify-integration.sh +37 -0
  124. package/templates/scripts/automation/verify-task.sh +54 -0
@@ -0,0 +1,119 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
5
+ # shellcheck source=lib.sh
6
+ source "$SCRIPT_DIR/lib.sh"
7
+
8
+ task_id="${1:-}"
9
+ [[ "$#" -eq 1 ]] || { printf 'Usage: %s TASK-ID\n' "$0" >&2; exit 2; }
10
+ automation_validate_task_id "$task_id"
11
+ automation_require_orchestrated
12
+
13
+ workspace_file="$(automation_workspace_path "$task_id")"
14
+ [[ -f "$workspace_file" ]] || automation_die "workspace metadata is missing for $task_id"
15
+ task_root="$(automation_workspace_task_root "$workspace_file")"
16
+ workspace_strategy="$(automation_workspace_strategy "$workspace_file")"
17
+ source_root="$(jq -er '.sourceRoot' "$workspace_file")"
18
+ [[ -d "$task_root" ]] || automation_die "task root is missing: $task_root"
19
+ if [[ "$(jq -r '.repositoryLeaseRequired // false' "$workspace_file")" == "true" ]]; then
20
+ automation_require_repository_lease "$task_id" "$source_root" "$workspace_strategy"
21
+ fi
22
+
23
+ automation_acquire_run_lock "$task_id"
24
+ trap 'automation_release_run_lock' EXIT
25
+
26
+ if [[ "${AUTOMATION_SKIP_AGENT_RUN:-0}" == "1" ]]; then
27
+ [[ "${AUTOMATION_TEST_MODE:-0}" == "1" ]] || automation_die "AUTOMATION_SKIP_AGENT_RUN is reserved for tests"
28
+ automation_info "test mode: task is prepared; agent launch skipped"
29
+ exit 0
30
+ fi
31
+ automation_require_command opencode
32
+
33
+ run_agent() {
34
+ local role="$1"
35
+ local prompt="$2"
36
+ local cycle="$3"
37
+ local attempt="${4:-0}"
38
+ local log_file exit_code
39
+ if [[ "$attempt" -eq 0 ]]; then
40
+ log_file="$(automation_evidence_path "$task_id")/${role}-cycle-${cycle}.log"
41
+ else
42
+ log_file="$(automation_evidence_path "$task_id")/${role}-cycle-${cycle}-attempt-${attempt}.log"
43
+ fi
44
+ automation_info "starting $role for $task_id in $task_root"
45
+ set +e
46
+ (
47
+ cd "$task_root"
48
+ opencode run --agent "$role" -- "$prompt"
49
+ ) 2>&1 | tee "$log_file"
50
+ exit_code=${PIPESTATUS[0]}
51
+ set -e
52
+ jq -nc \
53
+ --arg taskId "$task_id" \
54
+ --arg role "$role" \
55
+ --argjson cycle "$cycle" \
56
+ --argjson attempt "$attempt" \
57
+ --arg at "$(automation_now)" \
58
+ --argjson exitCode "$exit_code" \
59
+ '{taskId: $taskId, role: $role, cycle: $cycle, attempt: $attempt, at: $at, exitCode: $exitCode}' \
60
+ | automation_append_json "$(automation_evidence_path "$task_id")/agent-runs.jsonl"
61
+ return "$exit_code"
62
+ }
63
+
64
+ for _step in 1 2 3 4 5 6 7 8; do
65
+ state="$(automation_read_state "$task_id")"
66
+ case "$state" in
67
+ PENDING|CODING)
68
+ coding_cycle="$(jq -er '.codingCycle // 0' "$workspace_file")"
69
+ coder_prompt="Use \$scheduled-quality-coder with $task_id. This is an orchestrated non-interactive run. Follow the deterministic state and evidence scripts. Never commit, merge, create worktrees, or push."
70
+ run_agent scheduled-coder "$coder_prompt" "$coding_cycle" || true
71
+ next_state="$(automation_read_state "$task_id")"
72
+ if [[ "$next_state" == "PENDING" || "$next_state" == "CODING" ]]; then
73
+ automation_transition_state "$task_id" "$next_state" "BLOCKED" "orchestrator" "coder exited without reaching a terminal gate state"
74
+ fi
75
+ ;;
76
+ READY_FOR_REVIEW)
77
+ (
78
+ cd "$task_root"
79
+ ./scripts/automation/begin-review.sh "$task_id"
80
+ )
81
+ ;;
82
+ REVIEWING)
83
+ review_cycle="$(jq -er '.reviewCycles // 0' "$workspace_file")"
84
+ agent_runs="$(automation_evidence_path "$task_id")/agent-runs.jsonl"
85
+ review_attempt=0
86
+ if [[ -f "$agent_runs" ]]; then
87
+ review_attempt="$(jq -s --argjson cycle "$review_cycle" '[.[] | select(.role == "scheduled-reviewer" and .cycle == $cycle)] | length' "$agent_runs")"
88
+ fi
89
+ reviewer_prompt="Use \$scheduled-quality-reviewer with $task_id. This is fresh, non-interactive, read-only review attempt $review_attempt for sealed review cycle $review_cycle. Work within the step budget. Run exactly ./scripts/automation/status.sh $task_id once; its JSON already includes baseline, RED, ready, latest gate, current diff SHA, and the sealed-SHA comparison, so do not browse the external evidence directory. Then inspect the approved contract and run exactly git diff. Submit exactly one decision. Do not browse generated build reports, list the whole repository, retry denied command variants, or run Gradle commands separately before deciding: submit-review.sh performs and records the focused tests, full unit suite, assembleDebug, and lint for APPROVED. Reserve a tool call for submit-review.sh. Never edit, commit, merge, or push."
90
+ run_agent scheduled-reviewer "$reviewer_prompt" "$review_cycle" "$review_attempt" || true
91
+ if [[ "$(automation_read_state "$task_id")" == "REVIEWING" ]]; then
92
+ automation_transition_state "$task_id" "REVIEWING" "BLOCKED" "orchestrator" "reviewer exited without submitting a decision; sealed diff preserved for reviewer-only recovery"
93
+ fi
94
+ ;;
95
+ CHANGES_REQUESTED)
96
+ (
97
+ cd "$task_root"
98
+ ./scripts/automation/resume-review-fix.sh "$task_id"
99
+ ) || true
100
+ ;;
101
+ AWAITING_HUMAN)
102
+ automation_info "$task_id reached AWAITING_HUMAN"
103
+ (
104
+ cd "$task_root"
105
+ ./scripts/automation/acceptance-report.sh "$task_id" >/dev/null
106
+ )
107
+ "$SCRIPT_DIR/show-acceptance-review.sh" "$task_id"
108
+ exit 0
109
+ ;;
110
+ BLOCKED|TEST_FAILED|NEEDS_HUMAN|INTEGRATION_BLOCKED)
111
+ automation_die "$task_id stopped in $state; inspect $(automation_evidence_path "$task_id")"
112
+ ;;
113
+ *)
114
+ automation_die "cannot orchestrate $task_id from state $state"
115
+ ;;
116
+ esac
117
+ done
118
+
119
+ automation_die "$task_id exceeded the deterministic orchestration step bound"
@@ -0,0 +1,206 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
5
+ # shellcheck source=lib.sh
6
+ source "$SCRIPT_DIR/lib.sh"
7
+
8
+ shadow=0
9
+ source_mode=0
10
+ task_id=""
11
+ for arg in "$@"; do
12
+ case "$arg" in
13
+ --shadow) shadow=1 ;;
14
+ --source) source_mode=1 ;;
15
+ TASK-*) task_id="$arg" ;;
16
+ *) printf 'Usage: %s [--shadow|--source] [TASK-ID]\n' "$0" >&2; exit 2 ;;
17
+ esac
18
+ done
19
+ [[ "$shadow" != "1" || "$source_mode" != "1" ]] || automation_die "--shadow and --source are mutually exclusive"
20
+
21
+ fail_or_warn() {
22
+ if [[ "$shadow" == "1" ]]; then
23
+ automation_warn "$1"
24
+ return 0
25
+ fi
26
+ automation_die "$1"
27
+ }
28
+
29
+ automation_require_layout
30
+ automation_require_command git
31
+ automation_require_command jq
32
+ automation_require_command rg
33
+ automation_require_command shasum
34
+ if [[ "${AUTOMATION_TEST_MODE:-0}" != "1" ]]; then
35
+ automation_require_command opencode
36
+ fi
37
+ [[ -x "$AUTOMATION_ROOT/gradlew" ]] || automation_die "gradlew is missing or not executable"
38
+
39
+ automation_validate_config
40
+ workspace_strategy="$(automation_config_value '.workspaceStrategy')"
41
+ if [[ "$workspace_strategy" == "isolatedWorktree" ]]; then
42
+ automation_worktree_base >/dev/null
43
+ fi
44
+
45
+ enabled="$(automation_config_value '.enabled')"
46
+ mode="$(automation_config_value '.mode')"
47
+
48
+ [[ "$enabled" == "true" ]] || fail_or_warn "automation is disabled"
49
+ [[ "$mode" == "orchestrated" ]] || fail_or_warn "automation mode is $mode, not orchestrated"
50
+
51
+ if [[ "${AUTOMATION_TEST_MODE:-0}" != "1" ]]; then
52
+ [[ -n "${ANDROID_HOME:-}" ]] || fail_or_warn "ANDROID_HOME is not set; configure the Android SDK environment before running OpenCode"
53
+ if [[ -n "${ANDROID_HOME:-}" ]]; then
54
+ [[ -d "$ANDROID_HOME" ]] || fail_or_warn "ANDROID_HOME does not exist: $ANDROID_HOME"
55
+ fi
56
+ fi
57
+
58
+ if [[ -z "$task_id" ]] && ! automation_worktree_is_clean; then
59
+ fail_or_warn "Git worktree is dirty"
60
+ fi
61
+
62
+ if [[ "$source_mode" == "1" ]]; then
63
+ repository_lease_file="$(automation_repository_lease_dir)/lease.json"
64
+ if [[ -f "$repository_lease_file" ]]; then
65
+ fail_or_warn "repository workspace is leased by $(jq -r '.taskId // "an unknown task"' "$repository_lease_file")"
66
+ elif [[ -d "$(automation_repository_lease_dir)" ]]; then
67
+ fail_or_warn "repository workspace lease is malformed"
68
+ fi
69
+ automation_current_branch >/dev/null
70
+ git -C "$AUTOMATION_ROOT" config user.name >/dev/null || automation_die "Git user.name is not configured"
71
+ git -C "$AUTOMATION_ROOT" config user.email >/dev/null || automation_die "Git user.email is not configured"
72
+ fi
73
+
74
+ if [[ -n "$task_id" ]]; then
75
+ automation_validate_task_id "$task_id"
76
+ "$SCRIPT_DIR/validate-contract.sh" "$task_id"
77
+ workspace_file="$(automation_workspace_path "$task_id")"
78
+ [[ -f "$workspace_file" ]] || fail_or_warn "workspace metadata is missing for $task_id"
79
+ if [[ -f "$workspace_file" ]]; then
80
+ task_root="$(automation_workspace_task_root "$workspace_file")"
81
+ task_strategy="$(automation_workspace_strategy "$workspace_file")"
82
+ source_root="$(jq -er '.sourceRoot' "$workspace_file")"
83
+ task_branch="$(jq -er '.taskBranch' "$workspace_file")"
84
+ baseline_head="$(jq -er '.baselineHead' "$workspace_file")"
85
+ resolved_task_root="$(cd "$task_root" 2>/dev/null && pwd || true)"
86
+ [[ "$resolved_task_root" == "$AUTOMATION_ROOT" ]] || fail_or_warn "run must occur in the recorded task root: $task_root"
87
+ [[ "$(automation_current_branch)" == "$task_branch" ]] || fail_or_warn "current branch does not match task branch: $task_branch"
88
+ [[ "$(git -C "$AUTOMATION_ROOT" rev-parse HEAD)" == "$baseline_head" ]] || fail_or_warn "task HEAD changed outside the deterministic integrator"
89
+ planning_commit_policy="$(jq -r '.planningArtifactsCommitPolicy // "legacyCommittedSeparately"' "$workspace_file")"
90
+ if [[ "$planning_commit_policy" == "withProductChanges" ]]; then
91
+ automation_assert_planning_artifacts_sealed "$task_id" "$AUTOMATION_ROOT" || \
92
+ fail_or_warn "approved planning artifact validation failed for $task_id"
93
+ pending_path_count=0
94
+ while IFS= read -r pending_path; do
95
+ [[ -n "$pending_path" ]] || continue
96
+ pending_path_count=$((pending_path_count + 1))
97
+ automation_is_planning_artifact "$task_id" "$pending_path" || \
98
+ fail_or_warn "unexpected change exists before Coder starts: $pending_path"
99
+ if git -C "$AUTOMATION_ROOT" ls-files --error-unmatch -- "$pending_path" >/dev/null 2>&1; then
100
+ fail_or_warn "planning artifact was committed before product changes: $pending_path"
101
+ fi
102
+ done < <(automation_changed_paths)
103
+ [[ "$pending_path_count" -eq 2 ]] || fail_or_warn "task must start with exactly two sealed, uncommitted planning artifacts"
104
+ elif ! automation_worktree_is_clean; then
105
+ fail_or_warn "Git worktree is dirty"
106
+ fi
107
+ if [[ "$task_strategy" == "inPlaceExclusive" ]]; then
108
+ [[ "$task_root" == "$source_root" ]] || fail_or_warn "in-place task root must equal the recorded source root"
109
+ elif [[ "$task_strategy" == "isolatedWorktree" ]]; then
110
+ [[ "$task_root" != "$source_root" ]] || fail_or_warn "isolated task root must differ from the source root"
111
+ else
112
+ fail_or_warn "unsupported workspace strategy: $task_strategy"
113
+ fi
114
+ if [[ "$(jq -r '.repositoryLeaseRequired // false' "$workspace_file")" == "true" ]]; then
115
+ automation_require_repository_lease "$task_id" "$source_root" "$task_strategy" || \
116
+ fail_or_warn "repository workspace lease validation failed for $task_id"
117
+ fi
118
+ fi
119
+ fi
120
+
121
+ if [[ "${AUTOMATION_TEST_MODE:-0}" == "1" ]]; then
122
+ automation_info "test mode: OpenCode discovery checks skipped"
123
+ else
124
+ discovery_dir="$(mktemp -d "${TMPDIR:-/tmp}/opencode-android-orchestrator-preflight.XXXXXX")"
125
+ trap 'rm -rf "$discovery_dir"' EXIT
126
+
127
+ if ! opencode debug config > "$discovery_dir/config.json" 2> "$discovery_dir/config.err"; then
128
+ automation_die "OpenCode resolved config failed; see $discovery_dir/config.err"
129
+ fi
130
+ superpowers_pin="$(jq -r '.plugins.superpowers' "$AUTOMATION_CONFIG")"
131
+ rg -F "$superpowers_pin" "$discovery_dir/config.json" >/dev/null || automation_die "resolved OpenCode config is missing pinned Superpowers"
132
+
133
+ if ! opencode debug skill > "$discovery_dir/skills.txt" 2> "$discovery_dir/skills.err"; then
134
+ automation_die "OpenCode skill discovery failed; see $discovery_dir/skills.err"
135
+ fi
136
+ while IFS= read -r skill; do
137
+ rg -F "\"$skill\"" "$discovery_dir/skills.txt" >/dev/null || \
138
+ rg -F "$skill" "$discovery_dir/skills.txt" >/dev/null || \
139
+ automation_die "required skill is not discoverable: $skill"
140
+ done < <(jq -r '.requiredSkills[]' "$AUTOMATION_CONFIG")
141
+
142
+ opencode debug agent scheduled-coder > "$discovery_dir/coder-agent.json" 2> "$discovery_dir/coder-agent.err" || \
143
+ automation_die "scheduled-coder agent is not discoverable"
144
+ opencode debug agent scheduled-reviewer > "$discovery_dir/reviewer-agent.json" 2> "$discovery_dir/reviewer-agent.err" || \
145
+ automation_die "scheduled-reviewer agent is not discoverable"
146
+ opencode debug agent scheduled-planner > "$discovery_dir/planner-agent.json" 2> "$discovery_dir/planner-agent.err" || \
147
+ automation_die "scheduled-planner agent is not discoverable"
148
+
149
+ jq -e '
150
+ def last_rule($permission; $pattern):
151
+ [.permission[] | select(.permission == $permission and .pattern == $pattern) | .action][-1];
152
+ (last_rule("*"; "*") == "deny") and
153
+ (last_rule("edit"; "docs/plans/**") == "allow") and
154
+ (last_rule("edit"; "automation/tasks/**") == "allow") and
155
+ (last_rule("edit"; "**/src/**") == "deny") and
156
+ (last_rule("bash"; "./scripts/automation/prepare-contract-review.sh *") == "allow") and
157
+ (last_rule("bash"; "./scripts/automation/approve-and-run.sh *") == "allow") and
158
+ (last_rule("bash"; "./scripts/automation/resume-review.sh *") == "allow") and
159
+ (last_rule("bash"; "./scripts/automation/accept-and-integrate.sh *") == "allow") and
160
+ (last_rule("bash"; "./scripts/automation/abort-task.sh *") == "allow") and
161
+ (last_rule("android_orchestrator_status"; "*") == "allow") and
162
+ (last_rule("android_orchestrator_doctor"; "*") == "allow") and
163
+ (last_rule("schedule_job"; "*") == "deny") and
164
+ (last_rule("task"; "*") == "deny") and
165
+ (.tools.question == true) and
166
+ (.tools.android_orchestrator_status == true) and
167
+ (.tools.android_orchestrator_doctor == true) and
168
+ ((.tools.schedule_job // false) == false) and
169
+ ((.tools.task // false) == false)
170
+ ' "$discovery_dir/planner-agent.json" >/dev/null || automation_die "scheduled-planner resolved permissions are unsafe"
171
+
172
+ jq -e '
173
+ def last_rule($permission; $pattern):
174
+ [.permission[] | select(.permission == $permission and .pattern == $pattern) | .action][-1];
175
+ (last_rule("*"; "*") == "deny") and
176
+ (last_rule("edit"; "**/src/main/**") == "allow") and
177
+ (last_rule("edit"; ".opencode/skills/**") == "deny") and
178
+ (last_rule("android_orchestrator_status"; "*") == "allow") and
179
+ (last_rule("android_orchestrator_doctor"; "*") == "allow") and
180
+ (last_rule("schedule_job"; "*") == "deny") and
181
+ (last_rule("task"; "*") == "deny") and
182
+ (.tools.android_orchestrator_status == true) and
183
+ (.tools.android_orchestrator_doctor == true) and
184
+ ((.tools.schedule_job // false) == false) and
185
+ ((.tools.delete_job // false) == false) and
186
+ ((.tools.task // false) == false)
187
+ ' "$discovery_dir/coder-agent.json" >/dev/null || automation_die "scheduled-coder resolved permissions are unsafe"
188
+
189
+ jq -e '
190
+ def last_rule($permission; $pattern):
191
+ [.permission[] | select(.permission == $permission and .pattern == $pattern) | .action][-1];
192
+ (last_rule("*"; "*") == "deny") and
193
+ (last_rule("edit"; "*") == "deny") and
194
+ (last_rule("android_orchestrator_status"; "*") == "allow") and
195
+ (last_rule("android_orchestrator_doctor"; "*") == "allow") and
196
+ (last_rule("schedule_job"; "*") == "deny") and
197
+ (last_rule("task"; "*") == "deny") and
198
+ (.tools.android_orchestrator_status == true) and
199
+ (.tools.android_orchestrator_doctor == true) and
200
+ ((.tools.schedule_job // false) == false) and
201
+ ((.tools.delete_job // false) == false) and
202
+ ((.tools.task // false) == false)
203
+ ' "$discovery_dir/reviewer-agent.json" >/dev/null || automation_die "scheduled-reviewer resolved permissions are unsafe"
204
+ fi
205
+
206
+ automation_info "preflight completed (mode=$mode, shadow=$shadow, source=$source_mode${task_id:+, task=$task_id}, sdk=${ANDROID_HOME:-unset})"
@@ -0,0 +1,101 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
5
+ # shellcheck source=lib.sh
6
+ source "$SCRIPT_DIR/lib.sh"
7
+
8
+ task_id="${1:-}"
9
+ approval="${2:-}"
10
+ if [[ "$#" -ne 2 ]]; then
11
+ printf 'Usage: %s TASK-ID "PROPOSAL-APPROVAL"\n' "$0" >&2
12
+ exit 2
13
+ fi
14
+
15
+ automation_validate_task_id "$task_id"
16
+ automation_require_orchestrated
17
+ automation_require_approval proposal "$approval"
18
+ automation_assert_repository_lease_available "$task_id"
19
+ "$SCRIPT_DIR/validate-contract.sh" "$task_id" >/dev/null
20
+
21
+ contract="$(automation_contract_path "$task_id")"
22
+ contract_rel="automation/tasks/$task_id.json"
23
+ plan_rel="$(jq -er '.planPath' "$contract")"
24
+ plan="$AUTOMATION_ROOT/$plan_rel"
25
+ state_file="$(automation_state_path "$task_id")"
26
+ origin_file="$(automation_origin_path "$task_id")"
27
+
28
+ changed_paths=()
29
+ while IFS= read -r path; do
30
+ [[ -n "$path" ]] && changed_paths+=("$path")
31
+ done < <(automation_changed_paths)
32
+ if [[ "${#changed_paths[@]}" -ne 2 ]] || \
33
+ [[ " ${changed_paths[*]} " != *" $contract_rel "* ]] || \
34
+ [[ " ${changed_paths[*]} " != *" $plan_rel "* ]]; then
35
+ automation_die "before contract review, the only changed paths must be $plan_rel and $contract_rel"
36
+ fi
37
+ for planning_path in "$plan_rel" "$contract_rel"; do
38
+ if git -C "$AUTOMATION_ROOT" ls-files --error-unmatch -- "$planning_path" >/dev/null 2>&1; then
39
+ automation_die "planning artifact must be a new untracked file: $planning_path"
40
+ fi
41
+ done
42
+
43
+ original_branch="$(automation_current_branch)"
44
+ original_head="$(git -C "$AUTOMATION_ROOT" rev-parse HEAD)"
45
+ contract_sha="$(automation_file_sha256 "$contract")"
46
+ plan_sha="$(automation_file_sha256 "$plan")"
47
+
48
+ automation_acquire_run_lock "$task_id"
49
+ trap 'automation_release_run_lock' EXIT
50
+
51
+ if [[ -f "$state_file" ]]; then
52
+ [[ "$(automation_read_state "$task_id")" == "CONTRACT_REVIEW" ]] || \
53
+ automation_die "task already has runtime state: $(automation_read_state "$task_id")"
54
+ [[ -f "$origin_file" ]] || automation_die "existing contract review has no origin evidence"
55
+ jq -e \
56
+ --arg root "$AUTOMATION_ROOT" \
57
+ --arg branch "$original_branch" \
58
+ --arg head "$original_head" \
59
+ --arg contractSha "$contract_sha" \
60
+ --arg planSha "$plan_sha" \
61
+ '.sourceRoot == $root and .originalBranch == $branch and
62
+ .originalHeadBeforeContract == $head and
63
+ .contractSha256 == $contractSha and .planSha256 == $planSha' \
64
+ "$origin_file" >/dev/null || automation_die "contract review artifacts changed; create a new task ID"
65
+ automation_info "$task_id is already waiting for contract review"
66
+ "$SCRIPT_DIR/status.sh" "$task_id"
67
+ exit 0
68
+ fi
69
+
70
+ automation_ensure_runtime_layout
71
+ mkdir -p "$(automation_evidence_path "$task_id")"
72
+ jq -n \
73
+ --arg taskId "$task_id" \
74
+ --arg sourceRoot "$AUTOMATION_ROOT" \
75
+ --arg originalBranch "$original_branch" \
76
+ --arg originalHeadBeforeContract "$original_head" \
77
+ --arg contractPath "$contract_rel" \
78
+ --arg planPath "$plan_rel" \
79
+ --arg contractSha256 "$contract_sha" \
80
+ --arg planSha256 "$plan_sha" \
81
+ --arg preparedAt "$(automation_now)" \
82
+ '{taskId: $taskId, sourceRoot: $sourceRoot, originalBranch: $originalBranch,
83
+ originalHeadBeforeContract: $originalHeadBeforeContract,
84
+ contractPath: $contractPath, planPath: $planPath,
85
+ contractSha256: $contractSha256, planSha256: $planSha256,
86
+ preparedAt: $preparedAt}' \
87
+ | automation_record_json "$origin_file"
88
+
89
+ jq -nc \
90
+ --arg taskId "$task_id" \
91
+ --arg kind "proposal" \
92
+ --arg at "$(automation_now)" \
93
+ --arg contractSha256 "$contract_sha" \
94
+ --arg planSha256 "$plan_sha" \
95
+ '{taskId: $taskId, kind: $kind, at: $at,
96
+ contractSha256: $contractSha256, planSha256: $planSha256}' \
97
+ | automation_append_json "$(automation_approvals_path "$task_id")"
98
+
99
+ automation_initialize_state "$task_id" "CONTRACT_REVIEW" "planner" "proposal approved; generated artifacts await contract review"
100
+ automation_info "$task_id is ready for human contract review on $original_branch at $original_head"
101
+ "$SCRIPT_DIR/status.sh" "$task_id"
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
5
+ # shellcheck source=lib.sh
6
+ source "$SCRIPT_DIR/lib.sh"
7
+
8
+ task_id="${1:-}"
9
+ [[ -n "$task_id" ]] || { printf 'Usage: %s TASK-ID\n' "$0" >&2; exit 2; }
10
+ automation_validate_task_id "$task_id"
11
+ [[ "$(automation_read_state "$task_id")" == "CODING" ]] || automation_die "$task_id is not CODING"
12
+ "$SCRIPT_DIR/validate-contract.sh" "$task_id" >/dev/null
13
+
14
+ contract="$(automation_contract_path "$task_id")"
15
+ evidence_dir="$(automation_evidence_path "$task_id")"
16
+ mkdir -p "$evidence_dir"
17
+ workspace_file="$(automation_workspace_path "$task_id")"
18
+ coding_cycle=0
19
+ if [[ -f "$workspace_file" ]]; then
20
+ coding_cycle="$(jq -er '.codingCycle // 0' "$workspace_file")"
21
+ fi
22
+ attempt_file="$evidence_dir/gate-attempts-cycle-$coding_cycle.json"
23
+ previous_attempts=0
24
+ if [[ -f "$attempt_file" ]]; then
25
+ previous_attempts="$(jq -er '.attempts' "$attempt_file")"
26
+ fi
27
+ attempt=$((previous_attempts + 1))
28
+ max_fix_loops="$(jq -r '.maxFixLoops' "$contract")"
29
+ max_attempts=$((max_fix_loops + 1))
30
+ [[ "$attempt" -le "$max_attempts" ]] || automation_die "quality gate attempt limit already exhausted"
31
+
32
+ attempt_log="$evidence_dir/gate-cycle-$coding_cycle-attempt-$attempt.log"
33
+ started_at="$(automation_now)"
34
+ set +e
35
+ "$SCRIPT_DIR/verify-task.sh" "$task_id" 2>&1 | tee "$attempt_log"
36
+ gate_status=${PIPESTATUS[0]}
37
+ set -e
38
+
39
+ jq -n \
40
+ --arg taskId "$task_id" \
41
+ --argjson attempts "$attempt" \
42
+ --argjson codingCycle "$coding_cycle" \
43
+ --argjson maxAttempts "$max_attempts" \
44
+ --arg startedAt "$started_at" \
45
+ --arg finishedAt "$(automation_now)" \
46
+ --argjson exitCode "$gate_status" \
47
+ '{taskId: $taskId, codingCycle: $codingCycle, attempts: $attempts, maxAttempts: $maxAttempts, lastStartedAt: $startedAt, lastFinishedAt: $finishedAt, lastExitCode: $exitCode}' \
48
+ | automation_record_json "$attempt_file"
49
+
50
+ if [[ "$gate_status" -ne 0 ]]; then
51
+ if [[ "$gate_status" -eq 40 || "$gate_status" -eq 41 ]]; then
52
+ automation_transition_state "$task_id" "CODING" "BLOCKED" "quality-gate" "scope, integrity, or evidence gate failed with exit $gate_status"
53
+ elif [[ "$attempt" -ge "$max_attempts" ]]; then
54
+ automation_transition_state "$task_id" "CODING" "TEST_FAILED" "quality-gate" "verification failed after $attempt attempts"
55
+ else
56
+ automation_warn "verification failed; one systematic fix loop remains"
57
+ fi
58
+ exit "$gate_status"
59
+ fi
60
+
61
+ diff_sha="$(automation_worktree_diff_sha)"
62
+ jq -n \
63
+ --arg taskId "$task_id" \
64
+ --arg verifiedAt "$(automation_now)" \
65
+ --arg head "$(git -C "$AUTOMATION_ROOT" rev-parse HEAD)" \
66
+ --arg diffSha256 "$diff_sha" \
67
+ --argjson attempts "$attempt" \
68
+ --argjson codingCycle "$coding_cycle" \
69
+ '{taskId: $taskId, verifiedAt: $verifiedAt, head: $head, diffSha256: $diffSha256, gateAttempts: $attempts, codingCycle: $codingCycle}' \
70
+ | automation_record_json "$evidence_dir/ready.json"
71
+
72
+ automation_transition_state "$task_id" "CODING" "READY_FOR_REVIEW" "quality-gate" "G1-G6 passed with fresh evidence"
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
5
+ # shellcheck source=lib.sh
6
+ source "$SCRIPT_DIR/lib.sh"
7
+
8
+ task_id="${1:-}"
9
+ [[ -n "$task_id" ]] || { printf 'Usage: AUTOMATION_HUMAN_APPROVED=1 %s TASK-ID\n' "$0" >&2; exit 2; }
10
+ automation_validate_task_id "$task_id"
11
+ automation_require_layout
12
+
13
+ [[ "${AUTOMATION_HUMAN_APPROVED:-0}" == "1" ]] || automation_die "set AUTOMATION_HUMAN_APPROVED=1 after explicit human approval"
14
+ [[ "$(automation_config_value '.enabled')" == "true" ]] || automation_die "automation is disabled"
15
+ [[ "$(automation_config_value '.mode')" == "orchestrated" ]] || automation_die "automation mode is not orchestrated"
16
+
17
+ "$SCRIPT_DIR/validate-contract.sh" "$task_id"
18
+ state_file="$(automation_state_path "$task_id")"
19
+
20
+ if [[ ! -f "$state_file" ]]; then
21
+ automation_initialize_state "$task_id" "APPROVED_CONTRACT" "human" "legacy manual queue initialized"
22
+ fi
23
+
24
+ current="$(automation_read_state "$task_id")"
25
+ case "$current" in
26
+ APPROVED_CONTRACT|TEST_FAILED|BLOCKED|CHANGES_REQUESTED)
27
+ automation_transition_state "$task_id" "$current" "PENDING" "human" "explicit human queue approval"
28
+ ;;
29
+ PENDING)
30
+ automation_info "$task_id is already PENDING"
31
+ ;;
32
+ *)
33
+ automation_die "cannot queue $task_id from state $current"
34
+ ;;
35
+ esac
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
5
+ # shellcheck source=lib.sh
6
+ source "$SCRIPT_DIR/lib.sh"
7
+
8
+ task_id="${1:-}"
9
+ expected="${2:-}"
10
+ separator="${3:-}"
11
+ filter="${4:-}"
12
+
13
+ if [[ -z "$task_id" || -z "$expected" || "$separator" != "--" || -z "$filter" || "$#" -ne 4 ]]; then
14
+ printf 'Usage: %s TASK-ID EXPECTED-FAILURE-TEXT -- TEST-FILTER\n' "$0" >&2
15
+ exit 2
16
+ fi
17
+
18
+ automation_validate_task_id "$task_id"
19
+ safe_filter_pattern='^[A-Za-z0-9_.#$*-]+$'
20
+ [[ "$filter" =~ $safe_filter_pattern ]] || automation_die "unsafe test filter: $filter"
21
+ [[ ${#expected} -ge 3 ]] || automation_die "expected failure text is too short"
22
+ [[ "$(automation_read_state "$task_id")" == "CODING" ]] || automation_die "$task_id is not CODING"
23
+ "$SCRIPT_DIR/validate-contract.sh" "$task_id"
24
+
25
+ contract="$(automation_contract_path "$task_id")"
26
+ jq -e --arg filter "$filter" '.targetTests | index($filter) != null' "$contract" >/dev/null || \
27
+ automation_die "test filter is not declared in the contract: $filter"
28
+
29
+ evidence_dir="$(automation_evidence_path "$task_id")"
30
+ mkdir -p "$evidence_dir"
31
+ red_log="$evidence_dir/red.log"
32
+ red_meta="$evidence_dir/red.json"
33
+ [[ ! -e "$red_meta" ]] || automation_die "RED evidence already exists for $task_id"
34
+ started_at="$(automation_now)"
35
+
36
+ set +e
37
+ (
38
+ cd "$AUTOMATION_ROOT"
39
+ ./gradlew testDebugUnitTest --tests "$filter"
40
+ ) 2>&1 | tee "$red_log"
41
+ red_status=${PIPESTATUS[0]}
42
+ set -e
43
+
44
+ [[ "$red_status" -ne 0 ]] || automation_die "RED capture failed: focused test passed before implementation"
45
+ rg -F "$expected" "$red_log" >/dev/null || automation_die "RED output does not contain the expected failure text"
46
+
47
+ jq -n \
48
+ --arg taskId "$task_id" \
49
+ --arg startedAt "$started_at" \
50
+ --arg finishedAt "$(automation_now)" \
51
+ --arg expectedFailure "$expected" \
52
+ --arg testFilter "$filter" \
53
+ --argjson exitCode "$red_status" \
54
+ '{taskId: $taskId, startedAt: $startedAt, finishedAt: $finishedAt, command: ["./gradlew", "testDebugUnitTest", "--tests", $testFilter], expectedFailure: $expectedFailure, exitCode: $exitCode}' \
55
+ | automation_record_json "$red_meta"
56
+
57
+ automation_info "$task_id RED evidence recorded"
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
5
+ # shellcheck source=lib.sh
6
+ source "$SCRIPT_DIR/lib.sh"
7
+
8
+ task_id="${1:-}"
9
+ [[ "$#" -eq 1 ]] || { printf 'Usage: %s TASK-ID\n' "$0" >&2; exit 2; }
10
+ automation_validate_task_id "$task_id"
11
+ [[ "$(automation_read_state "$task_id")" == "CHANGES_REQUESTED" ]] || automation_die "$task_id is not CHANGES_REQUESTED"
12
+
13
+ workspace_file="$(automation_workspace_path "$task_id")"
14
+ [[ -f "$workspace_file" ]] || automation_die "workspace metadata is missing"
15
+ review_cycles="$(jq -er '.reviewCycles // 0' "$workspace_file")"
16
+ max_review_cycles="$(automation_config_value '.maxReviewCycles')"
17
+ if [[ "$review_cycles" -ge "$max_review_cycles" ]]; then
18
+ automation_transition_state "$task_id" "CHANGES_REQUESTED" "NEEDS_HUMAN" "orchestrator" "automatic review-fix cycle limit exhausted"
19
+ automation_die "review-fix cycle limit exhausted"
20
+ fi
21
+
22
+ next_review_cycle=$((review_cycles + 1))
23
+ coding_cycle="$(jq -er '.codingCycle // 0' "$workspace_file")"
24
+ next_coding_cycle=$((coding_cycle + 1))
25
+ jq \
26
+ --argjson reviewCycles "$next_review_cycle" \
27
+ --argjson codingCycle "$next_coding_cycle" \
28
+ --arg updatedAt "$(automation_now)" \
29
+ '.reviewCycles = $reviewCycles | .codingCycle = $codingCycle | .updatedAt = $updatedAt' \
30
+ "$workspace_file" | automation_record_json "$workspace_file"
31
+
32
+ automation_transition_state "$task_id" "CHANGES_REQUESTED" "CODING" "orchestrator" "bounded reviewer feedback cycle $next_review_cycle started"