@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.
Files changed (124) hide show
  1. package/CHANGELOG.md +92 -2
  2. package/LICENSE +21 -0
  3. package/README.md +402 -6
  4. package/THIRD_PARTY_NOTICES.md +119 -0
  5. package/dist/cli.js +360 -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 +497 -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 +78 -0
  28. package/dist/installer/adaptive-templates.d.ts.map +1 -0
  29. package/dist/installer/adaptive-templates.js +233 -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 +68 -0
  44. package/dist/installer/init.d.ts.map +1 -0
  45. package/dist/installer/init.js +398 -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 +32 -0
  52. package/dist/installer/opencode-config.d.ts.map +1 -0
  53. package/dist/installer/opencode-config.js +302 -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 +101 -0
  60. package/dist/installer/upgrade.d.ts.map +1 -0
  61. package/dist/installer/upgrade.js +1140 -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 +184 -0
  76. package/docs/SECURITY.md +219 -0
  77. package/docs/TROUBLESHOOTING.md +171 -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 +55 -9
  91. package/templates/automation/config.json +67 -0
  92. package/templates/automation/config.schema.json +194 -0
  93. package/templates/automation/task-contract.schema.json +92 -0
  94. package/templates/automation/tasks/TASK-TEMPLATE.json.example +56 -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 +815 -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 +55 -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 +716 -0
  121. package/templates/scripts/automation/transition-state.sh +21 -0
  122. package/templates/scripts/automation/validate-contract.sh +98 -0
  123. package/templates/scripts/automation/verify-integration.sh +38 -0
  124. 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"