@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,120 @@
|
|
|
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
|
+
evidence_dir="$(automation_evidence_path "$task_id")"
|
|
15
|
+
state_file="$(automation_state_path "$task_id")"
|
|
16
|
+
ready_file="$evidence_dir/ready.json"
|
|
17
|
+
transitions_file="$evidence_dir/transitions.jsonl"
|
|
18
|
+
resumptions_file="$evidence_dir/review-resumptions.jsonl"
|
|
19
|
+
|
|
20
|
+
[[ -f "$workspace_file" ]] || automation_die "workspace metadata is missing for $task_id"
|
|
21
|
+
[[ -f "$state_file" ]] || automation_die "state is missing for $task_id"
|
|
22
|
+
[[ -f "$evidence_dir/baseline.json" ]] || automation_die "baseline evidence is missing"
|
|
23
|
+
[[ -f "$evidence_dir/red.json" ]] || automation_die "RED evidence is missing"
|
|
24
|
+
[[ -f "$ready_file" ]] || automation_die "ready evidence is missing"
|
|
25
|
+
[[ -f "$transitions_file" ]] || automation_die "transition evidence is missing"
|
|
26
|
+
|
|
27
|
+
automation_acquire_run_lock "$task_id"
|
|
28
|
+
trap 'automation_release_run_lock' EXIT
|
|
29
|
+
|
|
30
|
+
[[ "$(automation_read_state "$task_id")" == "BLOCKED" ]] || automation_die "$task_id is not BLOCKED"
|
|
31
|
+
|
|
32
|
+
task_root="$(automation_workspace_task_root "$workspace_file")"
|
|
33
|
+
workspace_strategy="$(automation_workspace_strategy "$workspace_file")"
|
|
34
|
+
source_root="$(jq -er '.sourceRoot' "$workspace_file")"
|
|
35
|
+
source_root="$(jq -er '.sourceRoot' "$workspace_file")"
|
|
36
|
+
task_branch="$(jq -er '.taskBranch' "$workspace_file")"
|
|
37
|
+
baseline_head="$(jq -er '.baselineHead' "$workspace_file")"
|
|
38
|
+
coding_cycle="$(jq -er '.codingCycle // 0' "$workspace_file")"
|
|
39
|
+
|
|
40
|
+
[[ -d "$task_root" ]] || automation_die "task root is missing: $task_root"
|
|
41
|
+
if [[ "$(jq -r '.repositoryLeaseRequired // false' "$workspace_file")" == "true" ]]; then
|
|
42
|
+
automation_require_repository_lease "$task_id" "$source_root" "$workspace_strategy"
|
|
43
|
+
fi
|
|
44
|
+
task_root="$(cd "$task_root" && pwd -P)"
|
|
45
|
+
source_root="$(cd "$source_root" && pwd -P)"
|
|
46
|
+
[[ "$source_root" == "$AUTOMATION_ROOT" ]] || automation_die "workspace sourceRoot does not match this repository"
|
|
47
|
+
[[ "$(automation_current_branch "$task_root")" == "$task_branch" ]] || automation_die "task branch changed"
|
|
48
|
+
[[ "$(git -C "$task_root" rev-parse HEAD)" == "$baseline_head" ]] || automation_die "task HEAD changed after sealing"
|
|
49
|
+
[[ "$(jq -er '.head' "$evidence_dir/baseline.json")" == "$baseline_head" ]] || automation_die "baseline evidence does not match workspace metadata"
|
|
50
|
+
[[ "$(jq -er '.head' "$ready_file")" == "$baseline_head" ]] || automation_die "ready evidence does not match the baseline"
|
|
51
|
+
[[ "$(jq -er '.codingCycle // 0' "$ready_file")" == "$coding_cycle" ]] || automation_die "ready evidence belongs to a different coding cycle"
|
|
52
|
+
|
|
53
|
+
if ! jq -es -e '
|
|
54
|
+
([to_entries[] |
|
|
55
|
+
select(.value.from == "REVIEWING" and
|
|
56
|
+
.value.to == "BLOCKED" and
|
|
57
|
+
.value.actor == "orchestrator" and
|
|
58
|
+
(.value.note | startswith("reviewer exited without submitting a decision")))] | last) as $failure
|
|
59
|
+
| ($failure != null) and
|
|
60
|
+
all(.[$failure.key + 1:][];
|
|
61
|
+
(.from == "BLOCKED" and .to == "PENDING" and .actor == "human") or
|
|
62
|
+
(.from == "PENDING" and .to == "BLOCKED" and
|
|
63
|
+
(.actor == "coder" or .actor == "preflight" or .actor == "orchestrator")))
|
|
64
|
+
' "$transitions_file" >/dev/null; then
|
|
65
|
+
automation_die "BLOCKED state is not a recoverable reviewer interruption"
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
ready_diff_sha="$(jq -er '.diffSha256' "$ready_file")"
|
|
69
|
+
current_diff_sha="$(automation_worktree_diff_sha "$task_root")"
|
|
70
|
+
[[ "$current_diff_sha" == "$ready_diff_sha" ]] || automation_die "sealed diff changed; reviewer-only recovery is forbidden"
|
|
71
|
+
|
|
72
|
+
if [[ -f "$evidence_dir/reviews.jsonl" ]] &&
|
|
73
|
+
jq -es -e --arg sha "$ready_diff_sha" 'any(.[]; .diffSha256 == $sha)' "$evidence_dir/reviews.jsonl" >/dev/null; then
|
|
74
|
+
automation_die "a reviewer decision already exists for the sealed diff"
|
|
75
|
+
fi
|
|
76
|
+
if [[ -f "$evidence_dir/review.json" ]] &&
|
|
77
|
+
jq -e --arg sha "$ready_diff_sha" '.diffSha256 == $sha' "$evidence_dir/review.json" >/dev/null; then
|
|
78
|
+
automation_die "a reviewer decision already exists for the sealed diff"
|
|
79
|
+
fi
|
|
80
|
+
|
|
81
|
+
restart_count=0
|
|
82
|
+
if [[ -f "$resumptions_file" ]]; then
|
|
83
|
+
restart_count="$(jq -s --arg sha "$ready_diff_sha" '[.[] | select(.diffSha256 == $sha)] | length' "$resumptions_file")"
|
|
84
|
+
fi
|
|
85
|
+
max_restarts="$(automation_config_value '.maxReviewerRestarts')"
|
|
86
|
+
[[ "$restart_count" -lt "$max_restarts" ]] || automation_die "reviewer restart limit exhausted for the sealed diff"
|
|
87
|
+
next_restart=$((restart_count + 1))
|
|
88
|
+
|
|
89
|
+
(
|
|
90
|
+
cd "$task_root"
|
|
91
|
+
./scripts/automation/scope-gate.sh "$task_id" >/dev/null
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
blocked_revision="$(jq -er '.revision' "$state_file")"
|
|
95
|
+
automation_transition_state \
|
|
96
|
+
"$task_id" \
|
|
97
|
+
"BLOCKED" \
|
|
98
|
+
"REVIEWING" \
|
|
99
|
+
"human" \
|
|
100
|
+
"reviewer-only recovery $next_restart/$max_restarts; sealed diff preserved and coder bypassed"
|
|
101
|
+
|
|
102
|
+
jq -nc \
|
|
103
|
+
--arg taskId "$task_id" \
|
|
104
|
+
--arg at "$(automation_now)" \
|
|
105
|
+
--arg diffSha256 "$ready_diff_sha" \
|
|
106
|
+
--argjson blockedRevision "$blocked_revision" \
|
|
107
|
+
--argjson restart "$next_restart" \
|
|
108
|
+
--argjson maxRestarts "$max_restarts" \
|
|
109
|
+
--argjson codingCycle "$coding_cycle" \
|
|
110
|
+
'{taskId: $taskId, at: $at, diffSha256: $diffSha256,
|
|
111
|
+
blockedRevision: $blockedRevision, restart: $restart,
|
|
112
|
+
maxRestarts: $maxRestarts, codingCycle: $codingCycle,
|
|
113
|
+
route: "BLOCKED->REVIEWING", coderRerun: false}' \
|
|
114
|
+
| automation_append_json "$resumptions_file"
|
|
115
|
+
|
|
116
|
+
automation_info "$task_id reviewer-only recovery accepted; sealed diff $ready_diff_sha"
|
|
117
|
+
automation_release_run_lock
|
|
118
|
+
trap - EXIT
|
|
119
|
+
|
|
120
|
+
"$SCRIPT_DIR/orchestrate-task.sh" "$task_id"
|
|
@@ -0,0 +1,110 @@
|
|
|
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
|
+
"$SCRIPT_DIR/validate-contract.sh" "$task_id" >/dev/null
|
|
12
|
+
|
|
13
|
+
contract="$(automation_contract_path "$task_id")"
|
|
14
|
+
evidence_dir="$(automation_evidence_path "$task_id")"
|
|
15
|
+
baseline_meta="$evidence_dir/baseline.json"
|
|
16
|
+
[[ -f "$baseline_meta" ]] || { automation_die "baseline evidence is missing"; exit 40; }
|
|
17
|
+
|
|
18
|
+
baseline_head="$(jq -er '.head' "$baseline_meta")"
|
|
19
|
+
current_head="$(git -C "$AUTOMATION_ROOT" rev-parse HEAD)"
|
|
20
|
+
[[ "$baseline_head" == "$current_head" ]] || { automation_die "HEAD changed after baseline"; exit 40; }
|
|
21
|
+
|
|
22
|
+
workspace_file="$(automation_workspace_path "$task_id")"
|
|
23
|
+
planning_commit_policy="$(jq -r '.planningArtifactsCommitPolicy // "legacyCommittedSeparately"' "$workspace_file")"
|
|
24
|
+
if [[ "$planning_commit_policy" == "withProductChanges" ]]; then
|
|
25
|
+
automation_assert_planning_artifacts_sealed "$task_id" "$AUTOMATION_ROOT" || exit 40
|
|
26
|
+
changed_file_list="$(automation_product_changed_paths_at "$task_id" "$AUTOMATION_ROOT")"
|
|
27
|
+
[[ -n "$changed_file_list" ]] || { automation_die "task has no product changes beyond its sealed planning artifacts"; exit 40; }
|
|
28
|
+
else
|
|
29
|
+
changed_file_list="$(automation_changed_paths)"
|
|
30
|
+
[[ -n "$changed_file_list" ]] || { automation_die "task has no changed files"; exit 40; }
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
max_changed="$(jq -r '.maxChangedFiles' "$contract")"
|
|
34
|
+
changed_count="$(printf '%s\n' "$changed_file_list" | awk 'NF { count++ } END { print count + 0 }')"
|
|
35
|
+
[[ "$changed_count" -le "$max_changed" ]] || { automation_die "changed file count $changed_count exceeds limit $max_changed"; exit 40; }
|
|
36
|
+
|
|
37
|
+
production_changed=0
|
|
38
|
+
test_changed=0
|
|
39
|
+
while IFS= read -r path; do
|
|
40
|
+
[[ -n "$path" ]] || continue
|
|
41
|
+
|
|
42
|
+
while IFS= read -r protected; do
|
|
43
|
+
if automation_path_matches "$path" "$protected"; then
|
|
44
|
+
automation_die "protected path changed: $path"
|
|
45
|
+
exit 40
|
|
46
|
+
fi
|
|
47
|
+
done < <(jq -r '.protectedPaths[]' "$AUTOMATION_CONFIG")
|
|
48
|
+
|
|
49
|
+
if automation_array_matches_path "$contract" '.forbiddenPaths' "$path"; then
|
|
50
|
+
automation_die "forbidden path changed: $path"
|
|
51
|
+
exit 40
|
|
52
|
+
fi
|
|
53
|
+
if ! automation_array_matches_path "$contract" '.allowedPaths' "$path"; then
|
|
54
|
+
automation_die "path is outside allowedPaths: $path"
|
|
55
|
+
exit 40
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
if automation_array_matches_path "$AUTOMATION_CONFIG" '.androidProject.productionPaths' "$path"; then
|
|
59
|
+
production_changed=1
|
|
60
|
+
fi
|
|
61
|
+
if automation_array_matches_path "$AUTOMATION_CONFIG" '.androidProject.testPaths' "$path"; then
|
|
62
|
+
test_changed=1
|
|
63
|
+
fi
|
|
64
|
+
done <<< "$changed_file_list"
|
|
65
|
+
|
|
66
|
+
deleted_tests="$(
|
|
67
|
+
git -C "$AUTOMATION_ROOT" diff --name-only --diff-filter=D HEAD -- \
|
|
68
|
+
| while IFS= read -r path; do
|
|
69
|
+
if automation_array_matches_path "$AUTOMATION_CONFIG" '.androidProject.testPaths' "$path"; then
|
|
70
|
+
printf '%s\n' "$path"
|
|
71
|
+
fi
|
|
72
|
+
done
|
|
73
|
+
)"
|
|
74
|
+
[[ -z "$deleted_tests" ]] || { automation_die "test deletion is forbidden: $deleted_tests"; exit 40; }
|
|
75
|
+
|
|
76
|
+
test_policy="$(jq -r '.testPolicy' "$contract")"
|
|
77
|
+
if [[ "$production_changed" == "1" && "$test_policy" == "required" && "$test_changed" != "1" ]]; then
|
|
78
|
+
automation_die "production code changed without a test change"
|
|
79
|
+
exit 40
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
added_test_lines="$(
|
|
83
|
+
while IFS= read -r path; do
|
|
84
|
+
[[ -n "$path" ]] || continue
|
|
85
|
+
if automation_array_matches_path "$AUTOMATION_CONFIG" '.androidProject.testPaths' "$path"; then
|
|
86
|
+
git -C "$AUTOMATION_ROOT" diff --unified=0 HEAD -- "$path"
|
|
87
|
+
fi
|
|
88
|
+
done <<< "$changed_file_list" \
|
|
89
|
+
| awk '/^\+\+\+/ { next } /^\+/ { sub(/^\+/, ""); print }'
|
|
90
|
+
)"
|
|
91
|
+
weakening_pattern='@Ignore|@Disabled|assumeTrue\(false\)|assertTrue\(true\)|assertFalse\(false\)'
|
|
92
|
+
if printf '%s\n' "$added_test_lines" | rg -n "$weakening_pattern" >/dev/null; then
|
|
93
|
+
automation_die "potential test weakening detected in added lines"
|
|
94
|
+
exit 40
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
while IFS= read -r untracked_test; do
|
|
98
|
+
if automation_array_matches_path "$AUTOMATION_CONFIG" '.androidProject.testPaths' "$untracked_test" && \
|
|
99
|
+
rg -n "$weakening_pattern" "$AUTOMATION_ROOT/$untracked_test" >/dev/null; then
|
|
100
|
+
automation_die "potential test weakening detected in new test: $untracked_test"
|
|
101
|
+
exit 40
|
|
102
|
+
fi
|
|
103
|
+
done < <(git -C "$AUTOMATION_ROOT" ls-files --others --exclude-standard)
|
|
104
|
+
|
|
105
|
+
if [[ "$planning_commit_policy" == "withProductChanges" ]]; then
|
|
106
|
+
automation_info "scope gate passed ($changed_count product files; sealed planning artifacts excluded from the contract limit)"
|
|
107
|
+
else
|
|
108
|
+
automation_info "scope gate passed ($changed_count changed files)"
|
|
109
|
+
fi
|
|
110
|
+
printf '%s\n' "$changed_file_list"
|
|
@@ -0,0 +1,34 @@
|
|
|
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
|
+
wanted="${1:-}"
|
|
9
|
+
case "$wanted" in
|
|
10
|
+
PENDING|READY_FOR_REVIEW|REVIEWING) ;;
|
|
11
|
+
*) printf 'Usage: %s PENDING|READY_FOR_REVIEW|REVIEWING\n' "$0" >&2; exit 2 ;;
|
|
12
|
+
esac
|
|
13
|
+
|
|
14
|
+
matches=()
|
|
15
|
+
while IFS= read -r state_file; do
|
|
16
|
+
[[ -f "$state_file" ]] || continue
|
|
17
|
+
state="$(jq -er '.state' "$state_file" 2>/dev/null || true)"
|
|
18
|
+
task_id="$(jq -er '.taskId' "$state_file" 2>/dev/null || true)"
|
|
19
|
+
if [[ "$state" == "$wanted" && "$task_id" =~ ^TASK-[A-Z0-9-]+$ ]]; then
|
|
20
|
+
matches+=("$task_id")
|
|
21
|
+
fi
|
|
22
|
+
done < <(find "$AUTOMATION_STATE_DIR" -maxdepth 1 -type f -name 'TASK-*.json' | LC_ALL=C sort)
|
|
23
|
+
|
|
24
|
+
if [[ "${#matches[@]}" -eq 0 ]]; then
|
|
25
|
+
automation_info "no task is in state $wanted"
|
|
26
|
+
exit 3
|
|
27
|
+
fi
|
|
28
|
+
if [[ "${#matches[@]}" -ne 1 ]]; then
|
|
29
|
+
automation_die "expected one $wanted task, found ${#matches[@]}: ${matches[*]}"
|
|
30
|
+
exit 4
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
"$SCRIPT_DIR/validate-contract.sh" "${matches[0]}" >/dev/null
|
|
34
|
+
printf '%s\n' "${matches[0]}"
|
|
@@ -0,0 +1,24 @@
|
|
|
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
|
+
automation_info "starting read-only shadow preflight" >&2
|
|
9
|
+
"$SCRIPT_DIR/preflight.sh" --shadow >&2
|
|
10
|
+
|
|
11
|
+
task_count="$(find "$AUTOMATION_TASKS_DIR" -maxdepth 1 -type f -name 'TASK-*.json' | wc -l | tr -d ' ')"
|
|
12
|
+
automation_ensure_runtime_layout
|
|
13
|
+
state_count="$(find "$AUTOMATION_STATE_DIR" -maxdepth 1 -type f -name 'TASK-*.json' | wc -l | tr -d ' ')"
|
|
14
|
+
|
|
15
|
+
jq -n \
|
|
16
|
+
--arg checkedAt "$(automation_now)" \
|
|
17
|
+
--arg root "$AUTOMATION_ROOT" \
|
|
18
|
+
--arg enabled "$(automation_config_value '.enabled')" \
|
|
19
|
+
--arg mode "$(automation_config_value '.mode')" \
|
|
20
|
+
--argjson taskCount "$task_count" \
|
|
21
|
+
--argjson stateCount "$state_count" \
|
|
22
|
+
'{checkedAt: $checkedAt, root: $root, enabled: ($enabled == "true"), mode: $mode, activeContracts: $taskCount, runtimeStates: $stateCount, mutationPerformed: false}'
|
|
23
|
+
|
|
24
|
+
automation_info "shadow run complete; no repository mutation performed" >&2
|
|
@@ -0,0 +1,111 @@
|
|
|
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
|
+
workspace_file="$(automation_workspace_path "$task_id")"
|
|
14
|
+
evidence_dir="$(automation_evidence_path "$task_id")"
|
|
15
|
+
ready_file="$evidence_dir/ready.json"
|
|
16
|
+
review_file="$evidence_dir/review.json"
|
|
17
|
+
report_file="$evidence_dir/acceptance-report.json"
|
|
18
|
+
[[ -f "$workspace_file" && -f "$ready_file" && -f "$review_file" ]] || \
|
|
19
|
+
automation_die "acceptance evidence is incomplete"
|
|
20
|
+
|
|
21
|
+
task_root="$(automation_workspace_task_root "$workspace_file")"
|
|
22
|
+
[[ -d "$task_root" ]] || automation_die "recorded task root is missing: $task_root"
|
|
23
|
+
source_root="$(jq -er '.sourceRoot' "$workspace_file")"
|
|
24
|
+
original_branch="$(jq -er '.originalBranch' "$workspace_file")"
|
|
25
|
+
live_original_head="$(git -C "$source_root" rev-parse "refs/heads/$original_branch")"
|
|
26
|
+
|
|
27
|
+
if [[ ! -f "$report_file" ]] || \
|
|
28
|
+
[[ "$(jq -r '.originalHeadCurrent // ""' "$report_file" 2>/dev/null || true)" != "$live_original_head" ]]; then
|
|
29
|
+
(
|
|
30
|
+
cd "$task_root"
|
|
31
|
+
./scripts/automation/acceptance-report.sh "$task_id" >/dev/null
|
|
32
|
+
)
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
[[ "$(jq -er '.taskId' "$report_file")" == "$task_id" ]] || automation_die "acceptance report task ID does not match"
|
|
36
|
+
[[ "$(jq -er '.state' "$report_file")" == "AWAITING_HUMAN" ]] || automation_die "acceptance report is not awaiting human review"
|
|
37
|
+
[[ "$(jq -er '.decision' "$review_file")" == "APPROVED" ]] || automation_die "latest independent review is not approved"
|
|
38
|
+
|
|
39
|
+
current_diff_sha="$(automation_worktree_diff_sha "$task_root")"
|
|
40
|
+
report_diff_sha="$(jq -er '.sealedDiffSha256' "$report_file")"
|
|
41
|
+
[[ "$current_diff_sha" == "$report_diff_sha" ]] || automation_die "sealed diff changed after the acceptance package was generated"
|
|
42
|
+
[[ "$current_diff_sha" == "$(jq -er '.diffSha256' "$ready_file")" ]] || automation_die "sealed diff no longer matches the quality gate"
|
|
43
|
+
[[ "$current_diff_sha" == "$(jq -er '.diffSha256' "$review_file")" ]] || automation_die "sealed diff no longer matches independent review"
|
|
44
|
+
|
|
45
|
+
changed_count="$(jq -er '.changedPaths | length' "$report_file")"
|
|
46
|
+
max_changed_files="$(jq -er '.maxChangedFiles' "$report_file")"
|
|
47
|
+
device_tests_required="$(jq -r '.deviceTestsRequired' "$report_file")"
|
|
48
|
+
|
|
49
|
+
printf '# 🔔 人工验收提醒\n\n'
|
|
50
|
+
printf '自动执行已停在 `AWAITING_HUMAN`。下面内容已重新核对 sealed diff;此时代码、计划与任务合同均尚未提交或集成。\n\n'
|
|
51
|
+
printf '| 绑定项 | 当前封存值 |\n'
|
|
52
|
+
printf '| --- | --- |\n'
|
|
53
|
+
printf '| 任务 | `%s` · %s |\n' "$task_id" "$(jq -r '.title' "$report_file")"
|
|
54
|
+
printf '| 原分支 | `%s` |\n' "$(jq -r '.originalBranch' "$report_file")"
|
|
55
|
+
printf '| 原分支当前 HEAD | `%s` |\n' "$(jq -r '.originalHeadCurrent' "$report_file")"
|
|
56
|
+
printf '| 原分支漂移 | `%s` |\n' "$(jq -r 'if .originalBranchDrifted then "是(集成将阻断)" else "否" end' "$report_file")"
|
|
57
|
+
printf '| 任务分支收尾 | 成功集成后自动删除;失败或阻塞时保留 |\n'
|
|
58
|
+
printf '| sealed diff SHA | `%s` |\n' "$report_diff_sha"
|
|
59
|
+
printf '| 变更范围 | 实际 %s 个 / 合同上限 %s 个 |\n' "$changed_count" "$max_changed_files"
|
|
60
|
+
printf '| 提交策略 | 代码、测试、计划与任务合同合并为一个提交 |\n'
|
|
61
|
+
printf '| 自动证据 | Baseline ✓ · RED ✓ · G1–G6 ✓ · Reviewer APPROVED ✓ |\n'
|
|
62
|
+
printf '\n## 必须重点复核\n\n'
|
|
63
|
+
printf '### P0 · 真实行为是否满足合同\n\n'
|
|
64
|
+
printf '这是人工验收的核心;请按以下条件实际操作或检查结果,不要只看测试为绿:\n\n'
|
|
65
|
+
jq -r '.acceptanceCriteria | to_entries[] | "\(.key + 1). \(.value)"' "$report_file"
|
|
66
|
+
|
|
67
|
+
printf '\n### P0 · 旧行为与范围是否被误伤\n\n'
|
|
68
|
+
printf '**实际变更文件:**\n\n'
|
|
69
|
+
jq -r '.changedPaths[] | "- `\(.)`"' "$report_file"
|
|
70
|
+
printf '\n**随产品变更一并提交的封存规划文件(不计入合同文件上限):**\n\n'
|
|
71
|
+
jq -r '.planningArtifacts[] | "- `\(.)`"' "$report_file"
|
|
72
|
+
printf '\n**合同允许路径:**\n\n'
|
|
73
|
+
jq -r '.allowedPaths[] | "- `\(.)`"' "$report_file"
|
|
74
|
+
printf '\n**明确不应发生:**\n\n'
|
|
75
|
+
jq -r '.nonGoals[] | "- \(.)"' "$report_file"
|
|
76
|
+
|
|
77
|
+
printf '\n### P1 · 自动证据是否可信\n\n'
|
|
78
|
+
printf -- '- RED 退出码:`%s`(应为非 0,证明测试先真实失败)\n' "$(jq -r '.evidence.redExitCode' "$report_file")"
|
|
79
|
+
printf -- '- 质量门:`%s`,第 %s 个编码周期内共运行 %s 次\n' \
|
|
80
|
+
"$(jq -r '.evidence.qualityGate' "$report_file")" \
|
|
81
|
+
"$(jq -r '.evidence.codingCycle' "$report_file")" \
|
|
82
|
+
"$(jq -r '.evidence.gateAttempts' "$report_file")"
|
|
83
|
+
printf -- '- Reviewer:`%s`,独立复验退出码 `%s`\n' \
|
|
84
|
+
"$(jq -r '.evidence.reviewerDecision' "$report_file")" \
|
|
85
|
+
"$(jq -r '.evidence.reviewerVerificationExitCode' "$report_file")"
|
|
86
|
+
printf -- '- Reviewer 摘要:%s\n' "$(jq -r '.reviewSummary' "$report_file")"
|
|
87
|
+
printf -- '- 聚焦测试:\n'
|
|
88
|
+
jq -r '.targetTests[] | " - `./gradlew \(.gradleTask) --tests \(.filter)`"' "$report_file"
|
|
89
|
+
|
|
90
|
+
printf '\n### P1 · 绑定与剩余风险\n\n'
|
|
91
|
+
printf -- '- 当前 diff 与质量门、独立 Review、验收包的 SHA 三方一致。\n'
|
|
92
|
+
printf -- '- 目标接收分支是 `%s`;通过后只做本地集成,`pushed: false`。\n' "$(jq -r '.originalBranch' "$report_file")"
|
|
93
|
+
printf -- '- 原分支安全到达已验证提交后,本地任务分支 `%s` 将被自动删除;集成失败时保留以便恢复。\n' \
|
|
94
|
+
"$(jq -r '.taskBranch' "$report_file")"
|
|
95
|
+
if [[ "$(jq -r '.originalBranchDrifted' "$report_file")" == "true" ]]; then
|
|
96
|
+
printf -- '- **原分支已偏离批准基线;当前策略会进入 `INTEGRATION_BLOCKED`,不会自动 cherry-pick。**\n'
|
|
97
|
+
fi
|
|
98
|
+
if [[ "$device_tests_required" == "true" ]]; then
|
|
99
|
+
printf -- '- 合同要求设备测试,自动质量门已执行;仍应人工确认真机/模拟器上的可观察体验。\n'
|
|
100
|
+
else
|
|
101
|
+
printf -- '- 合同未要求设备测试。如果变更涉及 UI、系统权限或真机差异,请在通过前补做人工体验。\n'
|
|
102
|
+
fi
|
|
103
|
+
printf -- '- 测试策略:`%s`。%s\n' \
|
|
104
|
+
"$(jq -r '.testPolicy' "$report_file")" \
|
|
105
|
+
"$(jq -r '.testPolicyReason' "$report_file")"
|
|
106
|
+
|
|
107
|
+
printf '\n## 你的决定\n\n'
|
|
108
|
+
printf -- '- 全部重点通过:选择 **验收通过,提交到原分支。**\n'
|
|
109
|
+
printf -- '- 任一项不通过:选择 **验收不通过,需要说明失败项**,并指出失败的验收条件或观察结果。\n'
|
|
110
|
+
printf -- '- 还未检查完:选择 **暂不决定,保持封存**;系统继续停在 `AWAITING_HUMAN`。\n'
|
|
111
|
+
printf '\n> 不要修改已封存的任务目录后沿用本次验收;diff 一旦变化,脚本会拒绝旧验收。\n'
|
|
@@ -0,0 +1,125 @@
|
|
|
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
|
+
contract="$(automation_contract_path "$task_id")"
|
|
14
|
+
state_file="$(automation_state_path "$task_id")"
|
|
15
|
+
workspace_file="$(automation_workspace_path "$task_id")"
|
|
16
|
+
origin_file="$(automation_origin_path "$task_id")"
|
|
17
|
+
evidence_dir="$(automation_evidence_path "$task_id")"
|
|
18
|
+
|
|
19
|
+
state_json="$(if [[ -f "$state_file" ]]; then jq -c . "$state_file"; else printf 'null'; fi)"
|
|
20
|
+
workspace_json="$(if [[ -f "$workspace_file" ]]; then jq -c . "$workspace_file"; else printf 'null'; fi)"
|
|
21
|
+
origin_json="$(if [[ -f "$origin_file" ]]; then jq -c . "$origin_file"; else printf 'null'; fi)"
|
|
22
|
+
baseline_json="$(if [[ -f "$evidence_dir/baseline.json" ]]; then jq -c . "$evidence_dir/baseline.json"; else printf 'null'; fi)"
|
|
23
|
+
red_json="$(if [[ -f "$evidence_dir/red.json" ]]; then jq -c . "$evidence_dir/red.json"; else printf 'null'; fi)"
|
|
24
|
+
ready_json="$(if [[ -f "$evidence_dir/ready.json" ]]; then jq -c . "$evidence_dir/ready.json"; else printf 'null'; fi)"
|
|
25
|
+
review_json="$(if [[ -f "$evidence_dir/review.json" ]]; then jq -c . "$evidence_dir/review.json"; else printf 'null'; fi)"
|
|
26
|
+
gate_json=null
|
|
27
|
+
if [[ "$ready_json" != "null" ]]; then
|
|
28
|
+
coding_cycle="$(jq -r '.codingCycle // 0' <<< "$ready_json")"
|
|
29
|
+
gate_file="$evidence_dir/gate-attempts-cycle-$coding_cycle.json"
|
|
30
|
+
if [[ -f "$gate_file" ]]; then
|
|
31
|
+
gate_json="$(jq -c . "$gate_file")"
|
|
32
|
+
fi
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
current_diff_sha=""
|
|
36
|
+
lease_json=null
|
|
37
|
+
lease_matches=null
|
|
38
|
+
original_head_current=""
|
|
39
|
+
original_branch_drifted=null
|
|
40
|
+
task_branch_exists=null
|
|
41
|
+
if [[ "$workspace_json" != "null" ]]; then
|
|
42
|
+
task_root="$(jq -r '.taskRoot // .taskWorktree // empty' <<< "$workspace_json")"
|
|
43
|
+
if [[ -n "$task_root" && -d "$task_root" ]]; then
|
|
44
|
+
current_diff_sha="$(automation_worktree_diff_sha "$task_root")"
|
|
45
|
+
fi
|
|
46
|
+
source_root="$(jq -r '.sourceRoot // empty' <<< "$workspace_json")"
|
|
47
|
+
original_branch="$(jq -r '.originalBranch // empty' <<< "$workspace_json")"
|
|
48
|
+
task_branch="$(jq -r '.taskBranch // empty' <<< "$workspace_json")"
|
|
49
|
+
baseline_head="$(jq -r '.baselineHead // empty' <<< "$workspace_json")"
|
|
50
|
+
if [[ -n "$source_root" && -n "$task_branch" ]]; then
|
|
51
|
+
if git -C "$source_root" show-ref --verify --quiet "refs/heads/$task_branch"; then
|
|
52
|
+
task_branch_exists=true
|
|
53
|
+
else
|
|
54
|
+
task_branch_exists=false
|
|
55
|
+
fi
|
|
56
|
+
fi
|
|
57
|
+
if [[ -n "$source_root" && -n "$original_branch" ]] && \
|
|
58
|
+
git -C "$source_root" show-ref --verify --quiet "refs/heads/$original_branch"; then
|
|
59
|
+
original_head_current="$(git -C "$source_root" rev-parse "refs/heads/$original_branch")"
|
|
60
|
+
if [[ -n "$baseline_head" && "$original_head_current" != "$baseline_head" ]]; then
|
|
61
|
+
original_branch_drifted=true
|
|
62
|
+
else
|
|
63
|
+
original_branch_drifted=false
|
|
64
|
+
fi
|
|
65
|
+
fi
|
|
66
|
+
if [[ "$(jq -r '.repositoryLeaseRequired // false' <<< "$workspace_json")" == "true" ]]; then
|
|
67
|
+
lease_file="$(automation_repository_lease_dir)/lease.json"
|
|
68
|
+
if [[ -f "$lease_file" ]]; then
|
|
69
|
+
lease_json="$(jq -c . "$lease_file")"
|
|
70
|
+
if jq -e \
|
|
71
|
+
--arg taskId "$task_id" \
|
|
72
|
+
--arg sourceRoot "$source_root" \
|
|
73
|
+
--arg strategy "$(jq -r '.workspaceStrategy' <<< "$workspace_json")" \
|
|
74
|
+
'.taskId == $taskId and .sourceRoot == $sourceRoot and .workspaceStrategy == $strategy' \
|
|
75
|
+
"$lease_file" >/dev/null; then
|
|
76
|
+
lease_matches=true
|
|
77
|
+
else
|
|
78
|
+
lease_matches=false
|
|
79
|
+
fi
|
|
80
|
+
else
|
|
81
|
+
lease_matches=false
|
|
82
|
+
fi
|
|
83
|
+
fi
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
[[ -f "$contract" ]] || automation_die "contract not found: $contract"
|
|
87
|
+
jq -n \
|
|
88
|
+
--slurpfile contract "$contract" \
|
|
89
|
+
--argjson state "$state_json" \
|
|
90
|
+
--argjson workspace "$workspace_json" \
|
|
91
|
+
--argjson origin "$origin_json" \
|
|
92
|
+
--argjson baseline "$baseline_json" \
|
|
93
|
+
--argjson red "$red_json" \
|
|
94
|
+
--argjson ready "$ready_json" \
|
|
95
|
+
--argjson gate "$gate_json" \
|
|
96
|
+
--argjson review "$review_json" \
|
|
97
|
+
--argjson repositoryLease "$lease_json" \
|
|
98
|
+
--argjson repositoryLeaseMatches "$lease_matches" \
|
|
99
|
+
--argjson taskBranchExists "$task_branch_exists" \
|
|
100
|
+
--arg originalHeadCurrent "$original_head_current" \
|
|
101
|
+
--argjson originalBranchDrifted "$original_branch_drifted" \
|
|
102
|
+
--arg evidence "$evidence_dir" \
|
|
103
|
+
--arg currentDiffSha256 "$current_diff_sha" \
|
|
104
|
+
'{contract: $contract[0], state: $state, workspace: $workspace, origin: $origin,
|
|
105
|
+
runtime: {
|
|
106
|
+
repositoryLease: $repositoryLease,
|
|
107
|
+
repositoryLeaseMatches: $repositoryLeaseMatches,
|
|
108
|
+
taskBranchExists: $taskBranchExists,
|
|
109
|
+
originalHeadCurrent: ($originalHeadCurrent | if length == 0 then null else . end),
|
|
110
|
+
originalBranchDrifted: $originalBranchDrifted
|
|
111
|
+
},
|
|
112
|
+
evidenceDirectory: $evidence,
|
|
113
|
+
evidence: {
|
|
114
|
+
directory: $evidence,
|
|
115
|
+
baseline: $baseline,
|
|
116
|
+
red: $red,
|
|
117
|
+
ready: $ready,
|
|
118
|
+
latestGate: $gate,
|
|
119
|
+
review: $review,
|
|
120
|
+
currentDiffSha256: ($currentDiffSha256 | if length == 0 then null else . end),
|
|
121
|
+
sealedDiffMatches: (if ($ready == null or ($currentDiffSha256 | length) == 0)
|
|
122
|
+
then null
|
|
123
|
+
else $ready.diffSha256 == $currentDiffSha256
|
|
124
|
+
end)
|
|
125
|
+
}}'
|
|
@@ -0,0 +1,73 @@
|
|
|
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
|
+
decision="${2:-}"
|
|
10
|
+
shift $(( $# >= 2 ? 2 : $# ))
|
|
11
|
+
summary="$*"
|
|
12
|
+
|
|
13
|
+
if [[ -z "$task_id" || -z "$decision" || -z "$summary" ]]; then
|
|
14
|
+
printf 'Usage: %s TASK-ID APPROVED|CHANGES_REQUESTED SUMMARY\n' "$0" >&2
|
|
15
|
+
exit 2
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
automation_validate_task_id "$task_id"
|
|
19
|
+
[[ "$decision" == "APPROVED" || "$decision" == "CHANGES_REQUESTED" ]] || automation_die "invalid review decision: $decision"
|
|
20
|
+
[[ ${#summary} -ge 20 ]] || automation_die "review summary must contain at least 20 characters"
|
|
21
|
+
[[ "$(automation_read_state "$task_id")" == "REVIEWING" ]] || automation_die "$task_id is not REVIEWING"
|
|
22
|
+
|
|
23
|
+
evidence_dir="$(automation_evidence_path "$task_id")"
|
|
24
|
+
ready_meta="$evidence_dir/ready.json"
|
|
25
|
+
[[ -f "$ready_meta" ]] || automation_die "ready evidence is missing"
|
|
26
|
+
current_diff_sha="$(automation_worktree_diff_sha)"
|
|
27
|
+
ready_diff_sha="$(jq -er '.diffSha256' "$ready_meta")"
|
|
28
|
+
|
|
29
|
+
if [[ "$current_diff_sha" != "$ready_diff_sha" ]]; then
|
|
30
|
+
decision="CHANGES_REQUESTED"
|
|
31
|
+
summary="Diff changed after coder gate. $summary"
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
review_log="$evidence_dir/review-verification.log"
|
|
35
|
+
verification_status=0
|
|
36
|
+
if [[ "$decision" == "APPROVED" ]]; then
|
|
37
|
+
set +e
|
|
38
|
+
"$SCRIPT_DIR/verify-task.sh" "$task_id" 2>&1 | tee "$review_log"
|
|
39
|
+
verification_status=${PIPESTATUS[0]}
|
|
40
|
+
set -e
|
|
41
|
+
if [[ "$verification_status" -ne 0 ]]; then
|
|
42
|
+
decision="CHANGES_REQUESTED"
|
|
43
|
+
summary="Independent verification failed with exit $verification_status. $summary"
|
|
44
|
+
fi
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
jq -n \
|
|
48
|
+
--arg taskId "$task_id" \
|
|
49
|
+
--arg decision "$decision" \
|
|
50
|
+
--arg summary "$summary" \
|
|
51
|
+
--arg reviewedAt "$(automation_now)" \
|
|
52
|
+
--arg diffSha256 "$current_diff_sha" \
|
|
53
|
+
--argjson verificationExitCode "$verification_status" \
|
|
54
|
+
'{taskId: $taskId, decision: $decision, summary: $summary, reviewedAt: $reviewedAt, diffSha256: $diffSha256, verificationExitCode: $verificationExitCode}' \
|
|
55
|
+
| automation_record_json "$evidence_dir/review.json"
|
|
56
|
+
jq -nc \
|
|
57
|
+
--arg taskId "$task_id" \
|
|
58
|
+
--arg decision "$decision" \
|
|
59
|
+
--arg summary "$summary" \
|
|
60
|
+
--arg reviewedAt "$(automation_now)" \
|
|
61
|
+
--arg diffSha256 "$current_diff_sha" \
|
|
62
|
+
--argjson verificationExitCode "$verification_status" \
|
|
63
|
+
'{taskId: $taskId, decision: $decision, summary: $summary, reviewedAt: $reviewedAt, diffSha256: $diffSha256, verificationExitCode: $verificationExitCode}' \
|
|
64
|
+
| automation_append_json "$evidence_dir/reviews.jsonl"
|
|
65
|
+
|
|
66
|
+
if [[ "$decision" == "APPROVED" ]]; then
|
|
67
|
+
automation_transition_state "$task_id" "REVIEWING" "AWAITING_HUMAN" "reviewer" "independent review approved; human acceptance required"
|
|
68
|
+
automation_info "$task_id approved and awaiting human acceptance"
|
|
69
|
+
else
|
|
70
|
+
automation_transition_state "$task_id" "REVIEWING" "CHANGES_REQUESTED" "reviewer" "$summary"
|
|
71
|
+
automation_warn "$task_id requires changes"
|
|
72
|
+
exit 1
|
|
73
|
+
fi
|