@frankzhang2026/opencode-android-orchestrator 0.1.0 → 0.3.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 +92 -2
- package/LICENSE +21 -0
- package/README.md +402 -6
- package/THIRD_PARTY_NOTICES.md +119 -0
- package/dist/cli.js +360 -1
- package/dist/cli.js.map +1 -1
- package/dist/compatibility/hooks.d.ts +23 -0
- package/dist/compatibility/hooks.d.ts.map +1 -0
- package/dist/compatibility/hooks.js +44 -0
- package/dist/compatibility/hooks.js.map +1 -0
- package/dist/compatibility/versions.d.ts +19 -0
- package/dist/compatibility/versions.d.ts.map +1 -1
- package/dist/compatibility/versions.js +116 -0
- package/dist/compatibility/versions.js.map +1 -1
- package/dist/doctor/index.d.ts +36 -0
- package/dist/doctor/index.d.ts.map +1 -0
- package/dist/doctor/index.js +289 -0
- package/dist/doctor/index.js.map +1 -0
- package/dist/doctor/installation.d.ts +4 -0
- package/dist/doctor/installation.d.ts.map +1 -0
- package/dist/doctor/installation.js +497 -0
- package/dist/doctor/installation.js.map +1 -0
- package/dist/index.d.ts +13 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -2
- package/dist/index.js.map +1 -1
- package/dist/installer/adaptive-templates.d.ts +78 -0
- package/dist/installer/adaptive-templates.d.ts.map +1 -0
- package/dist/installer/adaptive-templates.js +233 -0
- package/dist/installer/adaptive-templates.js.map +1 -0
- package/dist/installer/agents-config.d.ts +19 -0
- package/dist/installer/agents-config.d.ts.map +1 -0
- package/dist/installer/agents-config.js +147 -0
- package/dist/installer/agents-config.js.map +1 -0
- package/dist/installer/android-project.d.ts +36 -0
- package/dist/installer/android-project.d.ts.map +1 -0
- package/dist/installer/android-project.js +418 -0
- package/dist/installer/android-project.js.map +1 -0
- package/dist/installer/index.d.ts +8 -0
- package/dist/installer/index.d.ts.map +1 -1
- package/dist/installer/index.js +8 -1
- package/dist/installer/index.js.map +1 -1
- package/dist/installer/init.d.ts +68 -0
- package/dist/installer/init.d.ts.map +1 -0
- package/dist/installer/init.js +398 -0
- package/dist/installer/init.js.map +1 -0
- package/dist/installer/install-manifest.d.ts +132 -0
- package/dist/installer/install-manifest.d.ts.map +1 -0
- package/dist/installer/install-manifest.js +1047 -0
- package/dist/installer/install-manifest.js.map +1 -0
- package/dist/installer/opencode-config.d.ts +32 -0
- package/dist/installer/opencode-config.d.ts.map +1 -0
- package/dist/installer/opencode-config.js +302 -0
- package/dist/installer/opencode-config.js.map +1 -0
- package/dist/installer/uninstall.d.ts +70 -0
- package/dist/installer/uninstall.d.ts.map +1 -0
- package/dist/installer/uninstall.js +770 -0
- package/dist/installer/uninstall.js.map +1 -0
- package/dist/installer/upgrade.d.ts +101 -0
- package/dist/installer/upgrade.d.ts.map +1 -0
- package/dist/installer/upgrade.js +1140 -0
- package/dist/installer/upgrade.js.map +1 -0
- package/dist/opencode-plugin.d.ts +2 -0
- package/dist/opencode-plugin.d.ts.map +1 -0
- package/dist/opencode-plugin.js +4 -0
- package/dist/opencode-plugin.js.map +1 -0
- package/dist/plugin/index.d.ts +7 -2
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +23 -6
- package/dist/plugin/index.js.map +1 -1
- package/dist/tools/index.d.ts +35 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +197 -1
- package/dist/tools/index.js.map +1 -1
- package/docs/MIGRATION.md +184 -0
- package/docs/SECURITY.md +219 -0
- package/docs/TROUBLESHOOTING.md +171 -0
- package/package.json +23 -5
- package/templates/.opencode/agents/scheduled-coder.md +115 -0
- package/templates/.opencode/agents/scheduled-planner.md +166 -0
- package/templates/.opencode/agents/scheduled-reviewer.md +88 -0
- package/templates/.opencode/commands/abort-task.md +20 -0
- package/templates/.opencode/commands/acceptance.md +20 -0
- package/templates/.opencode/commands/change.md +23 -0
- package/templates/.opencode/commands/resume-review.md +19 -0
- package/templates/.opencode/skills/scheduled-quality-coder/SKILL.md +93 -0
- package/templates/.opencode/skills/scheduled-quality-orchestrator/SKILL.md +216 -0
- package/templates/.opencode/skills/scheduled-quality-reviewer/SKILL.md +68 -0
- package/templates/AGENTS.md.fragment +19 -0
- package/templates/README.md +55 -9
- package/templates/automation/config.json +67 -0
- package/templates/automation/config.schema.json +194 -0
- package/templates/automation/task-contract.schema.json +92 -0
- package/templates/automation/tasks/TASK-TEMPLATE.json.example +56 -0
- package/templates/docs/plans/README.md +26 -0
- package/templates/installation-manifest.schema.json +201 -0
- package/templates/scripts/automation/abort-task.sh +171 -0
- package/templates/scripts/automation/accept-and-integrate.sh +263 -0
- package/templates/scripts/automation/acceptance-report.sh +132 -0
- package/templates/scripts/automation/approve-and-run.sh +175 -0
- package/templates/scripts/automation/begin-review.sh +18 -0
- package/templates/scripts/automation/block-task.sh +26 -0
- package/templates/scripts/automation/claim-task.sh +74 -0
- package/templates/scripts/automation/integration-scope-gate.sh +103 -0
- package/templates/scripts/automation/lib.sh +815 -0
- package/templates/scripts/automation/orchestrate-task.sh +119 -0
- package/templates/scripts/automation/preflight.sh +206 -0
- package/templates/scripts/automation/prepare-contract-review.sh +101 -0
- package/templates/scripts/automation/quality-gate.sh +72 -0
- package/templates/scripts/automation/queue-task.sh +35 -0
- package/templates/scripts/automation/record-red.sh +55 -0
- package/templates/scripts/automation/resume-review-fix.sh +32 -0
- package/templates/scripts/automation/resume-review.sh +120 -0
- package/templates/scripts/automation/scope-gate.sh +110 -0
- package/templates/scripts/automation/select-task.sh +34 -0
- package/templates/scripts/automation/shadow-run.sh +24 -0
- package/templates/scripts/automation/show-acceptance-review.sh +111 -0
- package/templates/scripts/automation/status.sh +125 -0
- package/templates/scripts/automation/submit-review.sh +73 -0
- package/templates/scripts/automation/tests/run-tests.sh +716 -0
- package/templates/scripts/automation/transition-state.sh +21 -0
- package/templates/scripts/automation/validate-contract.sh +98 -0
- package/templates/scripts/automation/verify-integration.sh +38 -0
- package/templates/scripts/automation/verify-task.sh +39 -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 ! automation_resolve_opencode_config "$discovery_dir/config.json" "$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,55 @@
|
|
|
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
|
+
automation_validate_test_filter "$filter"
|
|
20
|
+
[[ ${#expected} -ge 3 ]] || automation_die "expected failure text is too short"
|
|
21
|
+
[[ "$(automation_read_state "$task_id")" == "CODING" ]] || automation_die "$task_id is not CODING"
|
|
22
|
+
"$SCRIPT_DIR/validate-contract.sh" "$task_id"
|
|
23
|
+
|
|
24
|
+
contract="$(automation_contract_path "$task_id")"
|
|
25
|
+
target_count="$(jq -er --arg filter "$filter" '[.targetTests[] | select(.filter == $filter)] | length' "$contract")"
|
|
26
|
+
[[ "$target_count" -eq 1 ]] || automation_die "test filter must identify exactly one declared contract target: $filter"
|
|
27
|
+
gradle_task="$(jq -er --arg filter "$filter" '.targetTests[] | select(.filter == $filter) | .gradleTask' "$contract")"
|
|
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
|
+
automation_run_focused_test "$gradle_task" "$filter" "$AUTOMATION_ROOT" 2>&1 | tee "$red_log"
|
|
38
|
+
red_status=${PIPESTATUS[0]}
|
|
39
|
+
set -e
|
|
40
|
+
|
|
41
|
+
[[ "$red_status" -ne 0 ]] || automation_die "RED capture failed: focused test passed before implementation"
|
|
42
|
+
rg -F "$expected" "$red_log" >/dev/null || automation_die "RED output does not contain the expected failure text"
|
|
43
|
+
|
|
44
|
+
jq -n \
|
|
45
|
+
--arg taskId "$task_id" \
|
|
46
|
+
--arg startedAt "$started_at" \
|
|
47
|
+
--arg finishedAt "$(automation_now)" \
|
|
48
|
+
--arg expectedFailure "$expected" \
|
|
49
|
+
--arg gradleTask "$gradle_task" \
|
|
50
|
+
--arg testFilter "$filter" \
|
|
51
|
+
--argjson exitCode "$red_status" \
|
|
52
|
+
'{taskId: $taskId, startedAt: $startedAt, finishedAt: $finishedAt, command: ["./gradlew", $gradleTask, "--tests", $testFilter], expectedFailure: $expectedFailure, exitCode: $exitCode}' \
|
|
53
|
+
| automation_record_json "$red_meta"
|
|
54
|
+
|
|
55
|
+
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"
|