@frankzhang2026/opencode-android-orchestrator 1.0.3 → 1.0.5
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 +20 -1
- package/README.md +8 -8
- package/dist/installer/adaptive-templates.d.ts +1 -1
- package/dist/installer/adaptive-templates.d.ts.map +1 -1
- package/dist/installer/adaptive-templates.js +11 -0
- package/dist/installer/adaptive-templates.js.map +1 -1
- package/dist/installer/opencode-config.d.ts +3 -3
- package/dist/installer/opencode-config.js +1 -1
- package/dist/queue/cli.js +1 -1
- package/dist/queue/cli.js.map +1 -1
- package/dist/queue/queue.d.ts.map +1 -1
- package/dist/queue/queue.js +57 -0
- package/dist/queue/queue.js.map +1 -1
- package/dist/queue/tools.js +1 -1
- package/dist/queue/tools.js.map +1 -1
- package/docs/MIGRATION.md +8 -8
- package/docs/QUEUE.md +35 -7
- package/docs/SECURITY.md +1 -1
- package/docs/TROUBLESHOOTING.md +8 -7
- package/package.json +1 -1
- package/templates/.opencode/agents/scheduled-coder.md +9 -0
- package/templates/.opencode/agents/scheduled-planner.md +14 -1
- package/templates/.opencode/skills/scheduled-quality-coder/SKILL.md +7 -1
- package/templates/.opencode/skills/scheduled-quality-orchestrator/SKILL.md +3 -0
- package/templates/.opencode/skills/scheduled-quality-reviewer/SKILL.md +4 -0
- package/templates/automation/task-contract.schema.json +76 -4
- package/templates/automation/tasks/TASK-TEMPLATE.json.example +25 -2
- package/templates/scripts/automation/acceptance-report.sh +12 -0
- package/templates/scripts/automation/lib.sh +108 -0
- package/templates/scripts/automation/record-red.sh +170 -32
- package/templates/scripts/automation/show-acceptance-review.sh +7 -0
- package/templates/scripts/automation/status.sh +6 -0
- package/templates/scripts/automation/validate-contract.sh +46 -2
- package/templates/scripts/automation/verify-task.sh +14 -0
package/docs/MIGRATION.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Migration guide
|
|
2
2
|
|
|
3
3
|
This guide covers migration to
|
|
4
|
-
`@frankzhang2026/opencode-android-orchestrator@1.0.
|
|
4
|
+
`@frankzhang2026/opencode-android-orchestrator@1.0.5`. Pin the exact version and
|
|
5
5
|
prove the migration in a disposable clone before changing a long-lived
|
|
6
6
|
repository.
|
|
7
7
|
|
|
@@ -9,10 +9,10 @@ repository.
|
|
|
9
9
|
|
|
10
10
|
| Current state | Correct command after release | Important distinction |
|
|
11
11
|
| --- | --- | --- |
|
|
12
|
-
| No orchestrator files or manifest | `npx @frankzhang2026/opencode-android-orchestrator@1.0.
|
|
12
|
+
| No orchestrator files or manifest | `npx @frankzhang2026/opencode-android-orchestrator@1.0.5 init .` | Normal new installation; all runtime-detected Android modules and registered debug verification tasks are discovered automatically. |
|
|
13
13
|
| Published `0.1.0` scaffold only | Remove any project-local `@0.1.0` plugin reference after review, then run `init`. | `0.1.0` did not create a usable managed installation and cannot be upgraded. |
|
|
14
|
-
| `0.2.0` through `0.10.0` manifest-managed installation with intact managed/backup content | Run the `1.0.
|
|
15
|
-
| Healthy `1.0.0` through `1.0.
|
|
14
|
+
| `0.2.0` through `0.10.0` manifest-managed installation with intact managed/backup content | Run the `1.0.5` `upgrade`; add `--refresh-gradle-discovery` when generated module/task lists are incomplete. | Refresh replaces all derived module metadata, source paths, protected build files, and task allowlists from one Gradle runtime snapshot. Module scope, operator policies, user-owned AGENTS content, and an existing commit-prefix sidecar remain preserved. |
|
|
15
|
+
| Healthy `1.0.0` through `1.0.4` installation | Stop the queue service, finish or abort retained workspaces, then run the fixed `1.0.5` `upgrade`. | Pending inbox contracts remain durable. Version 1.0.5 adds schema V3 contracts and rejects RED evidence containing preserved-behavior or undeclared failures; older approved contracts retain their legacy flow. |
|
|
16
16
|
| Manually copied V3 files, no `.automation-plugin/manifest.json` | Finish active tasks, preserve historical evidence separately, then run `init`. | Exact files can be reused; differing managed files fail as conflicts. |
|
|
17
17
|
| Healthy older manifest-managed installation | Run `doctor`, then the fixed target version's `upgrade`. | `upgrade` requires a valid installed manifest and intact original backups. |
|
|
18
18
|
| Healthy current-version manifest | Run `doctor`; repeated `init` or same-version `upgrade` is verification-only and byte-idempotent. | Do not reinstall or delete the manifest. |
|
|
@@ -58,7 +58,7 @@ installation of `0.1.0` alone does not require project-file cleanup.
|
|
|
58
58
|
After release, initialize with the fixed version:
|
|
59
59
|
|
|
60
60
|
```sh
|
|
61
|
-
npx @frankzhang2026/opencode-android-orchestrator@1.0.
|
|
61
|
+
npx @frankzhang2026/opencode-android-orchestrator@1.0.5 init .
|
|
62
62
|
```
|
|
63
63
|
|
|
64
64
|
New installations default to all-module scope, so multiple application modules
|
|
@@ -106,7 +106,7 @@ Use the lifecycle command selected by the active manifest:
|
|
|
106
106
|
|
|
107
107
|
```sh
|
|
108
108
|
npx --yes --registry=https://registry.npmjs.org/ \
|
|
109
|
-
@frankzhang2026/opencode-android-orchestrator@1.0.
|
|
109
|
+
@frankzhang2026/opencode-android-orchestrator@1.0.5 upgrade . --json
|
|
110
110
|
```
|
|
111
111
|
|
|
112
112
|
The command-level Registry option is useful when a company-wide npm Registry
|
|
@@ -136,7 +136,7 @@ computed includes dynamically or a company convention plugin applied
|
|
|
136
136
|
|
|
137
137
|
```sh
|
|
138
138
|
npx --yes --registry=https://registry.npmjs.org/ \
|
|
139
|
-
@frankzhang2026/opencode-android-orchestrator@1.0.
|
|
139
|
+
@frankzhang2026/opencode-android-orchestrator@1.0.5 upgrade . \
|
|
140
140
|
--refresh-gradle-discovery --json
|
|
141
141
|
```
|
|
142
142
|
|
|
@@ -220,7 +220,7 @@ use the queue rather than the old direct Shell commands. See [Queue operation](Q
|
|
|
220
220
|
Run all checks from the detected Git root:
|
|
221
221
|
|
|
222
222
|
```sh
|
|
223
|
-
npx @frankzhang2026/opencode-android-orchestrator@1.0.
|
|
223
|
+
npx @frankzhang2026/opencode-android-orchestrator@1.0.5 doctor .
|
|
224
224
|
opencode debug config
|
|
225
225
|
opencode debug skill
|
|
226
226
|
opencode debug agent scheduled-planner
|
package/docs/QUEUE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Queue and background execution
|
|
2
2
|
|
|
3
|
-
Version `1.0.
|
|
3
|
+
Version `1.0.5` stores proposals and approved contracts under
|
|
4
4
|
`<git-common-dir>/automation-runtime/inbox/queue.json`. A contract is runnable
|
|
5
5
|
only after its full plan, version, digest, target branch and commit policy are
|
|
6
6
|
approved and durably recorded. Planning reads a fixed `planningHead`, so another
|
|
@@ -51,9 +51,14 @@ rejected even if it copies the visible run ID.
|
|
|
51
51
|
|
|
52
52
|
## Service and scheduling
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
Each newly approved contract atomically clears the queue's pause flag when it
|
|
55
|
+
is enqueued, then starts or wakes the package-owned detached service. This
|
|
56
|
+
resumes the entire queue; existing tasks keep their normal priority/FIFO order,
|
|
57
|
+
deadlines and dependencies. Failed or duplicate enqueue requests do not clear
|
|
58
|
+
the pause flag, and enqueue never clears a fault or replaces an active executor.
|
|
59
|
+
Closing the Planner does not stop the service. Enqueue notifications, deadlines,
|
|
60
|
+
worker completion, startup recovery and periodic scans all use the same atomic
|
|
61
|
+
reservation logic.
|
|
57
62
|
Idle scans do not call a model. No launchd or external Scheduler is registered.
|
|
58
63
|
The machine must be awake; a restarted service scans overdue entries once
|
|
59
64
|
through normal arbitration. Recurring task-template generation is not exposed
|
|
@@ -80,7 +85,8 @@ opencode-android-orchestrator queue start .
|
|
|
80
85
|
An unsuccessful OpenCode agent process pauses consumption as a shared execution
|
|
81
86
|
fault; inspect its log and fix provider/environment failures before clearing it.
|
|
82
87
|
|
|
83
|
-
Pause stops new reservations
|
|
88
|
+
Pause stops new reservations until explicit resume or a new contract is
|
|
89
|
+
successfully enqueued. Stop terminates the scheduler while preserving
|
|
84
90
|
its active detached worker. Resume does not clear a fault. Notifications are
|
|
85
91
|
persisted until acknowledged, so the original Planner session need not remain
|
|
86
92
|
open. `queue --help` lists direct local-operator commands; interactive agents
|
|
@@ -95,8 +101,10 @@ opencode-android-orchestrator queue pause .
|
|
|
95
101
|
opencode-android-orchestrator queue policy . isolatedWorktree humanApproval
|
|
96
102
|
```
|
|
97
103
|
|
|
98
|
-
|
|
99
|
-
|
|
104
|
+
Avoid approving new contracts during this maintenance pause: a new enqueue
|
|
105
|
+
resumes queue consumption. Review and commit the changed
|
|
106
|
+
`automation/config.json` before resuming. The queued task fixes its workspace
|
|
107
|
+
strategy when claimed. Its commit authorization
|
|
100
108
|
remains exactly the approved choice: changing a default cannot grant autoCommit
|
|
101
109
|
to an older task. Direct configuration drift while a workspace is retained
|
|
102
110
|
blocks scheduling; restore the recorded strategy before recovery.
|
|
@@ -116,6 +124,26 @@ It does not run `clean` or rerun all dependency tasks. Coder, Reviewer and local
|
|
|
116
124
|
integration perform the configured full suite and build gates. Evidence records
|
|
117
125
|
fresh-test logs, configured tasks and elapsed seconds.
|
|
118
126
|
|
|
127
|
+
Newly generated contracts use task-contract schema V3. Every verification case
|
|
128
|
+
has a stable ID, a one-based acceptance-criterion reference, an evidence source,
|
|
129
|
+
an exact test identity, and a pre-change classification:
|
|
130
|
+
|
|
131
|
+
- `preserve` must pass before and after implementation;
|
|
132
|
+
- `change` must fail before implementation with the approved exception type and
|
|
133
|
+
optional message fragment, then pass afterwards;
|
|
134
|
+
- `observe` may record a previously uncertain boundary, but a failure is
|
|
135
|
+
accepted only when the contract declares its expected cause.
|
|
136
|
+
|
|
137
|
+
Before product code changes, `record-red.sh TASK-ID` forces fresh focused Test
|
|
138
|
+
execution and collects structured case results through a temporary Gradle init
|
|
139
|
+
script. Missing, duplicate, skipped or undeclared cases, build failures, and any
|
|
140
|
+
preserved behavior failure reject the preflight. Failed attempts remain under
|
|
141
|
+
the task evidence directory; only a completely valid attempt creates immutable
|
|
142
|
+
RED and test-manifest records. Those records bind the contract hash, execution
|
|
143
|
+
baseline and test-only diff, so changing a RED test afterwards invalidates the
|
|
144
|
+
quality gate. V1/V2 contracts already approved before upgrade retain the legacy
|
|
145
|
+
explicit failure-text command.
|
|
146
|
+
|
|
119
147
|
## Baselines and recovery
|
|
120
148
|
|
|
121
149
|
Before execution, changes to contract-relevant files or execution configuration
|
package/docs/SECURITY.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Security model
|
|
2
2
|
|
|
3
3
|
This document describes the security properties of
|
|
4
|
-
`@frankzhang2026/opencode-android-orchestrator@1.0.
|
|
4
|
+
`@frankzhang2026/opencode-android-orchestrator@1.0.5`. The lifecycle foundation
|
|
5
5
|
completed the real OpenCode `1.14.22` and `1.15.13` release matrix in `0.2.0`;
|
|
6
6
|
`1.0.0` retains that compatibility boundary.
|
|
7
7
|
|
package/docs/TROUBLESHOOTING.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Troubleshooting
|
|
2
2
|
|
|
3
3
|
Use this guide for
|
|
4
|
-
`@frankzhang2026/opencode-android-orchestrator@1.0.
|
|
4
|
+
`@frankzhang2026/opencode-android-orchestrator@1.0.5`.
|
|
5
5
|
|
|
6
6
|
## Start with read-only evidence
|
|
7
7
|
|
|
@@ -11,7 +11,7 @@ From the repository root, capture:
|
|
|
11
11
|
git status --short --branch
|
|
12
12
|
git rev-parse HEAD
|
|
13
13
|
opencode --version
|
|
14
|
-
npx @frankzhang2026/opencode-android-orchestrator@1.0.
|
|
14
|
+
npx @frankzhang2026/opencode-android-orchestrator@1.0.5 doctor . --json
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
If installation never completed, doctor will correctly report a missing or
|
|
@@ -39,9 +39,9 @@ command-scoped override:
|
|
|
39
39
|
|
|
40
40
|
```sh
|
|
41
41
|
npm --registry=https://registry.npmjs.org/ view \
|
|
42
|
-
@frankzhang2026/opencode-android-orchestrator@1.0.
|
|
42
|
+
@frankzhang2026/opencode-android-orchestrator@1.0.5 version
|
|
43
43
|
npx --yes --registry=https://registry.npmjs.org/ \
|
|
44
|
-
@frankzhang2026/opencode-android-orchestrator@1.0.
|
|
44
|
+
@frankzhang2026/opencode-android-orchestrator@1.0.5 upgrade . --json
|
|
45
45
|
```
|
|
46
46
|
|
|
47
47
|
This leaves the company's saved npm configuration unchanged. Use the option
|
|
@@ -71,7 +71,7 @@ Git-backed Superpowers plugin at runtime.
|
|
|
71
71
|
| Invalid `--long-command-timeout-ms` | The value is not an integer from `120000` through `7200000`. | Use the `1800000` ms default or pass an intentional bounded value to `init`/`upgrade`; do not edit the generated config directly. |
|
|
72
72
|
| Android SDK failure | No valid explicit SDK, `ANDROID_HOME`, `ANDROID_SDK_ROOT`, or `local.properties` `sdk.dir` was found. | Configure one real SDK root containing `platforms/` and `build-tools/`. Do not publish `local.properties`. |
|
|
73
73
|
| Missing `git`, `jq`, `rg`, `shasum`, or Java | Required deterministic command is unavailable on `PATH`. | Install or restore the missing command, record its version, and rerun the read-only checks. |
|
|
74
|
-
| `Bundled Orchestrator skill is unavailable` | The installed `1.0.
|
|
74
|
+
| `Bundled Orchestrator skill is unavailable` | The installed `1.0.5` package is incomplete, damaged, or loaded from an unsupported partial copy. | Reinstall the exact package, inspect its `resources/third-party/superpowers-v6.2.0/skills/` entries, restart OpenCode, and rerun `opencode debug skill`. Do not add an external Superpowers plugin as a fallback. |
|
|
75
75
|
| `current process does not own this task queue execution` immediately after Coder start on 1.0.1 | OpenCode created the tool shell in a separate process group, so 1.0.1 rejected a legitimate Worker descendant. | Upgrade to 1.0.2 or later, restart OpenCode, then use the approved resume or abort workflow for the retained task. Do not edit the queue or lease files. |
|
|
76
76
|
| The exact Superpowers v6.2.0 plugin remains after upgrade | That entry existed in the verified pre-install OpenCode file and is therefore user-owned. | Leave it in place or remove it as a separate reviewed configuration change. Upgrade only removes the old Orchestrator-managed entry. |
|
|
77
77
|
|
|
@@ -88,7 +88,7 @@ silence of `./gradlew tasks --all --console=plain | rg ...` in a large build.
|
|
|
88
88
|
For an existing installation, run:
|
|
89
89
|
|
|
90
90
|
```sh
|
|
91
|
-
npx @frankzhang2026/opencode-android-orchestrator@1.0.
|
|
91
|
+
npx @frankzhang2026/opencode-android-orchestrator@1.0.5 upgrade . \
|
|
92
92
|
--refresh-gradle-discovery
|
|
93
93
|
```
|
|
94
94
|
|
|
@@ -97,7 +97,7 @@ least `1800000` milliseconds. A higher timeout already supplied by the caller
|
|
|
97
97
|
is preserved; unrelated Bash commands are unchanged. To configure one hour,
|
|
98
98
|
run `upgrade . --long-command-timeout-ms 3600000` on a healthy installation.
|
|
99
99
|
If a command still reports `120000 ms`, confirm that the project manifest and
|
|
100
|
-
OpenCode plugin reference are both `1.0.
|
|
100
|
+
OpenCode plugin reference are both `1.0.5`, restart the OpenCode session so the
|
|
101
101
|
plugin reloads, and rerun doctor before attempting recovery.
|
|
102
102
|
|
|
103
103
|
After installation, inspect OpenCode discovery separately:
|
|
@@ -246,6 +246,7 @@ session or a missing notification is not evidence that a task never started.
|
|
|
246
246
|
| Symptom | Recovery |
|
|
247
247
|
| --- | --- |
|
|
248
248
|
| Fresh matching question receipt required | Show a new queue review/draft question and select it in the same Planner session; do not paste its approval label into chat. |
|
|
249
|
+
| Service is running but the queue is paused | Use `queue resume .` to run existing approvals. A newly approved contract also resumes the queue when enqueued; duplicate or rejected enqueue requests leave the pause unchanged. |
|
|
249
250
|
| Waiting for human confirmation or fixed workspace | Accept the current candidate or use the approved abort workflow; further contracts may still be enqueued. |
|
|
250
251
|
| Isolated capacity reached | Integrate or explicitly archive retained workspaces; do not delete failed work simply to advance the queue. |
|
|
251
252
|
| Execution launch ownership unknown | Stop the recorded launcher, prove it exited, then use `queue recover-execution .`; preserve any partial workspace. |
|
package/package.json
CHANGED
|
@@ -109,6 +109,15 @@ literally. Do not infer missing requirements and do not ask questions during a
|
|
|
109
109
|
non-interactive run. If anything is ambiguous or blocked, stop and report the exact
|
|
110
110
|
reason; the deterministic scripts own state transitions.
|
|
111
111
|
|
|
112
|
+
For schema V3 tasks, keep production code unchanged while adding the approved
|
|
113
|
+
tests, then call `./scripts/automation/record-red.sh <TASK-ID>` with no model-
|
|
114
|
+
chosen failure text. The script checks every declared case. A familiar exception
|
|
115
|
+
name in a log is not sufficient RED. Fix a test-only preparation error only when
|
|
116
|
+
the contract remains unchanged and its preparation budget allows it; rerun the
|
|
117
|
+
preflight afterwards. Contract contradictions must be blocked with the reported
|
|
118
|
+
case IDs and measured results. Never delete evidence or weaken, skip or reclassify
|
|
119
|
+
a test to pass the preflight.
|
|
120
|
+
|
|
112
121
|
You may edit only paths allowed both by this agent and by the task contract.
|
|
113
122
|
Treat `.automation-worktree-allowlist` and the status JSON's
|
|
114
123
|
`runtime.effectiveWorktreeAllowlist` paths as human-owned local state: never
|
|
@@ -62,6 +62,16 @@ approval. After approval, assemble a complete plan and contract in memory and
|
|
|
62
62
|
call `android_orchestrator_intake` with action `draft`; do not create files in the
|
|
63
63
|
product checkout. Preserve the snapshot's target branch and planningHead.
|
|
64
64
|
|
|
65
|
+
Use schema V3 structured verification. Give every verification case a stable ID,
|
|
66
|
+
its one-based acceptance-criterion reference, its source, and its exact test
|
|
67
|
+
identity. Classify preserved behavior as `before: pass`, changed behavior as
|
|
68
|
+
`before: fail`, and an uncertain old boundary as `before: observe`. Do not infer
|
|
69
|
+
exact serializer, parser, locale, date or framework output from declarations or
|
|
70
|
+
memory. For preserved behavior, use an existing trusted test or explicitly mark
|
|
71
|
+
the value for baseline capture. Plan examples are implementation guidance and
|
|
72
|
+
must not add requirements beyond the contract. Check acceptance criteria,
|
|
73
|
+
non-goals and verification expectations for contradictions before drafting.
|
|
74
|
+
|
|
65
75
|
When the draft does not specify a workspace or commit policy, use the values in
|
|
66
76
|
`automation/config.json`; new installations configure `inPlaceExclusive` and
|
|
67
77
|
`humanApproval`. A user may explicitly override the commit policy to
|
|
@@ -77,7 +87,10 @@ fresh `合同确认` single-choice question using the exact returned `question`
|
|
|
77
87
|
key and digest. Adjustments create a new draft version and require a new review.
|
|
78
88
|
|
|
79
89
|
After enqueue succeeds, report its task ID, queue state and policies and return
|
|
80
|
-
to the user.
|
|
90
|
+
to the user. A newly enqueued contract automatically resumes the whole queue
|
|
91
|
+
and starts or wakes its service; existing scheduling rules and faults still
|
|
92
|
+
apply. Failed or duplicate enqueue requests do not clear a pause.
|
|
93
|
+
The detached repository service owns execution. Never hold this
|
|
81
94
|
conversation waiting for Coder/Reviewer or simulate model polling while idle.
|
|
82
95
|
The user can immediately submit another request.
|
|
83
96
|
|
|
@@ -52,7 +52,13 @@ it with `./scripts/automation/block-task.sh <TASK-ID> <reason>` before stopping.
|
|
|
52
52
|
|
|
53
53
|
4. On the initial coding cycle, add or change the smallest behavior test
|
|
54
54
|
permitted by `allowedPaths`.
|
|
55
|
-
5. If RED evidence does not already exist, capture a genuine RED result
|
|
55
|
+
5. If RED evidence does not already exist, capture a genuine RED result. For a
|
|
56
|
+
schema V3 contract use:
|
|
57
|
+
|
|
58
|
+
`./scripts/automation/record-red.sh <TASK-ID>`
|
|
59
|
+
|
|
60
|
+
It checks every declared preserved, changed and observed case against fresh
|
|
61
|
+
structured output. For a legacy schema V1/V2 contract use:
|
|
56
62
|
|
|
57
63
|
`./scripts/automation/record-red.sh <TASK-ID> <expected-failure-text> -- <test-filter>`
|
|
58
64
|
|
|
@@ -39,6 +39,9 @@ description: Plan stable committed code, approve independent inbox contracts, an
|
|
|
39
39
|
`{key, digest, approval}`. A new version requires a new approval.
|
|
40
40
|
7. Report durable enqueue success and return. Execution is asynchronous, one
|
|
41
41
|
repository slot; the foreground may plan/approve B and C while A runs.
|
|
42
|
+
A newly enqueued contract resumes the whole queue and starts or wakes its
|
|
43
|
+
service. Existing scheduling rules and faults still apply; failed or
|
|
44
|
+
duplicate enqueue requests do not clear a pause.
|
|
42
45
|
|
|
43
46
|
# Acceptance and controls
|
|
44
47
|
|
|
@@ -38,6 +38,10 @@ you review.
|
|
|
38
38
|
5. Check each acceptance criterion against observable behavior. Inspect for
|
|
39
39
|
regression risk, missing edge cases, out-of-scope changes, test deletion,
|
|
40
40
|
ignored tests, relaxed assertions, and implementation-shaped tests.
|
|
41
|
+
For schema V3, also verify every structured case source and identity, that
|
|
42
|
+
preserved cases passed before implementation, that changed cases failed only
|
|
43
|
+
for their declared reason, and that RED contains no undeclared failure. Treat
|
|
44
|
+
the Planner and Coder summaries as claims; use the bound preflight evidence.
|
|
41
45
|
6. Decide independently:
|
|
42
46
|
|
|
43
47
|
- approve only when the diff is correct and evidence is sufficient;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"$id": "urn:frankzhang2026:opencode-android-orchestrator:task-contract:
|
|
3
|
+
"$id": "urn:frankzhang2026:opencode-android-orchestrator:task-contract:v3",
|
|
4
4
|
"title": "Scheduled coding task contract",
|
|
5
5
|
"type": "object",
|
|
6
6
|
"additionalProperties": false,
|
|
@@ -14,6 +14,63 @@
|
|
|
14
14
|
"type": "string",
|
|
15
15
|
"minLength": 1,
|
|
16
16
|
"pattern": "^[A-Za-z0-9_.#$*-]+$"
|
|
17
|
+
},
|
|
18
|
+
"expectedFailure": {
|
|
19
|
+
"type": "object",
|
|
20
|
+
"additionalProperties": false,
|
|
21
|
+
"required": ["type", "origin"],
|
|
22
|
+
"properties": {
|
|
23
|
+
"type": { "type": "string", "minLength": 1 },
|
|
24
|
+
"messageIncludes": { "type": "string", "minLength": 3 },
|
|
25
|
+
"origin": { "type": "string", "minLength": 12 }
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"verificationCase": {
|
|
29
|
+
"type": "object",
|
|
30
|
+
"additionalProperties": false,
|
|
31
|
+
"required": ["id", "criterion", "intent", "before", "after", "source", "test"],
|
|
32
|
+
"properties": {
|
|
33
|
+
"id": { "type": "string", "pattern": "^[A-Z][A-Z0-9-]{2,63}$" },
|
|
34
|
+
"criterion": { "type": "integer", "minimum": 1 },
|
|
35
|
+
"intent": { "enum": ["preserve", "change", "observe"] },
|
|
36
|
+
"before": { "enum": ["pass", "fail", "observe"] },
|
|
37
|
+
"after": { "const": "pass" },
|
|
38
|
+
"source": { "enum": ["userRequirement", "existingTest", "baselineCapture", "measuredFact"] },
|
|
39
|
+
"test": {
|
|
40
|
+
"type": "object",
|
|
41
|
+
"additionalProperties": false,
|
|
42
|
+
"required": ["target", "className", "name"],
|
|
43
|
+
"properties": {
|
|
44
|
+
"target": { "type": "integer", "minimum": 0 },
|
|
45
|
+
"className": { "type": "string", "minLength": 1 },
|
|
46
|
+
"name": { "type": "string", "minLength": 1 }
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"expectedFailure": { "$ref": "#/$defs/expectedFailure" }
|
|
50
|
+
},
|
|
51
|
+
"allOf": [
|
|
52
|
+
{
|
|
53
|
+
"if": { "properties": { "intent": { "const": "preserve" } } },
|
|
54
|
+
"then": {
|
|
55
|
+
"properties": {
|
|
56
|
+
"before": { "const": "pass" },
|
|
57
|
+
"source": { "enum": ["existingTest", "baselineCapture", "measuredFact"] }
|
|
58
|
+
},
|
|
59
|
+
"not": { "required": ["expectedFailure"] }
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"if": { "properties": { "intent": { "const": "change" } } },
|
|
64
|
+
"then": {
|
|
65
|
+
"properties": { "before": { "const": "fail" }, "source": { "const": "userRequirement" } },
|
|
66
|
+
"required": ["expectedFailure"]
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"if": { "properties": { "intent": { "const": "observe" } } },
|
|
71
|
+
"then": { "properties": { "before": { "const": "observe" } } }
|
|
72
|
+
}
|
|
73
|
+
]
|
|
17
74
|
}
|
|
18
75
|
},
|
|
19
76
|
"required": [
|
|
@@ -32,10 +89,11 @@
|
|
|
32
89
|
"nonGoals",
|
|
33
90
|
"targetTests",
|
|
34
91
|
"deviceTestsRequired",
|
|
35
|
-
"testPolicy"
|
|
92
|
+
"testPolicy",
|
|
93
|
+
"verification"
|
|
36
94
|
],
|
|
37
95
|
"properties": {
|
|
38
|
-
"schemaVersion": { "const":
|
|
96
|
+
"schemaVersion": { "const": 3 },
|
|
39
97
|
"id": { "type": "string", "pattern": "^TASK-[A-Z0-9-]+$" },
|
|
40
98
|
"title": { "type": "string", "minLength": 1 },
|
|
41
99
|
"designApproved": { "const": true },
|
|
@@ -87,6 +145,20 @@
|
|
|
87
145
|
},
|
|
88
146
|
"deviceTestsRequired": { "type": "boolean" },
|
|
89
147
|
"testPolicy": { "enum": ["required", "not-required"] },
|
|
90
|
-
"testPolicyReason": { "type": "string" }
|
|
148
|
+
"testPolicyReason": { "type": "string" },
|
|
149
|
+
"verification": {
|
|
150
|
+
"type": "object",
|
|
151
|
+
"additionalProperties": false,
|
|
152
|
+
"required": ["version", "maxPreparationFixes", "cases"],
|
|
153
|
+
"properties": {
|
|
154
|
+
"version": { "const": 1 },
|
|
155
|
+
"maxPreparationFixes": { "type": "integer", "minimum": 0, "maximum": 1 },
|
|
156
|
+
"cases": {
|
|
157
|
+
"type": "array",
|
|
158
|
+
"minItems": 1,
|
|
159
|
+
"items": { "$ref": "#/$defs/verificationCase" }
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
91
163
|
}
|
|
92
164
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"schemaVersion":
|
|
2
|
+
"schemaVersion": 3,
|
|
3
3
|
"id": "TASK-EXAMPLE-001",
|
|
4
4
|
"title": "Replace with one small, observable behavior change",
|
|
5
5
|
"designApproved": true,
|
|
@@ -53,5 +53,28 @@
|
|
|
53
53
|
],
|
|
54
54
|
"deviceTestsRequired": false,
|
|
55
55
|
"testPolicy": "required",
|
|
56
|
-
"testPolicyReason": "Behavior changes require a focused regression test"
|
|
56
|
+
"testPolicyReason": "Behavior changes require a focused regression test",
|
|
57
|
+
"verification": {
|
|
58
|
+
"version": 1,
|
|
59
|
+
"maxPreparationFixes": 1,
|
|
60
|
+
"cases": [
|
|
61
|
+
{
|
|
62
|
+
"id": "FOCUSED-BEHAVIOR",
|
|
63
|
+
"criterion": 1,
|
|
64
|
+
"intent": "change",
|
|
65
|
+
"before": "fail",
|
|
66
|
+
"after": "pass",
|
|
67
|
+
"source": "userRequirement",
|
|
68
|
+
"test": {
|
|
69
|
+
"target": 0,
|
|
70
|
+
"className": "ReplaceWithFocusedTest",
|
|
71
|
+
"name": "replace with observable behavior"
|
|
72
|
+
},
|
|
73
|
+
"expectedFailure": {
|
|
74
|
+
"type": "java.lang.AssertionError",
|
|
75
|
+
"origin": "The assertion at the approved behavior call"
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
]
|
|
79
|
+
}
|
|
57
80
|
}
|
|
@@ -26,6 +26,16 @@ red_exit_code="$(jq -er '.exitCode' "$red_file")"
|
|
|
26
26
|
review_verification_exit_code="$(jq -er '.verificationExitCode' "$review_file")"
|
|
27
27
|
[[ "$red_exit_code" -ne 0 ]] || automation_die "RED evidence does not contain a failing test result"
|
|
28
28
|
[[ "$review_verification_exit_code" -eq 0 ]] || automation_die "independent review verification did not pass"
|
|
29
|
+
structured_red=null
|
|
30
|
+
if [[ "$(jq -er '.schemaVersion' "$contract")" == "3" ]]; then
|
|
31
|
+
preflight_file="$evidence_dir/test-preflight.json"
|
|
32
|
+
manifest_file="$evidence_dir/test-manifest.json"
|
|
33
|
+
[[ -f "$preflight_file" && -f "$manifest_file" ]] || automation_die "structured RED acceptance evidence is incomplete"
|
|
34
|
+
[[ "$(jq -er '.valid' "$preflight_file")" == "true" ]] || automation_die "structured RED preflight was not valid"
|
|
35
|
+
[[ "$(jq -er '.preflightSha256' "$red_file")" == "$(automation_file_sha256 "$preflight_file")" ]] || automation_die "structured RED preflight changed"
|
|
36
|
+
[[ "$(jq -er '.manifestSha256' "$red_file")" == "$(automation_file_sha256 "$manifest_file")" ]] || automation_die "structured RED manifest changed"
|
|
37
|
+
structured_red="$(jq -c '{valid, reasonCode, summary, cases: [.cases[] | {id, criterion, intent, expectedBefore, test, valid}]}' "$preflight_file")"
|
|
38
|
+
fi
|
|
29
39
|
|
|
30
40
|
recorded_task_root="$(automation_workspace_task_root "$workspace_file")"
|
|
31
41
|
workspace_strategy="$(automation_workspace_strategy "$workspace_file")"
|
|
@@ -90,6 +100,7 @@ jq -n \
|
|
|
90
100
|
--argjson acceptanceCriteria "$(jq -c '.acceptanceCriteria' "$contract")" \
|
|
91
101
|
--argjson nonGoals "$(jq -c '.nonGoals' "$contract")" \
|
|
92
102
|
--argjson targetTests "$(jq -c '.targetTests' "$contract")" \
|
|
103
|
+
--argjson structuredRed "$structured_red" \
|
|
93
104
|
'{taskId: $taskId, title: $title, state: $state,
|
|
94
105
|
generatedAt: $generatedAt, originalBranch: $originalBranch,
|
|
95
106
|
originalHeadBeforeContract: $originalHeadBeforeContract,
|
|
@@ -110,6 +121,7 @@ jq -n \
|
|
|
110
121
|
baselineRecorded: true,
|
|
111
122
|
redRecorded: true,
|
|
112
123
|
redExitCode: $redExitCode,
|
|
124
|
+
structuredRed: $structuredRed,
|
|
113
125
|
qualityGate: "PASSED",
|
|
114
126
|
gateAttempts: $gateAttempts,
|
|
115
127
|
codingCycle: $codingCycle,
|
|
@@ -566,6 +566,114 @@ automation_run_focused_test() {
|
|
|
566
566
|
)
|
|
567
567
|
}
|
|
568
568
|
|
|
569
|
+
# Run one focused target with fresh Test execution and machine-readable case
|
|
570
|
+
# results. Test assertion failures are collected instead of failing Gradle so
|
|
571
|
+
# the caller can distinguish approved RED from build and fixture failures.
|
|
572
|
+
automation_run_classified_focused_test() {
|
|
573
|
+
local task="$1"
|
|
574
|
+
local filter="$2"
|
|
575
|
+
local root="$3"
|
|
576
|
+
local result_file="$4"
|
|
577
|
+
local log_file="$5"
|
|
578
|
+
local init_file status
|
|
579
|
+
|
|
580
|
+
automation_validate_config || return 1
|
|
581
|
+
automation_validate_gradle_task "$task" || return 1
|
|
582
|
+
automation_validate_test_filter "$filter" || return 1
|
|
583
|
+
jq -e --arg task "$task" \
|
|
584
|
+
'.gradleVerification.focusedTestTasks | index($task) != null' \
|
|
585
|
+
"$AUTOMATION_CONFIG" >/dev/null || {
|
|
586
|
+
automation_die "focused Gradle task is not allowed by automation/config.json: $task"
|
|
587
|
+
return 1
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
mkdir -p "$AUTOMATION_RUNTIME_ROOT/gradle" "$(dirname "$result_file")" "$(dirname "$log_file")"
|
|
591
|
+
init_file="$(mktemp "$AUTOMATION_RUNTIME_ROOT/gradle/classified-tests.XXXXXX")"
|
|
592
|
+
: > "$result_file"
|
|
593
|
+
cat > "$init_file" <<'GRADLE'
|
|
594
|
+
import groovy.json.JsonOutput
|
|
595
|
+
import org.gradle.api.tasks.testing.Test
|
|
596
|
+
import org.gradle.api.tasks.testing.TestDescriptor
|
|
597
|
+
import org.gradle.api.tasks.testing.TestListener
|
|
598
|
+
import org.gradle.api.tasks.testing.TestResult
|
|
599
|
+
|
|
600
|
+
def outputPath = System.getProperty('orchestrator.caseResultFile')
|
|
601
|
+
if (outputPath == null || outputPath.isEmpty()) {
|
|
602
|
+
throw new GradleException('orchestrator.caseResultFile is required')
|
|
603
|
+
}
|
|
604
|
+
def outputFile = new File(outputPath)
|
|
605
|
+
def appendResult = { Map value ->
|
|
606
|
+
synchronized (gradle) {
|
|
607
|
+
outputFile << JsonOutput.toJson(value) << System.lineSeparator()
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
gradle.allprojects { project ->
|
|
612
|
+
project.tasks.withType(Test).configureEach { testTask ->
|
|
613
|
+
outputs.upToDateWhen { false }
|
|
614
|
+
outputs.doNotCacheIf('Orchestrator requires fresh classified test execution') { true }
|
|
615
|
+
ignoreFailures = true
|
|
616
|
+
failFast = false
|
|
617
|
+
if (testTask.hasProperty('dryRun')) testTask.dryRun = false
|
|
618
|
+
addTestListener(new TestListener() {
|
|
619
|
+
void beforeSuite(TestDescriptor descriptor) {}
|
|
620
|
+
void beforeTest(TestDescriptor descriptor) {}
|
|
621
|
+
void afterTest(TestDescriptor descriptor, TestResult result) {
|
|
622
|
+
def failure = result.exceptions == null || result.exceptions.isEmpty() ? null : result.exceptions[0]
|
|
623
|
+
appendResult([
|
|
624
|
+
kind: 'case', taskPath: testTask.path,
|
|
625
|
+
className: descriptor.className ?: '', name: descriptor.name ?: '',
|
|
626
|
+
result: result.resultType.toString(),
|
|
627
|
+
exceptionType: failure == null ? null : failure.class.name,
|
|
628
|
+
exceptionMessage: failure == null ? null : (failure.message ?: '')
|
|
629
|
+
])
|
|
630
|
+
}
|
|
631
|
+
void afterSuite(TestDescriptor descriptor, TestResult result) {
|
|
632
|
+
if (descriptor.parent == null) {
|
|
633
|
+
appendResult([
|
|
634
|
+
kind: 'suite', taskPath: testTask.path,
|
|
635
|
+
tests: result.testCount, failures: result.failedTestCount,
|
|
636
|
+
skipped: result.skippedTestCount,
|
|
637
|
+
result: result.resultType.toString()
|
|
638
|
+
])
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
})
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
GRADLE
|
|
645
|
+
|
|
646
|
+
set +e
|
|
647
|
+
(cd "$root" && ./gradlew "$task" --tests "$filter" \
|
|
648
|
+
--no-configuration-cache --console=plain --init-script "$init_file" \
|
|
649
|
+
"-Dorchestrator.caseResultFile=$result_file") 2>&1 | tee "$log_file"
|
|
650
|
+
status=${PIPESTATUS[0]}
|
|
651
|
+
set -e
|
|
652
|
+
rm -f "$init_file"
|
|
653
|
+
return "$status"
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
automation_test_diff_sha() {
|
|
657
|
+
local task_id="$1"
|
|
658
|
+
local root="${2:-$AUTOMATION_ROOT}"
|
|
659
|
+
local path tracked=0
|
|
660
|
+
{
|
|
661
|
+
while IFS= read -r path; do
|
|
662
|
+
[[ -n "$path" ]] || continue
|
|
663
|
+
if automation_array_matches_path "$AUTOMATION_CONFIG" '.androidProject.testPaths' "$path"; then
|
|
664
|
+
tracked=1
|
|
665
|
+
if git -C "$root" ls-files --error-unmatch -- "$path" >/dev/null 2>&1; then
|
|
666
|
+
git -C "$root" diff --binary --no-renames HEAD -- "$path"
|
|
667
|
+
else
|
|
668
|
+
printf 'UNTRACKED %s\0' "$path"
|
|
669
|
+
git -C "$root" hash-object -- "$path"
|
|
670
|
+
fi
|
|
671
|
+
fi
|
|
672
|
+
done < <(automation_product_changed_paths_at "$task_id" "$root")
|
|
673
|
+
[[ "$tracked" == "1" ]] || printf 'NO-TEST-CHANGES'
|
|
674
|
+
} | shasum -a 256 | awk '{print $1}'
|
|
675
|
+
}
|
|
676
|
+
|
|
569
677
|
automation_require_approval() {
|
|
570
678
|
local kind="$1"
|
|
571
679
|
local supplied="$2"
|