@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,132 @@
|
|
|
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")" == "AWAITING_HUMAN" ]] || automation_die "$task_id is not AWAITING_HUMAN"
|
|
12
|
+
|
|
13
|
+
contract="$(automation_contract_path "$task_id")"
|
|
14
|
+
workspace_file="$(automation_workspace_path "$task_id")"
|
|
15
|
+
origin_file="$(automation_origin_path "$task_id")"
|
|
16
|
+
evidence_dir="$(automation_evidence_path "$task_id")"
|
|
17
|
+
baseline_file="$evidence_dir/baseline.json"
|
|
18
|
+
red_file="$evidence_dir/red.json"
|
|
19
|
+
ready_file="$evidence_dir/ready.json"
|
|
20
|
+
review_file="$evidence_dir/review.json"
|
|
21
|
+
[[ -f "$workspace_file" && -f "$origin_file" && -f "$baseline_file" && -f "$red_file" && -f "$ready_file" && -f "$review_file" ]] || \
|
|
22
|
+
automation_die "acceptance evidence is incomplete"
|
|
23
|
+
[[ "$(jq -er '.decision' "$review_file")" == "APPROVED" ]] || automation_die "latest independent review is not approved"
|
|
24
|
+
red_exit_code="$(jq -er '.exitCode' "$red_file")"
|
|
25
|
+
review_verification_exit_code="$(jq -er '.verificationExitCode' "$review_file")"
|
|
26
|
+
[[ "$red_exit_code" -ne 0 ]] || automation_die "RED evidence does not contain a failing test result"
|
|
27
|
+
[[ "$review_verification_exit_code" -eq 0 ]] || automation_die "independent review verification did not pass"
|
|
28
|
+
|
|
29
|
+
recorded_task_root="$(automation_workspace_task_root "$workspace_file")"
|
|
30
|
+
workspace_strategy="$(automation_workspace_strategy "$workspace_file")"
|
|
31
|
+
source_root="$(jq -er '.sourceRoot' "$workspace_file")"
|
|
32
|
+
original_branch="$(jq -er '.originalBranch' "$origin_file")"
|
|
33
|
+
baseline_head="$(jq -er '.baselineHead' "$workspace_file")"
|
|
34
|
+
original_head_current="$(git -C "$source_root" rev-parse "refs/heads/$original_branch")"
|
|
35
|
+
original_branch_drifted=false
|
|
36
|
+
if [[ "$original_head_current" != "$baseline_head" ]]; then
|
|
37
|
+
original_branch_drifted=true
|
|
38
|
+
fi
|
|
39
|
+
[[ "$(cd "$recorded_task_root" && pwd)" == "$AUTOMATION_ROOT" ]] || automation_die "acceptance report must run in the recorded task root"
|
|
40
|
+
if [[ "$(jq -r '.repositoryLeaseRequired // false' "$workspace_file")" == "true" ]]; then
|
|
41
|
+
automation_require_repository_lease "$task_id" "$source_root" "$workspace_strategy"
|
|
42
|
+
fi
|
|
43
|
+
current_diff_sha="$(automation_worktree_diff_sha)"
|
|
44
|
+
[[ "$current_diff_sha" == "$(jq -er '.diffSha256' "$ready_file")" ]] || automation_die "sealed diff changed after the quality gate"
|
|
45
|
+
[[ "$current_diff_sha" == "$(jq -er '.diffSha256' "$review_file")" ]] || automation_die "sealed diff changed after independent review"
|
|
46
|
+
automation_assert_planning_artifacts_sealed "$task_id" "$AUTOMATION_ROOT"
|
|
47
|
+
|
|
48
|
+
"$SCRIPT_DIR/scope-gate.sh" "$task_id" >/dev/null
|
|
49
|
+
changed_paths=()
|
|
50
|
+
while IFS= read -r path; do
|
|
51
|
+
[[ -n "$path" ]] && changed_paths+=("$path")
|
|
52
|
+
done < <(automation_product_changed_paths_at "$task_id" "$AUTOMATION_ROOT")
|
|
53
|
+
changed_paths_json="$(printf '%s\n' "${changed_paths[@]}" | jq -Rsc 'split("\n") | map(select(length > 0))')"
|
|
54
|
+
planning_artifacts_json="$(jq -c '[.planPath, .contractPath]' "$origin_file")"
|
|
55
|
+
diff_stat="$(git -C "$AUTOMATION_ROOT" diff --stat HEAD --)"
|
|
56
|
+
untracked_paths="$(git -C "$AUTOMATION_ROOT" ls-files --others --exclude-standard | LC_ALL=C sort)"
|
|
57
|
+
|
|
58
|
+
sealed_diff="$evidence_dir/sealed.diff"
|
|
59
|
+
{
|
|
60
|
+
git -C "$AUTOMATION_ROOT" diff --binary HEAD --
|
|
61
|
+
while IFS= read -r path; do
|
|
62
|
+
[[ -n "$path" ]] || continue
|
|
63
|
+
git -C "$AUTOMATION_ROOT" diff --binary --no-index -- /dev/null "$AUTOMATION_ROOT/$path" || [[ "$?" -eq 1 ]]
|
|
64
|
+
done <<< "$untracked_paths"
|
|
65
|
+
} > "$sealed_diff"
|
|
66
|
+
|
|
67
|
+
report_file="$evidence_dir/acceptance-report.json"
|
|
68
|
+
jq -n \
|
|
69
|
+
--arg taskId "$task_id" \
|
|
70
|
+
--arg title "$(jq -er '.title' "$contract")" \
|
|
71
|
+
--arg generatedAt "$(automation_now)" \
|
|
72
|
+
--arg originalBranch "$original_branch" \
|
|
73
|
+
--arg originalHeadBeforeContract "$(jq -er '.originalHeadBeforeContract' "$origin_file")" \
|
|
74
|
+
--arg baselineHead "$baseline_head" \
|
|
75
|
+
--arg originalHeadCurrent "$original_head_current" \
|
|
76
|
+
--arg taskBranch "$(jq -er '.taskBranch' "$workspace_file")" \
|
|
77
|
+
--arg workspaceStrategy "$workspace_strategy" \
|
|
78
|
+
--arg taskRoot "$AUTOMATION_ROOT" \
|
|
79
|
+
--arg sealedDiffSha256 "$current_diff_sha" \
|
|
80
|
+
--arg diffStat "$diff_stat" \
|
|
81
|
+
--arg sealedDiffPath "$sealed_diff" \
|
|
82
|
+
--arg reviewSummary "$(jq -er '.summary' "$review_file")" \
|
|
83
|
+
--arg testPolicy "$(jq -er '.testPolicy' "$contract")" \
|
|
84
|
+
--arg testPolicyReason "$(jq -r '.testPolicyReason // "Not specified"' "$contract")" \
|
|
85
|
+
--argjson maxChangedFiles "$(jq -er '.maxChangedFiles' "$contract")" \
|
|
86
|
+
--argjson deviceTestsRequired "$(jq -r '.deviceTestsRequired' "$contract")" \
|
|
87
|
+
--argjson redExitCode "$red_exit_code" \
|
|
88
|
+
--argjson gateAttempts "$(jq -er '.gateAttempts' "$ready_file")" \
|
|
89
|
+
--argjson codingCycle "$(jq -er '.codingCycle' "$ready_file")" \
|
|
90
|
+
--argjson reviewVerificationExitCode "$review_verification_exit_code" \
|
|
91
|
+
--argjson changedPaths "$changed_paths_json" \
|
|
92
|
+
--argjson planningArtifacts "$planning_artifacts_json" \
|
|
93
|
+
--argjson originalBranchDrifted "$original_branch_drifted" \
|
|
94
|
+
--argjson allowedPaths "$(jq -c '.allowedPaths' "$contract")" \
|
|
95
|
+
--argjson acceptanceCriteria "$(jq -c '.acceptanceCriteria' "$contract")" \
|
|
96
|
+
--argjson nonGoals "$(jq -c '.nonGoals' "$contract")" \
|
|
97
|
+
--argjson targetTests "$(jq -c '.targetTests' "$contract")" \
|
|
98
|
+
'{taskId: $taskId, title: $title, state: "AWAITING_HUMAN",
|
|
99
|
+
generatedAt: $generatedAt, originalBranch: $originalBranch,
|
|
100
|
+
originalHeadBeforeContract: $originalHeadBeforeContract,
|
|
101
|
+
baselineHead: $baselineHead, originalHeadCurrent: $originalHeadCurrent,
|
|
102
|
+
originalBranchDrifted: $originalBranchDrifted, taskBranch: $taskBranch,
|
|
103
|
+
taskBranchCleanupPolicy: "deleteAfterSuccessfulIntegration",
|
|
104
|
+
workspaceStrategy: $workspaceStrategy, taskRoot: $taskRoot,
|
|
105
|
+
sealedDiffSha256: $sealedDiffSha256,
|
|
106
|
+
changedPaths: $changedPaths, maxChangedFiles: $maxChangedFiles,
|
|
107
|
+
planningArtifacts: $planningArtifacts,
|
|
108
|
+
planningArtifactsCommitPolicy: "withProductChanges",
|
|
109
|
+
allowedPaths: $allowedPaths, diffStat: $diffStat,
|
|
110
|
+
sealedDiffPath: $sealedDiffPath, acceptanceCriteria: $acceptanceCriteria,
|
|
111
|
+
nonGoals: $nonGoals, targetTests: $targetTests,
|
|
112
|
+
testPolicy: $testPolicy, testPolicyReason: $testPolicyReason,
|
|
113
|
+
deviceTestsRequired: $deviceTestsRequired,
|
|
114
|
+
evidence: {
|
|
115
|
+
baselineRecorded: true,
|
|
116
|
+
redRecorded: true,
|
|
117
|
+
redExitCode: $redExitCode,
|
|
118
|
+
qualityGate: "PASSED",
|
|
119
|
+
gateAttempts: $gateAttempts,
|
|
120
|
+
codingCycle: $codingCycle,
|
|
121
|
+
reviewerDecision: "APPROVED",
|
|
122
|
+
reviewerVerificationExitCode: $reviewVerificationExitCode
|
|
123
|
+
},
|
|
124
|
+
bindingChecks: {
|
|
125
|
+
state: "VERIFIED",
|
|
126
|
+
sealedDiffMatchesReady: true,
|
|
127
|
+
sealedDiffMatchesReview: true
|
|
128
|
+
},
|
|
129
|
+
reviewSummary: $reviewSummary, pushed: false}' \
|
|
130
|
+
| automation_record_json "$report_file"
|
|
131
|
+
|
|
132
|
+
jq . "$report_file"
|
|
@@ -0,0 +1,175 @@
|
|
|
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 "CONTRACT-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 contract "$approval"
|
|
18
|
+
"$SCRIPT_DIR/validate-contract.sh" "$task_id" >/dev/null
|
|
19
|
+
[[ "$(automation_read_state "$task_id")" == "CONTRACT_REVIEW" ]] || automation_die "$task_id is not awaiting contract review"
|
|
20
|
+
|
|
21
|
+
origin_file="$(automation_origin_path "$task_id")"
|
|
22
|
+
[[ -f "$origin_file" ]] || automation_die "origin evidence is missing"
|
|
23
|
+
source_root="$(jq -er '.sourceRoot' "$origin_file")"
|
|
24
|
+
original_branch="$(jq -er '.originalBranch' "$origin_file")"
|
|
25
|
+
original_head="$(jq -er '.originalHeadBeforeContract' "$origin_file")"
|
|
26
|
+
contract_rel="$(jq -er '.contractPath' "$origin_file")"
|
|
27
|
+
plan_rel="$(jq -er '.planPath' "$origin_file")"
|
|
28
|
+
contract="$source_root/$contract_rel"
|
|
29
|
+
plan="$source_root/$plan_rel"
|
|
30
|
+
|
|
31
|
+
[[ "$source_root" == "$AUTOMATION_ROOT" ]] || automation_die "approval must run from the recorded source worktree"
|
|
32
|
+
[[ "$(automation_current_branch "$source_root")" == "$original_branch" ]] || automation_die "original branch changed after contract review began"
|
|
33
|
+
[[ "$(git -C "$source_root" rev-parse HEAD)" == "$original_head" ]] || automation_die "original HEAD changed after contract review began"
|
|
34
|
+
[[ "$(automation_file_sha256 "$contract")" == "$(jq -er '.contractSha256' "$origin_file")" ]] || automation_die "contract changed after proposal approval"
|
|
35
|
+
[[ "$(automation_file_sha256 "$plan")" == "$(jq -er '.planSha256' "$origin_file")" ]] || automation_die "plan changed after proposal approval"
|
|
36
|
+
|
|
37
|
+
changed_paths=()
|
|
38
|
+
while IFS= read -r path; do
|
|
39
|
+
[[ -n "$path" ]] && changed_paths+=("$path")
|
|
40
|
+
done < <(automation_changed_paths_at "$source_root")
|
|
41
|
+
if [[ "${#changed_paths[@]}" -ne 2 ]] || \
|
|
42
|
+
[[ " ${changed_paths[*]} " != *" $contract_rel "* ]] || \
|
|
43
|
+
[[ " ${changed_paths[*]} " != *" $plan_rel "* ]]; then
|
|
44
|
+
automation_die "contract approval requires exactly the sealed plan and contract changes"
|
|
45
|
+
fi
|
|
46
|
+
for planning_path in "$plan_rel" "$contract_rel"; do
|
|
47
|
+
if git -C "$source_root" ls-files --error-unmatch -- "$planning_path" >/dev/null 2>&1; then
|
|
48
|
+
automation_die "planning artifact must remain a new untracked file until the combined task commit: $planning_path"
|
|
49
|
+
fi
|
|
50
|
+
done
|
|
51
|
+
|
|
52
|
+
automation_require_command git
|
|
53
|
+
automation_require_command jq
|
|
54
|
+
automation_require_command shasum
|
|
55
|
+
if [[ "${AUTOMATION_TEST_MODE:-0}" != "1" ]]; then
|
|
56
|
+
automation_require_command opencode
|
|
57
|
+
[[ -n "${ANDROID_HOME:-}" ]] || automation_die "ANDROID_HOME is not set; export it before approving the contract"
|
|
58
|
+
[[ -d "$ANDROID_HOME" ]] || automation_die "ANDROID_HOME does not exist: $ANDROID_HOME"
|
|
59
|
+
fi
|
|
60
|
+
git -C "$source_root" config user.name >/dev/null || automation_die "Git user.name is not configured"
|
|
61
|
+
git -C "$source_root" config user.email >/dev/null || automation_die "Git user.email is not configured"
|
|
62
|
+
|
|
63
|
+
workspace_strategy="$(automation_config_value '.workspaceStrategy')"
|
|
64
|
+
worktree_base=""
|
|
65
|
+
task_slug="$(tr '[:upper:]' '[:lower:]' <<< "$task_id")"
|
|
66
|
+
task_branch="$(automation_task_branch "$task_id")"
|
|
67
|
+
task_root="$source_root"
|
|
68
|
+
if [[ "$workspace_strategy" == "isolatedWorktree" ]]; then
|
|
69
|
+
worktree_base="$(automation_worktree_base)"
|
|
70
|
+
task_root="$worktree_base/$task_slug"
|
|
71
|
+
[[ ! -e "$task_root" ]] || automation_die "task worktree path already exists: $task_root"
|
|
72
|
+
fi
|
|
73
|
+
if git -C "$source_root" show-ref --verify --quiet "refs/heads/$task_branch"; then
|
|
74
|
+
automation_die "task branch already exists: $task_branch"
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
automation_acquire_run_lock "$task_id"
|
|
78
|
+
preparation_complete=0
|
|
79
|
+
lease_acquired=0
|
|
80
|
+
workspace_materialized=0
|
|
81
|
+
approval_exit() {
|
|
82
|
+
local exit_code=$?
|
|
83
|
+
trap - EXIT
|
|
84
|
+
if [[ "$preparation_complete" != "1" ]] && [[ -f "$(automation_state_path "$task_id")" ]] && \
|
|
85
|
+
[[ "$(automation_read_state "$task_id" 2>/dev/null || true)" == "PREPARING" ]]; then
|
|
86
|
+
automation_transition_state "$task_id" "PREPARING" "BLOCKED" "orchestrator" "task workspace preparation failed" || true
|
|
87
|
+
fi
|
|
88
|
+
if [[ "$lease_acquired" == "1" && "$workspace_materialized" != "1" ]]; then
|
|
89
|
+
automation_release_repository_lease "$task_id" || true
|
|
90
|
+
fi
|
|
91
|
+
automation_release_run_lock
|
|
92
|
+
exit "$exit_code"
|
|
93
|
+
}
|
|
94
|
+
trap approval_exit EXIT
|
|
95
|
+
|
|
96
|
+
automation_acquire_repository_lease "$task_id" "$source_root" "$workspace_strategy"
|
|
97
|
+
lease_acquired=1
|
|
98
|
+
|
|
99
|
+
jq -nc \
|
|
100
|
+
--arg taskId "$task_id" \
|
|
101
|
+
--arg kind "contract" \
|
|
102
|
+
--arg at "$(automation_now)" \
|
|
103
|
+
--arg originalBranch "$original_branch" \
|
|
104
|
+
--arg originalHead "$original_head" \
|
|
105
|
+
--arg contractSha256 "$(automation_file_sha256 "$contract")" \
|
|
106
|
+
'{taskId: $taskId, kind: $kind, at: $at, originalBranch: $originalBranch,
|
|
107
|
+
originalHead: $originalHead, contractSha256: $contractSha256,
|
|
108
|
+
planningArtifactsCommitPolicy: "withProductChanges"}' \
|
|
109
|
+
| automation_append_json "$(automation_approvals_path "$task_id")"
|
|
110
|
+
|
|
111
|
+
baseline_head="$original_head"
|
|
112
|
+
|
|
113
|
+
jq \
|
|
114
|
+
--arg approvedAt "$(automation_now)" \
|
|
115
|
+
--arg baselineHead "$baseline_head" \
|
|
116
|
+
'.approvedAt = $approvedAt |
|
|
117
|
+
.baselineHead = $baselineHead |
|
|
118
|
+
.planningArtifactsCommitPolicy = "withProductChanges" |
|
|
119
|
+
.contractCommit = null' \
|
|
120
|
+
"$origin_file" | automation_record_json "$origin_file"
|
|
121
|
+
|
|
122
|
+
automation_transition_state "$task_id" "CONTRACT_REVIEW" "APPROVED_CONTRACT" "human" "sealed contract explicitly approved"
|
|
123
|
+
automation_transition_state "$task_id" "APPROVED_CONTRACT" "PREPARING" "orchestrator" "preparing transactional task workspace"
|
|
124
|
+
|
|
125
|
+
if [[ "$workspace_strategy" == "inPlaceExclusive" ]]; then
|
|
126
|
+
git -C "$source_root" switch -c "$task_branch" "$baseline_head"
|
|
127
|
+
else
|
|
128
|
+
mkdir -p "$worktree_base"
|
|
129
|
+
git -C "$source_root" worktree add "$task_root" -b "$task_branch" "$baseline_head"
|
|
130
|
+
mkdir -p "$(dirname "$task_root/$plan_rel")" "$(dirname "$task_root/$contract_rel")"
|
|
131
|
+
cp "$plan" "$task_root/$plan_rel"
|
|
132
|
+
cp "$contract" "$task_root/$contract_rel"
|
|
133
|
+
[[ "$(automation_file_sha256 "$task_root/$plan_rel")" == "$(jq -er '.planSha256' "$origin_file")" ]] || \
|
|
134
|
+
automation_die "isolated task plan transfer changed its sealed content"
|
|
135
|
+
[[ "$(automation_file_sha256 "$task_root/$contract_rel")" == "$(jq -er '.contractSha256' "$origin_file")" ]] || \
|
|
136
|
+
automation_die "isolated task contract transfer changed its sealed content"
|
|
137
|
+
rm -f "$plan" "$contract"
|
|
138
|
+
automation_worktree_is_clean "$source_root" || automation_die "source worktree is not clean after moving planning artifacts to the isolated task root"
|
|
139
|
+
fi
|
|
140
|
+
workspace_materialized=1
|
|
141
|
+
automation_assert_planning_artifacts_sealed "$task_id" "$task_root"
|
|
142
|
+
materialized_paths=()
|
|
143
|
+
while IFS= read -r path; do
|
|
144
|
+
[[ -n "$path" ]] && materialized_paths+=("$path")
|
|
145
|
+
done < <(automation_changed_paths_at "$task_root")
|
|
146
|
+
if [[ "${#materialized_paths[@]}" -ne 2 ]] || \
|
|
147
|
+
[[ " ${materialized_paths[*]} " != *" $contract_rel "* ]] || \
|
|
148
|
+
[[ " ${materialized_paths[*]} " != *" $plan_rel "* ]]; then
|
|
149
|
+
automation_die "prepared task root must contain only the sealed, uncommitted plan and contract"
|
|
150
|
+
fi
|
|
151
|
+
workspace_file="$(automation_workspace_path "$task_id")"
|
|
152
|
+
jq -n \
|
|
153
|
+
--arg taskId "$task_id" \
|
|
154
|
+
--arg sourceRoot "$source_root" \
|
|
155
|
+
--arg originalBranch "$original_branch" \
|
|
156
|
+
--arg baselineHead "$baseline_head" \
|
|
157
|
+
--arg workspaceStrategy "$workspace_strategy" \
|
|
158
|
+
--arg taskRoot "$task_root" \
|
|
159
|
+
--arg taskBranch "$task_branch" \
|
|
160
|
+
--arg worktreeBase "$worktree_base" \
|
|
161
|
+
--arg createdAt "$(automation_now)" \
|
|
162
|
+
'{taskId: $taskId, sourceRoot: $sourceRoot, originalBranch: $originalBranch,
|
|
163
|
+
baselineHead: $baselineHead, workspaceStrategy: $workspaceStrategy,
|
|
164
|
+
taskRoot: $taskRoot, repositoryLeaseRequired: true,
|
|
165
|
+
planningArtifactsCommitPolicy: "withProductChanges",
|
|
166
|
+
taskBranch: $taskBranch, worktreeBase: $worktreeBase,
|
|
167
|
+
codingCycle: 0, reviewCycles: 0, createdAt: $createdAt}' \
|
|
168
|
+
| automation_record_json "$workspace_file"
|
|
169
|
+
|
|
170
|
+
automation_transition_state "$task_id" "PREPARING" "PENDING" "orchestrator" "$workspace_strategy task workspace prepared and queued"
|
|
171
|
+
preparation_complete=1
|
|
172
|
+
automation_release_run_lock
|
|
173
|
+
trap - EXIT
|
|
174
|
+
|
|
175
|
+
"$SCRIPT_DIR/orchestrate-task.sh" "$task_id"
|
|
@@ -0,0 +1,18 @@
|
|
|
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")" == "READY_FOR_REVIEW" ]] || automation_die "$task_id is not READY_FOR_REVIEW"
|
|
12
|
+
|
|
13
|
+
ready_file="$(automation_evidence_path "$task_id")/ready.json"
|
|
14
|
+
[[ -f "$ready_file" ]] || automation_die "ready evidence is missing"
|
|
15
|
+
current_diff_sha="$(automation_worktree_diff_sha)"
|
|
16
|
+
[[ "$current_diff_sha" == "$(jq -er '.diffSha256' "$ready_file")" ]] || automation_die "diff changed after quality gate"
|
|
17
|
+
|
|
18
|
+
automation_transition_state "$task_id" "READY_FOR_REVIEW" "REVIEWING" "orchestrator" "sealed diff handed to a fresh read-only reviewer"
|
|
@@ -0,0 +1,26 @@
|
|
|
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
|
+
shift $(( $# >= 1 ? 1 : $# ))
|
|
10
|
+
reason="$*"
|
|
11
|
+
|
|
12
|
+
if [[ -z "$task_id" || ${#reason} -lt 12 ]]; then
|
|
13
|
+
printf 'Usage: %s TASK-ID REASON-OF-AT-LEAST-12-CHARACTERS\n' "$0" >&2
|
|
14
|
+
exit 2
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
automation_validate_task_id "$task_id"
|
|
18
|
+
current="$(automation_read_state "$task_id")"
|
|
19
|
+
case "$current" in
|
|
20
|
+
PENDING|CODING)
|
|
21
|
+
automation_transition_state "$task_id" "$current" "BLOCKED" "coder" "$reason"
|
|
22
|
+
;;
|
|
23
|
+
*)
|
|
24
|
+
automation_die "cannot block $task_id from state $current"
|
|
25
|
+
;;
|
|
26
|
+
esac
|
|
@@ -0,0 +1,74 @@
|
|
|
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_require_layout
|
|
12
|
+
|
|
13
|
+
[[ "$(automation_read_state "$task_id")" == "PENDING" ]] || automation_die "$task_id is not PENDING"
|
|
14
|
+
evidence_dir="$(automation_evidence_path "$task_id")"
|
|
15
|
+
mkdir -p "$evidence_dir"
|
|
16
|
+
preflight_log="$evidence_dir/preflight.log"
|
|
17
|
+
set +e
|
|
18
|
+
"$SCRIPT_DIR/preflight.sh" "$task_id" 2>&1 | tee "$preflight_log"
|
|
19
|
+
preflight_status=${PIPESTATUS[0]}
|
|
20
|
+
set -e
|
|
21
|
+
if [[ "$preflight_status" -ne 0 ]]; then
|
|
22
|
+
automation_transition_state "$task_id" "PENDING" "BLOCKED" "preflight" "preflight failed with exit $preflight_status"
|
|
23
|
+
automation_die "preflight failed; task moved to BLOCKED"
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
automation_transition_state "$task_id" "PENDING" "CODING" "coder-launcher" "preflight passed; capturing baseline"
|
|
27
|
+
|
|
28
|
+
baseline_log="$evidence_dir/baseline.log"
|
|
29
|
+
baseline_meta="$evidence_dir/baseline.json"
|
|
30
|
+
protected_hashes="$evidence_dir/protected.sha256"
|
|
31
|
+
started_at="$(automation_now)"
|
|
32
|
+
head_commit="$(git -C "$AUTOMATION_ROOT" rev-parse HEAD)"
|
|
33
|
+
baseline_command="$(automation_gradle_group_command_json "fullUnitTestTasks")"
|
|
34
|
+
|
|
35
|
+
set +e
|
|
36
|
+
automation_run_gradle_group "fullUnitTestTasks" "$AUTOMATION_ROOT" 2>&1 | tee "$baseline_log"
|
|
37
|
+
baseline_status=${PIPESTATUS[0]}
|
|
38
|
+
set -e
|
|
39
|
+
|
|
40
|
+
if [[ "$baseline_status" -ne 0 ]]; then
|
|
41
|
+
jq -n \
|
|
42
|
+
--arg taskId "$task_id" \
|
|
43
|
+
--arg startedAt "$started_at" \
|
|
44
|
+
--arg finishedAt "$(automation_now)" \
|
|
45
|
+
--arg head "$head_commit" \
|
|
46
|
+
--argjson command "$baseline_command" \
|
|
47
|
+
--argjson exitCode "$baseline_status" \
|
|
48
|
+
'{taskId: $taskId, startedAt: $startedAt, finishedAt: $finishedAt, head: $head, command: $command, exitCode: $exitCode}' \
|
|
49
|
+
| automation_record_json "$baseline_meta"
|
|
50
|
+
automation_transition_state "$task_id" "CODING" "BLOCKED" "preflight" "baseline unit tests failed"
|
|
51
|
+
automation_die "baseline unit tests failed"
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
: > "$protected_hashes"
|
|
55
|
+
while IFS= read -r tracked; do
|
|
56
|
+
while IFS= read -r protected; do
|
|
57
|
+
if automation_path_matches "$tracked" "$protected"; then
|
|
58
|
+
shasum -a 256 "$AUTOMATION_ROOT/$tracked" >> "$protected_hashes"
|
|
59
|
+
break
|
|
60
|
+
fi
|
|
61
|
+
done < <(jq -r '.protectedPaths[]' "$AUTOMATION_CONFIG")
|
|
62
|
+
done < <(git -C "$AUTOMATION_ROOT" ls-files)
|
|
63
|
+
|
|
64
|
+
jq -n \
|
|
65
|
+
--arg taskId "$task_id" \
|
|
66
|
+
--arg startedAt "$started_at" \
|
|
67
|
+
--arg finishedAt "$(automation_now)" \
|
|
68
|
+
--arg head "$head_commit" \
|
|
69
|
+
--arg worktree "$AUTOMATION_ROOT" \
|
|
70
|
+
--argjson command "$baseline_command" \
|
|
71
|
+
'{taskId: $taskId, startedAt: $startedAt, finishedAt: $finishedAt, head: $head, worktree: $worktree, command: $command, exitCode: 0}' \
|
|
72
|
+
| automation_record_json "$baseline_meta"
|
|
73
|
+
|
|
74
|
+
automation_info "$task_id claimed; baseline is green"
|
|
@@ -0,0 +1,103 @@
|
|
|
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
|
+
base="${2:-}"
|
|
10
|
+
head="${3:-}"
|
|
11
|
+
if [[ "$#" -ne 3 ]]; then
|
|
12
|
+
printf 'Usage: %s TASK-ID BASE-COMMIT HEAD-COMMIT\n' "$0" >&2
|
|
13
|
+
exit 2
|
|
14
|
+
fi
|
|
15
|
+
automation_validate_task_id "$task_id"
|
|
16
|
+
git -C "$AUTOMATION_ROOT" cat-file -e "$base^{commit}" 2>/dev/null || automation_die "invalid integration base: $base"
|
|
17
|
+
git -C "$AUTOMATION_ROOT" cat-file -e "$head^{commit}" 2>/dev/null || automation_die "invalid integration head: $head"
|
|
18
|
+
"$SCRIPT_DIR/validate-contract.sh" "$task_id" >/dev/null
|
|
19
|
+
|
|
20
|
+
contract="$(automation_contract_path "$task_id")"
|
|
21
|
+
workspace_file="$(automation_workspace_path "$task_id")"
|
|
22
|
+
planning_commit_policy="$(jq -r '.planningArtifactsCommitPolicy // "legacyCommittedSeparately"' "$workspace_file")"
|
|
23
|
+
if [[ "$planning_commit_policy" == "withProductChanges" ]]; then
|
|
24
|
+
automation_assert_planning_artifacts_sealed "$task_id" "$AUTOMATION_ROOT" || exit 40
|
|
25
|
+
all_changed_file_list="$(automation_changed_paths_between "$base" "$head")"
|
|
26
|
+
origin_file="$(automation_origin_path "$task_id")"
|
|
27
|
+
contract_rel="$(jq -er '.contractPath' "$origin_file")"
|
|
28
|
+
plan_rel="$(jq -er '.planPath' "$origin_file")"
|
|
29
|
+
for planning_path in "$plan_rel" "$contract_rel"; do
|
|
30
|
+
if ! printf '%s\n' "$all_changed_file_list" | awk -v expected="$planning_path" '$0 == expected { found = 1 } END { exit !found }'; then
|
|
31
|
+
automation_die "integration candidate omitted approved planning artifact: $planning_path"
|
|
32
|
+
exit 40
|
|
33
|
+
fi
|
|
34
|
+
done
|
|
35
|
+
changed_file_list="$(automation_product_changed_paths_between "$task_id" "$base" "$head" "$AUTOMATION_ROOT")"
|
|
36
|
+
else
|
|
37
|
+
changed_file_list="$(automation_changed_paths_between "$base" "$head")"
|
|
38
|
+
fi
|
|
39
|
+
[[ -n "$changed_file_list" ]] || { automation_die "integration candidate has no product changes"; exit 40; }
|
|
40
|
+
|
|
41
|
+
max_changed="$(jq -r '.maxChangedFiles' "$contract")"
|
|
42
|
+
changed_count="$(printf '%s\n' "$changed_file_list" | awk 'NF { count++ } END { print count + 0 }')"
|
|
43
|
+
[[ "$changed_count" -le "$max_changed" ]] || { automation_die "integration changed file count $changed_count exceeds limit $max_changed"; exit 40; }
|
|
44
|
+
|
|
45
|
+
production_changed=0
|
|
46
|
+
test_changed=0
|
|
47
|
+
while IFS= read -r path; do
|
|
48
|
+
[[ -n "$path" ]] || continue
|
|
49
|
+
if automation_array_matches_path "$AUTOMATION_CONFIG" '.protectedPaths' "$path"; then
|
|
50
|
+
automation_die "protected path changed in integration candidate: $path"
|
|
51
|
+
exit 40
|
|
52
|
+
fi
|
|
53
|
+
if automation_array_matches_path "$contract" '.forbiddenPaths' "$path"; then
|
|
54
|
+
automation_die "forbidden path changed in integration candidate: $path"
|
|
55
|
+
exit 40
|
|
56
|
+
fi
|
|
57
|
+
if ! automation_array_matches_path "$contract" '.allowedPaths' "$path"; then
|
|
58
|
+
automation_die "integration path is outside allowedPaths: $path"
|
|
59
|
+
exit 40
|
|
60
|
+
fi
|
|
61
|
+
if automation_array_matches_path "$AUTOMATION_CONFIG" '.androidProject.productionPaths' "$path"; then
|
|
62
|
+
production_changed=1
|
|
63
|
+
fi
|
|
64
|
+
if automation_array_matches_path "$AUTOMATION_CONFIG" '.androidProject.testPaths' "$path"; then
|
|
65
|
+
test_changed=1
|
|
66
|
+
fi
|
|
67
|
+
done <<< "$changed_file_list"
|
|
68
|
+
|
|
69
|
+
deleted_tests="$(
|
|
70
|
+
git -C "$AUTOMATION_ROOT" diff --name-only --diff-filter=D "$base" "$head" -- \
|
|
71
|
+
| while IFS= read -r path; do
|
|
72
|
+
if automation_array_matches_path "$AUTOMATION_CONFIG" '.androidProject.testPaths' "$path"; then
|
|
73
|
+
printf '%s\n' "$path"
|
|
74
|
+
fi
|
|
75
|
+
done
|
|
76
|
+
)"
|
|
77
|
+
[[ -z "$deleted_tests" ]] || { automation_die "test deletion is forbidden: $deleted_tests"; exit 40; }
|
|
78
|
+
if [[ "$production_changed" == "1" && "$(jq -r '.testPolicy' "$contract")" == "required" && "$test_changed" != "1" ]]; then
|
|
79
|
+
automation_die "production code changed without a test change"
|
|
80
|
+
exit 40
|
|
81
|
+
fi
|
|
82
|
+
|
|
83
|
+
added_test_lines="$(
|
|
84
|
+
while IFS= read -r path; do
|
|
85
|
+
[[ -n "$path" ]] || continue
|
|
86
|
+
if automation_array_matches_path "$AUTOMATION_CONFIG" '.androidProject.testPaths' "$path"; then
|
|
87
|
+
git -C "$AUTOMATION_ROOT" diff --unified=0 "$base" "$head" -- "$path"
|
|
88
|
+
fi
|
|
89
|
+
done <<< "$changed_file_list" \
|
|
90
|
+
| awk '/^\+\+\+/ { next } /^\+/ { sub(/^\+/, ""); print }'
|
|
91
|
+
)"
|
|
92
|
+
weakening_pattern='@Ignore|@Disabled|assumeTrue\(false\)|assertTrue\(true\)|assertFalse\(false\)'
|
|
93
|
+
if printf '%s\n' "$added_test_lines" | rg -n "$weakening_pattern" >/dev/null; then
|
|
94
|
+
automation_die "potential test weakening detected in integration candidate"
|
|
95
|
+
exit 40
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
if [[ "$planning_commit_policy" == "withProductChanges" ]]; then
|
|
99
|
+
automation_info "integration scope gate passed ($changed_count product files plus the two sealed planning artifacts)"
|
|
100
|
+
else
|
|
101
|
+
automation_info "integration scope gate passed ($changed_count changed files)"
|
|
102
|
+
fi
|
|
103
|
+
printf '%s\n' "$changed_file_list"
|