@frankzhang2026/opencode-android-orchestrator 0.2.0 → 0.4.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 +30 -1
- package/README.md +85 -43
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/dist/cli.js +127 -10
- package/dist/cli.js.map +1 -1
- package/dist/doctor/installation.d.ts.map +1 -1
- package/dist/doctor/installation.js +29 -4
- package/dist/doctor/installation.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/installer/adaptive-templates.d.ts +25 -3
- package/dist/installer/adaptive-templates.d.ts.map +1 -1
- package/dist/installer/adaptive-templates.js +75 -9
- package/dist/installer/adaptive-templates.js.map +1 -1
- package/dist/installer/index.d.ts +2 -2
- package/dist/installer/index.d.ts.map +1 -1
- package/dist/installer/index.js +2 -2
- package/dist/installer/index.js.map +1 -1
- package/dist/installer/init.d.ts +2 -1
- package/dist/installer/init.d.ts.map +1 -1
- package/dist/installer/init.js +11 -2
- package/dist/installer/init.js.map +1 -1
- package/dist/installer/opencode-config.d.ts +4 -3
- package/dist/installer/opencode-config.d.ts.map +1 -1
- package/dist/installer/opencode-config.js +10 -4
- package/dist/installer/opencode-config.js.map +1 -1
- package/dist/installer/upgrade.d.ts +5 -0
- package/dist/installer/upgrade.d.ts.map +1 -1
- package/dist/installer/upgrade.js +44 -10
- package/dist/installer/upgrade.js.map +1 -1
- package/docs/MIGRATION.md +25 -14
- package/docs/SECURITY.md +29 -5
- package/docs/TROUBLESHOOTING.md +25 -6
- package/package.json +1 -1
- package/templates/.opencode/agents/scheduled-coder.md +3 -0
- package/templates/.opencode/agents/scheduled-reviewer.md +5 -4
- package/templates/.opencode/skills/scheduled-quality-coder/SKILL.md +10 -6
- package/templates/.opencode/skills/scheduled-quality-reviewer/SKILL.md +7 -4
- package/templates/AGENTS.md.fragment +4 -0
- package/templates/README.md +5 -2
- package/templates/automation/config.json +18 -0
- package/templates/automation/config.schema.json +31 -0
- package/templates/automation/task-contract.schema.json +22 -1
- package/templates/automation/tasks/TASK-TEMPLATE.json.example +5 -1
- package/templates/scripts/automation/abort-task.sh +5 -10
- package/templates/scripts/automation/accept-and-integrate.sh +1 -1
- package/templates/scripts/automation/acceptance-report.sh +2 -9
- package/templates/scripts/automation/approve-and-run.sh +3 -0
- package/templates/scripts/automation/claim-task.sh +6 -6
- package/templates/scripts/automation/lib.sh +403 -8
- package/templates/scripts/automation/orchestrate-task.sh +1 -1
- package/templates/scripts/automation/preflight.sh +1 -1
- package/templates/scripts/automation/record-red.sh +7 -9
- package/templates/scripts/automation/scope-gate.sh +12 -9
- package/templates/scripts/automation/show-acceptance-review.sh +1 -1
- package/templates/scripts/automation/status.sh +4 -0
- package/templates/scripts/automation/tests/run-tests.sh +164 -9
- package/templates/scripts/automation/validate-contract.sh +16 -5
- package/templates/scripts/automation/verify-integration.sh +12 -11
- package/templates/scripts/automation/verify-task.sh +13 -28
|
@@ -82,6 +82,7 @@ write_workspace() {
|
|
|
82
82
|
mkdir -p \
|
|
83
83
|
"$fixture/automation/tasks" \
|
|
84
84
|
"$fixture/docs/plans" \
|
|
85
|
+
"$fixture/local" \
|
|
85
86
|
"$fixture/scripts/automation" \
|
|
86
87
|
"$fixture/mobile-client/src/main/java/dev/example/orchestratorfixture" \
|
|
87
88
|
"$fixture/mobile-client/src/test/java/dev/example/orchestratorfixture"
|
|
@@ -89,6 +90,8 @@ mkdir -p \
|
|
|
89
90
|
cp "$SOURCE_SCRIPT_DIR"/*.sh "$fixture/scripts/automation/"
|
|
90
91
|
chmod +x "$fixture/scripts/automation/"*.sh
|
|
91
92
|
printf '%s\n' '# Approved test plan' > "$fixture/docs/plans/TASK-TEST-001.md"
|
|
93
|
+
printf '%s\n' 'operator baseline' > "$fixture/local/operator-note.txt"
|
|
94
|
+
printf '%s\n' 'rename baseline' > "$fixture/local/rename-source.txt"
|
|
92
95
|
printf '%s\n' 'class ExistingFeature' > "$fixture/mobile-client/src/main/java/dev/example/orchestratorfixture/ExistingFeature.kt"
|
|
93
96
|
|
|
94
97
|
printf '%s\n' \
|
|
@@ -137,10 +140,18 @@ jq -n '{
|
|
|
137
140
|
"systematic-debugging",
|
|
138
141
|
"verification-before-completion"
|
|
139
142
|
],
|
|
143
|
+
gradleVerification: {
|
|
144
|
+
fullUnitTestTasks: ["testDebugUnitTest"],
|
|
145
|
+
focusedTestTasks: ["testDebugUnitTest"],
|
|
146
|
+
assembleTasks: ["assembleDebug"],
|
|
147
|
+
lintTasks: ["lint"],
|
|
148
|
+
deviceTestTasks: ["connectedDebugAndroidTest"]
|
|
149
|
+
},
|
|
140
150
|
androidProject: {
|
|
141
151
|
name: "automation-shell-fixture",
|
|
142
152
|
gradleDsl: "kotlin",
|
|
143
153
|
settingsFile: "settings.gradle.kts",
|
|
154
|
+
moduleScope: "all",
|
|
144
155
|
primaryModule: ":mobile-client",
|
|
145
156
|
modules: [{
|
|
146
157
|
gradlePath: ":mobile-client",
|
|
@@ -158,6 +169,7 @@ jq -n '{
|
|
|
158
169
|
]
|
|
159
170
|
},
|
|
160
171
|
protectedPaths: [
|
|
172
|
+
".automation-worktree-allowlist",
|
|
161
173
|
".opencode/",
|
|
162
174
|
".automation-plugin/",
|
|
163
175
|
"automation/",
|
|
@@ -192,6 +204,7 @@ jq -n '{
|
|
|
192
204
|
"mobile-client/src/test/java/dev/example/orchestratorfixture/**"
|
|
193
205
|
],
|
|
194
206
|
forbiddenPaths: [
|
|
207
|
+
".automation-worktree-allowlist",
|
|
195
208
|
".opencode/**",
|
|
196
209
|
".automation-plugin/**",
|
|
197
210
|
"automation/**",
|
|
@@ -217,7 +230,10 @@ jq -n '{
|
|
|
217
230
|
],
|
|
218
231
|
acceptanceCriteria: ["Greeting returns the approved value"],
|
|
219
232
|
nonGoals: ["No unrelated refactoring"],
|
|
220
|
-
targetTests: [
|
|
233
|
+
targetTests: [{
|
|
234
|
+
gradleTask: "testDebugUnitTest",
|
|
235
|
+
filter: "dev.example.orchestratorfixture.GreetingTest"
|
|
236
|
+
}],
|
|
221
237
|
deviceTestsRequired: false,
|
|
222
238
|
testPolicy: "required",
|
|
223
239
|
testPolicyReason: "The behavior requires a focused regression test"
|
|
@@ -232,6 +248,74 @@ jq -n '{
|
|
|
232
248
|
git commit -qm 'Create automation fixture'
|
|
233
249
|
)
|
|
234
250
|
|
|
251
|
+
printf '%s\n' \
|
|
252
|
+
'# Exact repository-relative paths intentionally kept local' \
|
|
253
|
+
'local/operator-note.txt' \
|
|
254
|
+
'local/generated-note.txt' \
|
|
255
|
+
> "$fixture/.automation-worktree-allowlist"
|
|
256
|
+
printf '%s\n' 'operator local edit' > "$fixture/local/operator-note.txt"
|
|
257
|
+
printf '%s\n' 'generated local state' > "$fixture/local/generated-note.txt"
|
|
258
|
+
run_fixture ./scripts/automation/preflight.sh --source >/dev/null
|
|
259
|
+
allowlisted_changes="$(run_fixture bash -c 'source ./scripts/automation/lib.sh; automation_changed_paths')"
|
|
260
|
+
[[ -z "$allowlisted_changes" ]] || fail "allowlisted paths remained visible: $allowlisted_changes"
|
|
261
|
+
|
|
262
|
+
printf '%s\n' \
|
|
263
|
+
'local/operator-note.txt' \
|
|
264
|
+
'local/generated-note.txt' \
|
|
265
|
+
'local/rename-target.txt' \
|
|
266
|
+
> "$fixture/.automation-worktree-allowlist"
|
|
267
|
+
git -C "$fixture" mv local/rename-source.txt local/rename-target.txt
|
|
268
|
+
rename_changes="$(run_fixture bash -c 'source ./scripts/automation/lib.sh; automation_changed_paths')"
|
|
269
|
+
[[ "$rename_changes" == "local/rename-source.txt" ]] || \
|
|
270
|
+
fail "an allowlisted rename target hid its non-allowlisted source: $rename_changes"
|
|
271
|
+
git -C "$fixture" mv local/rename-target.txt local/rename-source.txt
|
|
272
|
+
printf '%s\n' \
|
|
273
|
+
'# Exact repository-relative paths intentionally kept local' \
|
|
274
|
+
'local/operator-note.txt' \
|
|
275
|
+
'local/generated-note.txt' \
|
|
276
|
+
> "$fixture/.automation-worktree-allowlist"
|
|
277
|
+
pass 'exact tracked and untracked entries are ignored without hiding rename sources'
|
|
278
|
+
|
|
279
|
+
printf '%s\n' 'must still block' > "$fixture/unapproved-change.txt"
|
|
280
|
+
if run_fixture ./scripts/automation/preflight.sh --source >/dev/null 2>&1; then
|
|
281
|
+
fail 'preflight ignored a path that was not allowlisted'
|
|
282
|
+
fi
|
|
283
|
+
rm "$fixture/unapproved-change.txt"
|
|
284
|
+
pass 'a non-allowlisted worktree change still blocks orchestration startup'
|
|
285
|
+
|
|
286
|
+
printf '%s\n' 'automation/config.json' > "$fixture/.automation-worktree-allowlist"
|
|
287
|
+
if run_fixture bash -c 'source ./scripts/automation/lib.sh; automation_changed_paths >/dev/null' 2>/dev/null; then
|
|
288
|
+
fail 'worktree allowlist accepted a protected automation path'
|
|
289
|
+
fi
|
|
290
|
+
printf '%s\n' 'local/*.json' > "$fixture/.automation-worktree-allowlist"
|
|
291
|
+
if run_fixture bash -c 'source ./scripts/automation/lib.sh; automation_changed_paths >/dev/null' 2>/dev/null; then
|
|
292
|
+
fail 'worktree allowlist accepted a glob pattern'
|
|
293
|
+
fi
|
|
294
|
+
printf '%s\n' 'local' > "$fixture/.automation-worktree-allowlist"
|
|
295
|
+
if run_fixture bash -c 'source ./scripts/automation/lib.sh; automation_changed_paths >/dev/null' 2>/dev/null; then
|
|
296
|
+
fail 'worktree allowlist accepted a directory path'
|
|
297
|
+
fi
|
|
298
|
+
printf '%s\n' 'local/operator-note.txt' 'local/operator-note.txt' > "$fixture/.automation-worktree-allowlist"
|
|
299
|
+
if run_fixture bash -c 'source ./scripts/automation/lib.sh; automation_changed_paths >/dev/null' 2>/dev/null; then
|
|
300
|
+
fail 'worktree allowlist accepted duplicate entries'
|
|
301
|
+
fi
|
|
302
|
+
printf '%s\n' '../outside.txt' > "$fixture/.automation-worktree-allowlist"
|
|
303
|
+
if run_fixture bash -c 'source ./scripts/automation/lib.sh; automation_changed_paths >/dev/null' 2>/dev/null; then
|
|
304
|
+
fail 'worktree allowlist accepted a path outside the repository'
|
|
305
|
+
fi
|
|
306
|
+
rm "$fixture/.automation-worktree-allowlist"
|
|
307
|
+
ln -s local/operator-note.txt "$fixture/.automation-worktree-allowlist"
|
|
308
|
+
if run_fixture bash -c 'source ./scripts/automation/lib.sh; automation_changed_paths >/dev/null' 2>/dev/null; then
|
|
309
|
+
fail 'worktree allowlist accepted a symbolic-link control file'
|
|
310
|
+
fi
|
|
311
|
+
rm "$fixture/.automation-worktree-allowlist"
|
|
312
|
+
printf '%s\n' \
|
|
313
|
+
'# Exact repository-relative paths intentionally kept local' \
|
|
314
|
+
'local/operator-note.txt' \
|
|
315
|
+
'local/generated-note.txt' \
|
|
316
|
+
> "$fixture/.automation-worktree-allowlist"
|
|
317
|
+
pass 'invalid, duplicate, symbolic-link, patterned, and protected allowlist input fails closed'
|
|
318
|
+
|
|
235
319
|
run_fixture ./scripts/automation/validate-contract.sh TASK-TEST-001 >/dev/null
|
|
236
320
|
pass 'valid version-3 configuration and task contract are accepted'
|
|
237
321
|
|
|
@@ -273,11 +357,17 @@ pass 'illegal state transition is rejected'
|
|
|
273
357
|
run_fixture ./scripts/automation/claim-task.sh TASK-TEST-001 >/dev/null
|
|
274
358
|
[[ "$(jq -r '.state' "$runtime_root/state/TASK-TEST-001.json")" == "CODING" ]] || fail 'claim did not create CODING state'
|
|
275
359
|
[[ -f "$runtime_root/evidence/TASK-TEST-001/baseline.json" ]] || fail 'baseline metadata missing'
|
|
360
|
+
jq -e \
|
|
361
|
+
'.command == ["./gradlew", "testDebugUnitTest"]' \
|
|
362
|
+
"$runtime_root/evidence/TASK-TEST-001/baseline.json" >/dev/null || fail 'baseline command does not match the configured task matrix'
|
|
276
363
|
pass 'claim verifies workspace identity and records a green baseline'
|
|
277
364
|
|
|
278
365
|
printf '%s\n' 'class GreetingTest { fun expectedBehavior() = Unit }' > "$fixture/mobile-client/src/test/java/dev/example/orchestratorfixture/GreetingTest.kt"
|
|
279
366
|
run_fixture ./scripts/automation/record-red.sh TASK-TEST-001 'expected missing behavior' -- dev.example.orchestratorfixture.GreetingTest >/dev/null
|
|
280
367
|
[[ "$(jq -r '.exitCode' "$runtime_root/evidence/TASK-TEST-001/red.json")" -ne 0 ]] || fail 'RED evidence exit code was not captured'
|
|
368
|
+
jq -e \
|
|
369
|
+
'.command == ["./gradlew", "testDebugUnitTest", "--tests", "dev.example.orchestratorfixture.GreetingTest"]' \
|
|
370
|
+
"$runtime_root/evidence/TASK-TEST-001/red.json" >/dev/null || fail 'RED command does not bind the configured task and filter'
|
|
281
371
|
pass 'focused failing test records genuine RED evidence'
|
|
282
372
|
|
|
283
373
|
printf '%s\n' 'unsafe' > "$fixture/automation/forbidden-change.txt"
|
|
@@ -338,7 +428,7 @@ jq \
|
|
|
338
428
|
'.id = "TASK-TEST-002" |
|
|
339
429
|
.title = "Exercise the bounded verification failure loop" |
|
|
340
430
|
.planPath = "docs/plans/TASK-TEST-002.md" |
|
|
341
|
-
.targetTests = ["dev.example.orchestratorfixture.FailureLoopTest"]' \
|
|
431
|
+
.targetTests = [{gradleTask: "testDebugUnitTest", filter: "dev.example.orchestratorfixture.FailureLoopTest"}]' \
|
|
342
432
|
"$fixture/automation/tasks/TASK-TEST-001.json" \
|
|
343
433
|
> "$fixture/automation/tasks/TASK-TEST-002.json"
|
|
344
434
|
(
|
|
@@ -375,7 +465,7 @@ jq \
|
|
|
375
465
|
'.id = "TASK-TEST-003" |
|
|
376
466
|
.title = "Verify dirty worktree preflight blocking" |
|
|
377
467
|
.planPath = "docs/plans/TASK-TEST-003.md" |
|
|
378
|
-
.targetTests = ["dev.example.orchestratorfixture.PreflightBlockTest"]' \
|
|
468
|
+
.targetTests = [{gradleTask: "testDebugUnitTest", filter: "dev.example.orchestratorfixture.PreflightBlockTest"}]' \
|
|
379
469
|
"$fixture/automation/tasks/TASK-TEST-001.json" \
|
|
380
470
|
> "$fixture/automation/tasks/TASK-TEST-003.json"
|
|
381
471
|
(
|
|
@@ -402,7 +492,7 @@ jq \
|
|
|
402
492
|
'.id = "TASK-TEST-005" |
|
|
403
493
|
.title = "Exercise the bounded reviewer repair cycle" |
|
|
404
494
|
.planPath = "docs/plans/TASK-TEST-005.md" |
|
|
405
|
-
.targetTests = ["dev.example.orchestratorfixture.ReviewerFixTest"]' \
|
|
495
|
+
.targetTests = [{gradleTask: "testDebugUnitTest", filter: "dev.example.orchestratorfixture.ReviewerFixTest"}]' \
|
|
406
496
|
"$fixture/automation/tasks/TASK-TEST-001.json" \
|
|
407
497
|
> "$fixture/automation/tasks/TASK-TEST-005.json"
|
|
408
498
|
(
|
|
@@ -452,7 +542,7 @@ jq \
|
|
|
452
542
|
'.id = "TASK-TEST-004" |
|
|
453
543
|
.title = "Exercise in-place transactional integration" |
|
|
454
544
|
.planPath = "docs/plans/TASK-TEST-004.md" |
|
|
455
|
-
.targetTests = ["dev.example.orchestratorfixture.OrchestratedFlowTest"]' \
|
|
545
|
+
.targetTests = [{gradleTask: "testDebugUnitTest", filter: "dev.example.orchestratorfixture.OrchestratedFlowTest"}]' \
|
|
456
546
|
"$fixture/automation/tasks/TASK-TEST-001.json" \
|
|
457
547
|
> "$fixture/automation/tasks/TASK-TEST-004.json"
|
|
458
548
|
|
|
@@ -483,6 +573,8 @@ approved_baseline="$(jq -er '.baselineHead' "$runtime_root/workspaces/TASK-TEST-
|
|
|
483
573
|
leased_status="$(run_fixture ./scripts/automation/status.sh TASK-TEST-004)"
|
|
484
574
|
[[ "$(jq -r '.runtime.repositoryLeaseMatches' <<< "$leased_status")" == "true" ]] || fail 'status did not verify the repository lease'
|
|
485
575
|
[[ "$(jq -r '.runtime.originalBranchDrifted' <<< "$leased_status")" == "false" ]] || fail 'status reported false branch drift'
|
|
576
|
+
[[ "$(jq -c '.runtime.effectiveWorktreeAllowlist' <<< "$leased_status")" == '["local/operator-note.txt","local/generated-note.txt"]' ]] || \
|
|
577
|
+
fail 'status did not expose the approved in-place allowlist snapshot'
|
|
486
578
|
if run_fixture bash -c 'source ./scripts/automation/lib.sh; automation_acquire_repository_lease TASK-TEST-999 "$PWD" inPlaceExclusive' >/dev/null 2>&1; then
|
|
487
579
|
fail 'a second task acquired the persistent repository lease'
|
|
488
580
|
fi
|
|
@@ -491,6 +583,10 @@ pass 'persistent repository lease rejects a concurrent automation task'
|
|
|
491
583
|
[[ "$(jq -r '.state' "$runtime_root/state/TASK-TEST-004.json")" == "PENDING" ]] || fail 'prepared task did not become PENDING'
|
|
492
584
|
pass 'contract approval switches to a leased task branch while deferring the sealed plan and contract to the product commit'
|
|
493
585
|
|
|
586
|
+
git -C "$fixture" add -- \
|
|
587
|
+
.automation-worktree-allowlist \
|
|
588
|
+
local/operator-note.txt \
|
|
589
|
+
local/generated-note.txt
|
|
494
590
|
run_task "$task_root" ./scripts/automation/claim-task.sh TASK-TEST-004 >/dev/null
|
|
495
591
|
printf '%s\n' 'class OrchestratedFlowTest { fun expectedBehavior() = Unit }' > "$task_root/mobile-client/src/test/java/dev/example/orchestratorfixture/OrchestratedFlowTest.kt"
|
|
496
592
|
run_task "$task_root" ./scripts/automation/record-red.sh TASK-TEST-004 'expected missing behavior' -- dev.example.orchestratorfixture.OrchestratedFlowTest >/dev/null
|
|
@@ -500,6 +596,15 @@ run_task "$task_root" ./scripts/automation/begin-review.sh TASK-TEST-004 >/dev/n
|
|
|
500
596
|
run_task "$task_root" env AUTOMATION_FAKE_GREEN=1 ./scripts/automation/submit-review.sh TASK-TEST-004 APPROVED 'Fresh review confirms the sealed behavior and scope.' >/dev/null
|
|
501
597
|
run_task "$task_root" ./scripts/automation/acceptance-report.sh TASK-TEST-004 >/dev/null
|
|
502
598
|
[[ "$(jq -r '.changedPaths | length' "$runtime_root/evidence/TASK-TEST-004/acceptance-report.json")" -eq 2 ]] || fail 'acceptance package omitted product paths'
|
|
599
|
+
if jq -e '.changedPaths | any(. == "local/operator-note.txt" or . == "local/generated-note.txt")' \
|
|
600
|
+
"$runtime_root/evidence/TASK-TEST-004/acceptance-report.json" >/dev/null; then
|
|
601
|
+
fail 'acceptance package included an allowlisted local path'
|
|
602
|
+
fi
|
|
603
|
+
if rg -F 'diff --git a/local/operator-note.txt b/local/operator-note.txt' "$runtime_root/evidence/TASK-TEST-004/sealed.diff" >/dev/null || \
|
|
604
|
+
rg -F 'diff --git a/local/generated-note.txt b/local/generated-note.txt' "$runtime_root/evidence/TASK-TEST-004/sealed.diff" >/dev/null || \
|
|
605
|
+
rg -F 'diff --git a/.automation-worktree-allowlist b/.automation-worktree-allowlist' "$runtime_root/evidence/TASK-TEST-004/sealed.diff" >/dev/null; then
|
|
606
|
+
fail 'sealed diff included allowlisted local content'
|
|
607
|
+
fi
|
|
503
608
|
[[ "$(jq -r '.evidence.qualityGate' "$runtime_root/evidence/TASK-TEST-004/acceptance-report.json")" == "PASSED" ]] || fail 'acceptance package omitted quality-gate status'
|
|
504
609
|
acceptance_card="$(run_fixture ./scripts/automation/show-acceptance-review.sh TASK-TEST-004)"
|
|
505
610
|
[[ "$acceptance_card" == *"人工验收提醒"* ]] || fail 'acceptance review did not actively identify the human gate'
|
|
@@ -538,6 +643,27 @@ for combined_path in \
|
|
|
538
643
|
awk -v expected="$combined_path" '$0 == expected { found = 1 } END { exit !found }' || \
|
|
539
644
|
fail "combined task commit omitted $combined_path"
|
|
540
645
|
done
|
|
646
|
+
if git -C "$fixture" diff-tree --no-commit-id --name-only -r "$combined_commit" | \
|
|
647
|
+
rg -x 'local/(operator-note|generated-note)\.txt|\.automation-worktree-allowlist' >/dev/null; then
|
|
648
|
+
fail 'combined task commit included an allowlisted local path or its control file'
|
|
649
|
+
fi
|
|
650
|
+
[[ "$(cat "$fixture/local/operator-note.txt")" == "operator local edit" ]] || fail 'tracked allowlisted content changed during integration'
|
|
651
|
+
[[ "$(cat "$fixture/local/generated-note.txt")" == "generated local state" ]] || fail 'untracked allowlisted content changed during integration'
|
|
652
|
+
staged_allowlist_paths="$(git -C "$fixture" diff --cached --name-only HEAD -- | LC_ALL=C sort)"
|
|
653
|
+
for staged_allowlist_path in \
|
|
654
|
+
.automation-worktree-allowlist \
|
|
655
|
+
local/generated-note.txt \
|
|
656
|
+
local/operator-note.txt; do
|
|
657
|
+
printf '%s\n' "$staged_allowlist_paths" | \
|
|
658
|
+
awk -v expected="$staged_allowlist_path" '$0 == expected { found = 1 } END { exit !found }' || \
|
|
659
|
+
fail "integration consumed a staged allowlisted path: $staged_allowlist_path"
|
|
660
|
+
done
|
|
661
|
+
run_fixture bash -c 'source ./scripts/automation/lib.sh; automation_worktree_is_clean' || fail 'allowlisted local state made the integrated source root dirty'
|
|
662
|
+
pass 'in-place integration preserves allowlisted local state without evidence or commit leakage'
|
|
663
|
+
git -C "$fixture" restore --staged -- \
|
|
664
|
+
.automation-worktree-allowlist \
|
|
665
|
+
local/operator-note.txt \
|
|
666
|
+
local/generated-note.txt
|
|
541
667
|
[[ "$(jq -r '.contractCommit' "$runtime_root/evidence/TASK-TEST-004/origin.json")" == "$combined_commit" ]] || fail 'origin evidence did not bind the contract to the combined task commit'
|
|
542
668
|
[[ "$(jq -r '.pushed' "$runtime_root/evidence/TASK-TEST-004/integration.json")" == "false" ]] || fail 'integration evidence did not forbid push'
|
|
543
669
|
[[ "$(jq -r '.method' "$runtime_root/evidence/TASK-TEST-004/integration.json")" == "inPlaceExclusive-fast-forward" ]] || fail 'integration did not use the in-place fast-forward path'
|
|
@@ -557,11 +683,15 @@ jq \
|
|
|
557
683
|
'.id = "TASK-TEST-008" |
|
|
558
684
|
.title = "Archive an interrupted in-place task" |
|
|
559
685
|
.planPath = "docs/plans/TASK-TEST-008.md" |
|
|
560
|
-
.targetTests = ["dev.example.orchestratorfixture.AbortArchiveTest"]' \
|
|
686
|
+
.targetTests = [{gradleTask: "testDebugUnitTest", filter: "dev.example.orchestratorfixture.AbortArchiveTest"}]' \
|
|
561
687
|
"$fixture/automation/tasks/TASK-TEST-001.json" \
|
|
562
688
|
> "$fixture/automation/tasks/TASK-TEST-008.json"
|
|
563
689
|
run_fixture ./scripts/automation/prepare-contract-review.sh TASK-TEST-008 '批准方案,生成计划和任务合同。' >/dev/null
|
|
564
690
|
run_fixture env AUTOMATION_SKIP_AGENT_RUN=1 ./scripts/automation/approve-and-run.sh TASK-TEST-008 '合同已复核,批准自动执行到人工验收阶段。' >/dev/null
|
|
691
|
+
git -C "$fixture" add -- \
|
|
692
|
+
.automation-worktree-allowlist \
|
|
693
|
+
local/operator-note.txt \
|
|
694
|
+
local/generated-note.txt
|
|
565
695
|
printf '%s\n' 'class AbortArchive { fun value() = "preserved" }' > "$fixture/mobile-client/src/main/java/dev/example/orchestratorfixture/AbortArchive.kt"
|
|
566
696
|
printf '%s\n' 'must not be archived automatically' > "$fixture/out-of-contract.txt"
|
|
567
697
|
if run_fixture ./scripts/automation/abort-task.sh TASK-TEST-008 '中止任务,封存修改并恢复原分支。' >/dev/null 2>&1; then
|
|
@@ -575,10 +705,27 @@ abort_recovery_commit="$(jq -er '.recoveryCommit' "$runtime_root/evidence/TASK-T
|
|
|
575
705
|
git -C "$fixture" cat-file -e "$abort_recovery_commit:mobile-client/src/main/java/dev/example/orchestratorfixture/AbortArchive.kt" || fail 'abort recovery commit omitted the allowed change'
|
|
576
706
|
git -C "$fixture" cat-file -e "$abort_recovery_commit:docs/plans/TASK-TEST-008.md" || fail 'abort recovery commit omitted the sealed plan'
|
|
577
707
|
git -C "$fixture" cat-file -e "$abort_recovery_commit:automation/tasks/TASK-TEST-008.json" || fail 'abort recovery commit omitted the sealed contract'
|
|
708
|
+
if git -C "$fixture" diff-tree --no-commit-id --name-only -r "$abort_recovery_commit" | \
|
|
709
|
+
rg -x 'local/(operator-note|generated-note)\.txt|\.automation-worktree-allowlist' >/dev/null; then
|
|
710
|
+
fail 'abort recovery commit included an allowlisted local path or its control file'
|
|
711
|
+
fi
|
|
712
|
+
staged_abort_allowlist_paths="$(git -C "$fixture" diff --cached --name-only HEAD -- | LC_ALL=C sort)"
|
|
713
|
+
for staged_allowlist_path in \
|
|
714
|
+
.automation-worktree-allowlist \
|
|
715
|
+
local/generated-note.txt \
|
|
716
|
+
local/operator-note.txt; do
|
|
717
|
+
printf '%s\n' "$staged_abort_allowlist_paths" | \
|
|
718
|
+
awk -v expected="$staged_allowlist_path" '$0 == expected { found = 1 } END { exit !found }' || \
|
|
719
|
+
fail "abort consumed a staged allowlisted path: $staged_allowlist_path"
|
|
720
|
+
done
|
|
578
721
|
[[ ! -f "$fixture/mobile-client/src/main/java/dev/example/orchestratorfixture/AbortArchive.kt" ]] || fail 'abort left archived code on the original branch'
|
|
579
722
|
[[ "$(jq -r '.state' "$runtime_root/state/TASK-TEST-008.json")" == "ABORTED" ]] || fail 'allowed abort did not reach ABORTED'
|
|
580
723
|
[[ ! -d "$runtime_root/locks/repository.workspace.lease" ]] || fail 'allowed abort did not release the repository lease'
|
|
581
724
|
pass 'abort refuses unrelated files and archives allowed uncommitted changes in a recovery commit'
|
|
725
|
+
git -C "$fixture" restore --staged -- \
|
|
726
|
+
.automation-worktree-allowlist \
|
|
727
|
+
local/operator-note.txt \
|
|
728
|
+
local/generated-note.txt
|
|
582
729
|
|
|
583
730
|
planning_only_baseline="$(git -C "$fixture" rev-parse HEAD)"
|
|
584
731
|
printf '%s\n' '# Planning-only abort plan' > "$fixture/docs/plans/TASK-TEST-009.md"
|
|
@@ -586,7 +733,7 @@ jq \
|
|
|
586
733
|
'.id = "TASK-TEST-009" |
|
|
587
734
|
.title = "Abort before product editing" |
|
|
588
735
|
.planPath = "docs/plans/TASK-TEST-009.md" |
|
|
589
|
-
.targetTests = ["dev.example.orchestratorfixture.PlanningOnlyAbortTest"]' \
|
|
736
|
+
.targetTests = [{gradleTask: "testDebugUnitTest", filter: "dev.example.orchestratorfixture.PlanningOnlyAbortTest"}]' \
|
|
590
737
|
"$fixture/automation/tasks/TASK-TEST-001.json" \
|
|
591
738
|
> "$fixture/automation/tasks/TASK-TEST-009.json"
|
|
592
739
|
run_fixture ./scripts/automation/prepare-contract-review.sh TASK-TEST-009 '批准方案,生成计划和任务合同。' >/dev/null
|
|
@@ -611,16 +758,24 @@ jq \
|
|
|
611
758
|
'.id = "TASK-TEST-007" |
|
|
612
759
|
.title = "Keep optional isolated workspace support" |
|
|
613
760
|
.planPath = "docs/plans/TASK-TEST-007.md" |
|
|
614
|
-
.targetTests = ["dev.example.orchestratorfixture.IsolatedFlowTest"]' \
|
|
761
|
+
.targetTests = [{gradleTask: "testDebugUnitTest", filter: "dev.example.orchestratorfixture.IsolatedFlowTest"}]' \
|
|
615
762
|
"$fixture/automation/tasks/TASK-TEST-001.json" \
|
|
616
763
|
> "$fixture/automation/tasks/TASK-TEST-007.json"
|
|
617
764
|
run_fixture ./scripts/automation/prepare-contract-review.sh TASK-TEST-007 '批准方案,生成计划和任务合同。' >/dev/null
|
|
618
765
|
run_fixture env AUTOMATION_SKIP_AGENT_RUN=1 ./scripts/automation/approve-and-run.sh TASK-TEST-007 '合同已复核,批准自动执行到人工验收阶段。' >/dev/null
|
|
619
766
|
isolated_task_root="$(jq -er '.taskRoot' "$runtime_root/workspaces/TASK-TEST-007.json")"
|
|
620
767
|
[[ "$isolated_task_root" != "$fixture" && -d "$isolated_task_root" ]] || fail 'optional isolated strategy did not create a separate task root'
|
|
768
|
+
[[ "$(jq -r '.worktreeAllowlist | length' "$runtime_root/workspaces/TASK-TEST-007.json")" -eq 2 ]] || fail 'isolated workspace did not retain the approved allowlist snapshot'
|
|
621
769
|
[[ -f "$isolated_task_root/docs/plans/TASK-TEST-007.md" && -f "$isolated_task_root/automation/tasks/TASK-TEST-007.json" ]] || fail 'isolated task root did not receive the sealed planning artifacts'
|
|
622
770
|
[[ ! -e "$fixture/docs/plans/TASK-TEST-007.md" && ! -e "$fixture/automation/tasks/TASK-TEST-007.json" ]] || fail 'isolated preparation left duplicate planning artifacts in the source root'
|
|
623
771
|
[[ "$(git -C "$fixture" worktree list --porcelain | awk '/^worktree / { count++ } END { print count + 0 }')" -eq 2 ]] || fail 'isolated strategy created more than one additional worktree'
|
|
772
|
+
isolated_status="$(run_task "$isolated_task_root" ./scripts/automation/status.sh TASK-TEST-007)"
|
|
773
|
+
[[ "$(jq -c '.runtime.effectiveWorktreeAllowlist' <<< "$isolated_status")" == '[]' ]] || \
|
|
774
|
+
fail 'status applied the source allowlist inside an isolated task root'
|
|
775
|
+
printf '%s\n' 'task-local edit must stay visible' > "$isolated_task_root/local/operator-note.txt"
|
|
776
|
+
isolated_visible_paths="$(run_task "$isolated_task_root" bash -c 'source ./scripts/automation/lib.sh; automation_changed_paths')"
|
|
777
|
+
printf '%s\n' "$isolated_visible_paths" | rg -x 'local/operator-note.txt' >/dev/null || fail 'source allowlist hid a task-local isolated-worktree edit'
|
|
778
|
+
git -C "$isolated_task_root" restore -- local/operator-note.txt
|
|
624
779
|
run_task "$isolated_task_root" ./scripts/automation/claim-task.sh TASK-TEST-007 >/dev/null
|
|
625
780
|
printf '%s\n' 'class IsolatedFlowTest { fun expectedBehavior() = Unit }' > "$isolated_task_root/mobile-client/src/test/java/dev/example/orchestratorfixture/IsolatedFlowTest.kt"
|
|
626
781
|
run_task "$isolated_task_root" ./scripts/automation/record-red.sh TASK-TEST-007 'expected missing behavior' -- dev.example.orchestratorfixture.IsolatedFlowTest >/dev/null
|
|
@@ -654,7 +809,7 @@ jq \
|
|
|
654
809
|
'.id = "TASK-TEST-006" |
|
|
655
810
|
.title = "Block an advanced original branch" |
|
|
656
811
|
.planPath = "docs/plans/TASK-TEST-006.md" |
|
|
657
|
-
.targetTests = ["dev.example.orchestratorfixture.AdvancedOriginalTest"]' \
|
|
812
|
+
.targetTests = [{gradleTask: "testDebugUnitTest", filter: "dev.example.orchestratorfixture.AdvancedOriginalTest"}]' \
|
|
658
813
|
"$fixture/automation/tasks/TASK-TEST-001.json" \
|
|
659
814
|
> "$fixture/automation/tasks/TASK-TEST-006.json"
|
|
660
815
|
run_fixture ./scripts/automation/prepare-contract-review.sh TASK-TEST-006 '批准方案,生成计划和任务合同。' >/dev/null
|
|
@@ -13,6 +13,7 @@ input="${1:-}"
|
|
|
13
13
|
[[ -n "$input" ]] || { usage; exit 2; }
|
|
14
14
|
automation_require_command jq
|
|
15
15
|
automation_require_layout
|
|
16
|
+
automation_validate_config
|
|
16
17
|
|
|
17
18
|
if [[ "$input" == */* || "$input" == *.json || "$input" == *.example ]]; then
|
|
18
19
|
contract="$input"
|
|
@@ -39,7 +40,12 @@ jq -e '
|
|
|
39
40
|
(.allowedSuperpowers == ["test-driven-development", "systematic-debugging", "verification-before-completion"]) and
|
|
40
41
|
(.acceptanceCriteria | type == "array" and length > 0) and
|
|
41
42
|
(.nonGoals | type == "array" and length > 0) and
|
|
42
|
-
(.targetTests | type == "array" and length > 0
|
|
43
|
+
(.targetTests | type == "array" and length > 0 and all(.[];
|
|
44
|
+
type == "object" and
|
|
45
|
+
keys == ["filter", "gradleTask"] and
|
|
46
|
+
(.gradleTask | type == "string" and test("^(?:[A-Za-z][A-Za-z0-9_.-]*|(?::[A-Za-z0-9_.-]+)+)$")) and
|
|
47
|
+
(.filter | type == "string" and test("^[A-Za-z0-9_.#$*-]+$")))) and
|
|
48
|
+
([.targetTests[].filter] | length == (unique | length)) and
|
|
43
49
|
(.deviceTestsRequired | type == "boolean") and
|
|
44
50
|
(.testPolicy == "required" or .testPolicy == "not-required") and
|
|
45
51
|
(if .testPolicy == "not-required" then (.testPolicyReason | type == "string" and length >= 20) else true end)
|
|
@@ -68,10 +74,15 @@ while IFS= read -r protected; do
|
|
|
68
74
|
fi
|
|
69
75
|
done < <(jq -r '.protectedPaths[]' "$AUTOMATION_CONFIG")
|
|
70
76
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
77
|
+
while IFS=$'\t' read -r gradle_task filter; do
|
|
78
|
+
automation_validate_gradle_task "$gradle_task"
|
|
79
|
+
automation_validate_test_filter "$filter"
|
|
80
|
+
jq -e \
|
|
81
|
+
--arg task "$gradle_task" \
|
|
82
|
+
'.gradleVerification.focusedTestTasks | index($task) != null' \
|
|
83
|
+
"$AUTOMATION_CONFIG" >/dev/null || \
|
|
84
|
+
automation_die "target test Gradle task is not allowed by automation/config.json: $gradle_task"
|
|
85
|
+
done < <(jq -r '.targetTests[] | [.gradleTask, .filter] | @tsv' "$contract")
|
|
75
86
|
|
|
76
87
|
if rg -n -i 'replace with|TASK-EXAMPLE|todo|tbd|placeholder' "$contract" >/dev/null; then
|
|
77
88
|
automation_die "contract still contains template placeholders"
|
|
@@ -12,26 +12,27 @@ head="${3:-}"
|
|
|
12
12
|
automation_validate_task_id "$task_id"
|
|
13
13
|
[[ "$(automation_read_state "$task_id")" == "INTEGRATING" ]] || automation_die "$task_id is not INTEGRATING"
|
|
14
14
|
contract="$(automation_contract_path "$task_id")"
|
|
15
|
+
"$SCRIPT_DIR/validate-contract.sh" "$task_id" >/dev/null
|
|
15
16
|
|
|
16
17
|
"$SCRIPT_DIR/integration-scope-gate.sh" "$task_id" "$base" "$head"
|
|
17
18
|
|
|
18
|
-
while IFS
|
|
19
|
-
automation_info "running integration focused test: $filter"
|
|
20
|
-
|
|
21
|
-
done < <(jq -r '.targetTests[]' "$contract")
|
|
19
|
+
while IFS=$'\t' read -r gradle_task filter; do
|
|
20
|
+
automation_info "running integration focused test ($gradle_task): $filter"
|
|
21
|
+
automation_run_focused_test "$gradle_task" "$filter" "$AUTOMATION_ROOT"
|
|
22
|
+
done < <(jq -r '.targetTests[] | [.gradleTask, .filter] | @tsv' "$contract")
|
|
22
23
|
|
|
23
24
|
automation_info "running integration full unit tests"
|
|
24
|
-
|
|
25
|
+
automation_run_gradle_group "fullUnitTestTasks" "$AUTOMATION_ROOT"
|
|
25
26
|
|
|
26
|
-
automation_info "
|
|
27
|
-
|
|
27
|
+
automation_info "running configured integration assemble tasks"
|
|
28
|
+
automation_run_gradle_group "assembleTasks" "$AUTOMATION_ROOT"
|
|
28
29
|
|
|
29
|
-
automation_info "running integration Android lint"
|
|
30
|
-
|
|
30
|
+
automation_info "running configured integration Android lint tasks"
|
|
31
|
+
automation_run_gradle_group "lintTasks" "$AUTOMATION_ROOT"
|
|
31
32
|
|
|
32
33
|
if [[ "$(jq -r '.deviceTestsRequired' "$contract")" == "true" ]]; then
|
|
33
|
-
automation_info "running integration
|
|
34
|
-
|
|
34
|
+
automation_info "running configured integration device tests"
|
|
35
|
+
automation_run_gradle_group "deviceTestTasks" "$AUTOMATION_ROOT"
|
|
35
36
|
fi
|
|
36
37
|
|
|
37
38
|
automation_info "integration candidate passed all deterministic verification"
|
|
@@ -17,38 +17,23 @@ evidence_dir="$(automation_evidence_path "$task_id")"
|
|
|
17
17
|
|
|
18
18
|
"$SCRIPT_DIR/scope-gate.sh" "$task_id"
|
|
19
19
|
|
|
20
|
-
while IFS
|
|
21
|
-
automation_info "running focused test: $filter"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
./gradlew testDebugUnitTest --tests "$filter"
|
|
25
|
-
)
|
|
26
|
-
done < <(jq -r '.targetTests[]' "$contract")
|
|
20
|
+
while IFS=$'\t' read -r gradle_task filter; do
|
|
21
|
+
automation_info "running focused test ($gradle_task): $filter"
|
|
22
|
+
automation_run_focused_test "$gradle_task" "$filter" "$AUTOMATION_ROOT"
|
|
23
|
+
done < <(jq -r '.targetTests[] | [.gradleTask, .filter] | @tsv' "$contract")
|
|
27
24
|
|
|
28
25
|
automation_info "running full unit tests"
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
automation_info "
|
|
35
|
-
|
|
36
|
-
cd "$AUTOMATION_ROOT"
|
|
37
|
-
./gradlew assembleDebug
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
automation_info "running Android lint"
|
|
41
|
-
(
|
|
42
|
-
cd "$AUTOMATION_ROOT"
|
|
43
|
-
./gradlew lint
|
|
44
|
-
)
|
|
26
|
+
automation_run_gradle_group "fullUnitTestTasks" "$AUTOMATION_ROOT"
|
|
27
|
+
|
|
28
|
+
automation_info "running configured assemble tasks"
|
|
29
|
+
automation_run_gradle_group "assembleTasks" "$AUTOMATION_ROOT"
|
|
30
|
+
|
|
31
|
+
automation_info "running configured Android lint tasks"
|
|
32
|
+
automation_run_gradle_group "lintTasks" "$AUTOMATION_ROOT"
|
|
45
33
|
|
|
46
34
|
if [[ "$(jq -r '.deviceTestsRequired' "$contract")" == "true" ]]; then
|
|
47
|
-
automation_info "running
|
|
48
|
-
|
|
49
|
-
cd "$AUTOMATION_ROOT"
|
|
50
|
-
./gradlew connectedDebugAndroidTest
|
|
51
|
-
)
|
|
35
|
+
automation_info "running configured device tests"
|
|
36
|
+
automation_run_gradle_group "deviceTestTasks" "$AUTOMATION_ROOT"
|
|
52
37
|
fi
|
|
53
38
|
|
|
54
39
|
automation_info "all deterministic verification commands passed"
|