Skip to content

Commit 18045d7

Browse files
authored
Merge pull request #106 from codeacme17/codex/issue-dev-loop
feat: add durable issue development loop
2 parents 3e8f255 + e25b212 commit 18045d7

79 files changed

Lines changed: 17591 additions & 2 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.codex/config.toml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[agents]
2+
max_threads = 3
3+
max_depth = 1
4+
5+
[agents.echo_ui_pr_reviewer]
6+
description = "Fresh-context, read-only reviewer for Echo UI issue pull requests."
7+
config_file = "../loops/issue-dev-loop/agents/echo-ui-pr-reviewer.toml"
8+
9+
[agents.echo_ui_review_adjudicator]
10+
description = "Read-only adjudicator for disputed high-severity Echo UI review findings."
11+
config_file = "../loops/issue-dev-loop/agents/echo-ui-review-adjudicator.toml"
12+
13+
[agents.echo_ui_loop_evolver]
14+
description = "Fresh-context evaluator that improves Echo UI loops from measured history."
15+
config_file = "../loops/issue-dev-loop/agents/echo-ui-loop-evolver.toml"
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
name: Issue dev loop evidence
2+
3+
on:
4+
pull_request:
5+
branches: [dev]
6+
types: [opened, synchronize, reopened, ready_for_review]
7+
8+
permissions:
9+
contents: read
10+
11+
concurrency:
12+
group: issue-dev-loop-evidence-${{ github.event.pull_request.number }}
13+
cancel-in-progress: true
14+
15+
jobs:
16+
bootstrap-evidence:
17+
if: >-
18+
github.event_name == 'pull_request' &&
19+
github.head_ref == 'codex/issue-dev-loop' &&
20+
github.event.pull_request.head.repo.full_name == github.repository &&
21+
github.event.pull_request.user.login == 'codeacme17'
22+
runs-on: ubuntu-latest
23+
timeout-minutes: 45
24+
steps:
25+
- name: Check out bootstrap PR head
26+
uses: actions/checkout@v6
27+
with:
28+
ref: ${{ github.event.pull_request.head.sha }}
29+
fetch-depth: 0
30+
persist-credentials: false
31+
32+
- name: Assert immutable bootstrap head
33+
run: test "$(git rev-parse HEAD)" = "${{ github.event.pull_request.head.sha }}"
34+
35+
- name: Set up pnpm
36+
uses: pnpm/action-setup@v6
37+
38+
- name: Set up Node
39+
uses: actions/setup-node@v6
40+
with:
41+
node-version: 24
42+
cache: pnpm
43+
44+
- name: Install dependencies
45+
run: pnpm install --frozen-lockfile --ignore-scripts
46+
47+
- name: Run bootstrap verification
48+
run: pnpm verify
49+
50+
evidence:
51+
if: github.event_name == 'pull_request' && startsWith(github.event.pull_request.head.ref, 'codex/issue-') && github.event.pull_request.head.ref != 'codex/issue-dev-loop'
52+
runs-on: ubuntu-latest
53+
timeout-minutes: 45
54+
steps:
55+
- name: Check out owner-merged control plane
56+
uses: actions/checkout@v6
57+
with:
58+
ref: ${{ github.event.pull_request.base.sha }}
59+
fetch-depth: 0
60+
path: control
61+
persist-credentials: false
62+
63+
- name: Check out exact PR head
64+
uses: actions/checkout@v6
65+
with:
66+
repository: ${{ github.event.pull_request.head.repo.full_name }}
67+
ref: ${{ github.event.pull_request.head.sha }}
68+
fetch-depth: 0
69+
path: candidate
70+
persist-credentials: false
71+
72+
- name: Set up trusted Node
73+
uses: actions/setup-node@v6
74+
with:
75+
node-version: 24
76+
77+
- name: Resolve loop run
78+
id: run
79+
env:
80+
PR_HEAD_REF: ${{ github.event.pull_request.head.ref }}
81+
run: >-
82+
node control/loops/issue-dev-loop/scripts/resolve-run.mjs --loop-root candidate/loops/issue-dev-loop --branch "$PR_HEAD_REF"
83+
84+
- name: Require one active run
85+
run: test "${{ steps.run.outputs.has_run }}" = "true"
86+
87+
- name: Check out frozen owner-merged baseline
88+
uses: actions/checkout@v6
89+
with:
90+
ref: ${{ steps.run.outputs.base_sha }}
91+
fetch-depth: 1
92+
path: trusted
93+
persist-credentials: false
94+
95+
- name: Assert immutable head and trusted baseline
96+
env:
97+
FROZEN_BASE_SHA: ${{ steps.run.outputs.base_sha }}
98+
LIVE_BASE_SHA: ${{ github.event.pull_request.base.sha }}
99+
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
100+
run: |
101+
test "$(git -C candidate rev-parse HEAD)" = "${PR_HEAD_SHA}"
102+
test "$(git -C trusted rev-parse HEAD)" = "${FROZEN_BASE_SHA}"
103+
git -C control merge-base --is-ancestor "${FROZEN_BASE_SHA}" "${LIVE_BASE_SHA}"
104+
105+
- name: Protect trusted control plane
106+
if: steps.run.outputs.has_run == 'true'
107+
run: >-
108+
node trusted/loops/issue-dev-loop/scripts/validate-candidate-control-plane.mjs --loop-root candidate/loops/issue-dev-loop --run-id "${{ steps.run.outputs.run_id }}" --base-sha "${{ steps.run.outputs.base_sha }}" --head-sha "${{ github.event.pull_request.head.sha }}"
109+
110+
- name: Protect append-only loop history
111+
if: steps.run.outputs.has_run == 'true'
112+
run: >-
113+
node trusted/loops/issue-dev-loop/scripts/validate-history.mjs --loop-root candidate/loops/issue-dev-loop --base-ref "${{ steps.run.outputs.base_sha }}"
114+
115+
- name: Build trusted verifier image
116+
run: >-
117+
docker build --tag echo-ui-loop-verifier:${{ github.run_id }}-${{ github.run_attempt }} --file trusted/loops/issue-dev-loop/scripts/verifier.Dockerfile trusted/loops/issue-dev-loop/scripts
118+
119+
- name: Prepare isolated candidate and baseline volumes
120+
shell: bash
121+
run: |
122+
candidate_volume="issue-dev-loop-candidate-${{ github.run_id }}-${{ github.run_attempt }}"
123+
baseline_volume="issue-dev-loop-baseline-${{ github.run_id }}-${{ github.run_attempt }}"
124+
image="echo-ui-loop-verifier:${{ github.run_id }}-${{ github.run_attempt }}"
125+
docker volume create "${candidate_volume}"
126+
docker volume create "${baseline_volume}"
127+
docker run --rm \
128+
--mount "type=volume,src=${candidate_volume},dst=/work" \
129+
--mount "type=bind,src=${GITHUB_WORKSPACE}/candidate,dst=/source,readonly" \
130+
"${image}" \
131+
sh -ceu 'cp -a /source/. /work/; cd /work; pnpm install --frozen-lockfile --ignore-scripts'
132+
docker run --rm \
133+
--mount "type=volume,src=${baseline_volume},dst=/work" \
134+
--mount "type=bind,src=${GITHUB_WORKSPACE}/trusted,dst=/source,readonly" \
135+
"${image}" \
136+
sh -ceu 'cp -a /source/. /work/; cd /work; pnpm install --frozen-lockfile --ignore-scripts'
137+
138+
- name: Run authoritative verification
139+
id: verify
140+
shell: bash
141+
run: |
142+
mkdir -p "${RUNNER_TEMP}/issue-dev-evidence"
143+
started_at="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
144+
candidate_volume="issue-dev-loop-candidate-${{ github.run_id }}-${{ github.run_attempt }}"
145+
baseline_volume="issue-dev-loop-baseline-${{ github.run_id }}-${{ github.run_attempt }}"
146+
image="echo-ui-loop-verifier:${{ github.run_id }}-${{ github.run_attempt }}"
147+
set +e
148+
docker run --rm --network none \
149+
--mount "type=volume,src=${candidate_volume},dst=/work" \
150+
"${image}" \
151+
pnpm verify 2>&1 | tee "${RUNNER_TEMP}/issue-dev-evidence/pnpm-verify.log"
152+
candidate_exit_code="${PIPESTATUS[0]}"
153+
set -e
154+
baseline_status=blocked
155+
baseline_exit_code=1
156+
if [[ "${candidate_exit_code}" == "0" ]]; then
157+
set +e
158+
docker run --rm --network none \
159+
--mount "type=volume,src=${baseline_volume},dst=/work" \
160+
"${image}" \
161+
pnpm test 2>&1 | tee "${RUNNER_TEMP}/issue-dev-evidence/owner-merged-baseline-tests.log"
162+
baseline_exit_code="${PIPESTATUS[0]}"
163+
set -e
164+
if [[ "${baseline_exit_code}" == "0" ]]; then
165+
baseline_status=passed
166+
else
167+
baseline_status=failed
168+
fi
169+
fi
170+
if [[ "${candidate_exit_code}" == "0" && "${baseline_exit_code}" == "0" ]]; then
171+
exit_code=0
172+
else
173+
exit_code=1
174+
fi
175+
finished_at="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
176+
if [[ "${exit_code}" == "0" ]]; then verdict=passed; else verdict=failed; fi
177+
echo "started_at=${started_at}" >> "${GITHUB_OUTPUT}"
178+
echo "finished_at=${finished_at}" >> "${GITHUB_OUTPUT}"
179+
echo "exit_code=${exit_code}" >> "${GITHUB_OUTPUT}"
180+
echo "verdict=${verdict}" >> "${GITHUB_OUTPUT}"
181+
echo "baseline_status=${baseline_status}" >> "${GITHUB_OUTPUT}"
182+
183+
- name: Remove isolated verifier volumes
184+
if: always()
185+
run: |
186+
docker volume rm --force "issue-dev-loop-candidate-${{ github.run_id }}-${{ github.run_attempt }}"
187+
docker volume rm --force "issue-dev-loop-baseline-${{ github.run_id }}-${{ github.run_attempt }}"
188+
189+
- name: Generate exact-head manifest
190+
if: steps.run.outputs.has_run == 'true' && always()
191+
run: >-
192+
node trusted/loops/issue-dev-loop/scripts/generate-evidence.mjs --loop-root candidate/loops/issue-dev-loop --run-id "${{ steps.run.outputs.run_id }}" --head-sha "${{ github.event.pull_request.head.sha }}" --trusted-workflow-sha "${{ steps.run.outputs.base_sha }}" --workflow-base-sha "${{ github.event.pull_request.base.sha }}" --workflow-run-sha "${{ github.event.pull_request.head.sha }}" --status "${{ steps.verify.outputs.verdict || 'blocked' }}" --baseline-status "${{ steps.verify.outputs.baseline_status || 'blocked' }}" --started-at "${{ steps.verify.outputs.started_at || github.event.pull_request.updated_at }}" --finished-at "${{ steps.verify.outputs.finished_at || github.event.pull_request.updated_at }}" --output "${RUNNER_TEMP}/issue-dev-evidence/manifest.json"
193+
194+
- name: Upload review evidence
195+
if: steps.run.outputs.has_run == 'true' && always()
196+
id: artifact
197+
uses: actions/upload-artifact@v4
198+
with:
199+
name: issue-dev-loop-${{ steps.run.outputs.run_id }}-${{ github.event.pull_request.head.sha }}
200+
path: |
201+
${{ runner.temp }}/issue-dev-evidence
202+
candidate/loops/issue-dev-loop/screen-shots/${{ steps.run.outputs.run_id }}
203+
candidate/loops/issue-dev-loop/logs/runs/${{ steps.run.outputs.run_id }}
204+
if-no-files-found: error
205+
retention-days: 30
206+
207+
- name: Publish artifact link
208+
if: steps.run.outputs.has_run == 'true' && always()
209+
run: echo "Evidence artifact — ${{ steps.artifact.outputs.artifact-url }}" >> "${GITHUB_STEP_SUMMARY}"
210+
211+
- name: Enforce verification result
212+
if: steps.verify.outputs.exit_code != '0'
213+
run: exit 1

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
logs
2+
!loops/issue-dev-loop/logs/
3+
!loops/issue-dev-loop/logs/**
24
*.log
35
npm-debug.log*
46
yarn-debug.log*

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ Hook specially designed for audio interaction and analysis applications, which c
4646

4747
Echo UI's component library is responsive, meaning they can automatically adapt to different screen sizes, providing a good experience on different devices.
4848

49+
## Documentation development
50+
51+
Run the documentation site locally with `pnpm dev:docs`. Before submitting documentation changes, run `pnpm test:docs` to verify the generated site, routes, and UI contract.
52+
4953
## License
5054

5155
[MIT](./LICENSE) © 2023-Present [leyoonafr](https://github.com/codeacme17)

loops/README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Echo UI engineering loops
2+
3+
This directory contains durable, auditable workflows that Codex can run against Echo UI. A loop is larger than a skill: it owns a contract, compact state, append-only history, triggers, evidence, review policy, and evolution policy.
4+
5+
| Loop | Purpose | Default trigger |
6+
| --- | --- | --- |
7+
| [`issue-dev-loop`](./issue-dev-loop/LOOP.md) | Develop one `codex-ready` issue through an owner-reviewed PR | Cheap preflight, then scheduled Codex run |
8+
9+
## Repository rules
10+
11+
- Treat each loop directory as the canonical source for its own workflow.
12+
- Keep each loop's agent instructions inside its loop directory. Do not expose a loop as a top-level `.agents/skills` entry; scheduled automation invokes the loop contract and project agents directly.
13+
- Persist compact state, sanitized event history, and issue-relevant screenshots in the issue PR. Publish exact-head evidence manifests and verification logs as CI artifacts; keep raw local output and large recordings out of Git.
14+
- Treat repository loop code as reviewable source, not a credential boundary. Install a versioned control plane outside the repository only from clean owner-merged `dev`, and route all credential-bearing operations through that hash-verified installation.
15+
- Never grant a loop authority to approve, auto-merge, or merge a PR.
16+
- Use `pnpm loop:issue-dev:validate` before changing loop infrastructure.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Owner communication channel
2+
3+
GitHub issue and PR comments are the canonical, auditable channel. The runtime mentions `@codeacme17` on the originating issue before a PR exists and on the PR after publication. A configured generic webhook mirrors the same JSON payload for immediate push delivery.
4+
5+
## Blocking events
6+
7+
`approval_required`, `clarification_required`, `blocked`, `review_dispute`, `pr_ready_for_review`, `pr_updated_for_review`, and `loop_failed` require immediate delivery and must be sent with `blocking: true`. The runtime enters `waiting_for_owner` even when delivery fails, and it must not choose a default answer after a timeout. A still-Draft PR advances to `awaiting_owner_review` only after SHA-bound evidence validation; the notification asks `codeacme17` to mark it Ready in GitHub, review it, and merge or request changes.
8+
9+
`--dry-run` stages a simulated payload only. It never records `owner_notified`, never pauses the run, and never satisfies a blocking delivery gate.
10+
11+
`pr_completed` is informational and does not ask the owner for another decision, but successful canonical GitHub delivery is a hard prerequisite for completion. `prepare-finalization` posts this reserved marker only after remotely observing the strictly post-notification owner Ready transition, exact-head owner approval, and owner merge. Its body binds the merge SHA, its remote timestamp must be at or after the merge, and it must use a comment URL distinct from the Ready notification. The command waits for the bounded optional webhook attempt and binds both notification outcomes before any terminal journal publication. GitHub delivery failure leaves the run active and retryable.
12+
13+
Each blocking GitHub notification prints a unique resume instruction. To continue after answering, include `RESUME <run-id>` in a normal issue/PR comment; submitting a GitHub `CHANGES_REQUESTED` review is also an explicit response. The runtime verifies author, target, timestamp, successful delivery, and response URL before resuming. Silence and unrelated comments never count.
14+
15+
## Runtime setup
16+
17+
1. Authenticate the unattended executor and fresh reviewer with distinct GitHub identities. Their exact logins and the names of their profile-path environment variables live in `channel.json`. For the current configuration, set `ECHO_UI_LOOP_AUTOMATION_GH_CONFIG_DIR` to the `Ethandasw` `gh` profile directory and `ECHO_UI_LOOP_REVIEWER_GH_CONFIG_DIR` to the `Traviinam` profile directory in the trusted router environment. The directory names themselves are local details and do not need to match the roles. Both directories must be private (`0700`, with no group/other access on any descendant) and outside every untrusted agent-visible root.
18+
2. From a clean owner-merged `dev` checkout at exact `origin/dev`, install a new versioned control plane outside every repository worktree and every scheduler-writable root with `install-trusted-control-plane.mjs`. Expose it read/execute-only to the unattended process through the scheduler sandbox or separate ownership/ACLs; the automation identity must not be able to rewrite it, its parent, the pinned executables, modes, ACLs, or sandbox policy. Set `ECHO_UI_LOOP_CONTROL_PLANE` to its `issue-dev-loop` directory, `ECHO_UI_LOOP_TARGET_ROOT` to the scheduled worktree's loop directory, and `ECHO_UI_LOOP_UNTRUSTED_ROOTS` to a JSON array covering the repository plus every root mounted into `$implement`, reviewer, or test sandboxes. Those sandboxes must not inherit either profile-path variable or `GH_CONFIG_DIR` and must be unable to read the profile directories. Run `"$ECHO_UI_LOOP_CONTROL_PLANE/scripts/with-github-identity" --loop-root "$ECHO_UI_LOOP_TARGET_ROOT" automation -- node "$ECHO_UI_LOOP_CONTROL_PLANE/scripts/loopctl.mjs" validate --activation --loop-root "$ECHO_UI_LOOP_TARGET_ROOT"` before scheduling.
19+
3. Run every operational loop command, executor GitHub command, remote Git command, trigger, and reviewer publication through that installed launcher with the explicit target root. The repository launcher intentionally refuses credentials. The installed launcher hash-verifies its files and absolute executables, compares trusted channel fields, removes Node preload hooks, clears token overrides and process hooks, verifies `gh api user`, and gives Git a one-command credential helper without changing global Git or `gh` configuration. A PATH gate applies the role and current-run policy to descendant `git` and `gh` processes too; issue-worktree routers, PATH shims, arbitrary shell/environment commands, and arbitrary Node scripts are rejected.
20+
4. Create one dedicated repository issue for the append-only loop state journal and set its number as `stateIssueNumber`. It stores active checkpoints and terminal records. Restrict journal entries to the automation identity; humans may read but should not edit or delete them.
21+
5. Enable GitHub notifications for mentions and review requests for `codeacme17`.
22+
6. Optionally set `ECHO_UI_LOOP_OWNER_WEBHOOK_URL` to an endpoint that accepts the notification JSON with `Content-Type: application/json`.
23+
7. Never store profile paths, webhook URLs, or credentials in this repository.
24+
25+
Review publications are valid only when authored by `reviewerGitHubLogin`; executor replies must match `automationGitHubLogin`. Those identities must differ from one another and from `ownerGitHubLogin`. Owner decisions are valid only when the author matches `ownerGitHubLogin`. A webhook delivery is an alert, not an approval channel.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"schemaVersion": 1,
3+
"ownerGitHubLogin": "codeacme17",
4+
"automationGitHubLogin": "Ethandasw",
5+
"reviewerGitHubLogin": "Traviinam",
6+
"automationGitHubConfigEnvironmentVariable": "ECHO_UI_LOOP_AUTOMATION_GH_CONFIG_DIR",
7+
"reviewerGitHubConfigEnvironmentVariable": "ECHO_UI_LOOP_REVIEWER_GH_CONFIG_DIR",
8+
"stateIssueNumber": 105,
9+
"repository": "codeacme17/echo-ui",
10+
"canonicalChannel": "github",
11+
"webhookEnvironmentVariable": "ECHO_UI_LOOP_OWNER_WEBHOOK_URL",
12+
"untrustedRootsEnvironmentVariable": "ECHO_UI_LOOP_UNTRUSTED_ROOTS",
13+
"informationalImmediateTypes": [
14+
"pr_completed"
15+
],
16+
"immediateTypes": [
17+
"approval_required",
18+
"clarification_required",
19+
"blocked",
20+
"review_dispute",
21+
"pr_ready_for_review",
22+
"pr_updated_for_review",
23+
"loop_failed"
24+
]
25+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"$schema": "https://json-schema.org/draft/2020-12/schema",
3+
"$id": "https://echoui.dev/schemas/loop-notification.schema.json",
4+
"title": "Echo UI loop owner notification",
5+
"type": "object",
6+
"required": [
7+
"schemaVersion",
8+
"notificationId",
9+
"loop",
10+
"runId",
11+
"type",
12+
"blocking",
13+
"summary",
14+
"requestedAction",
15+
"createdAt"
16+
],
17+
"additionalProperties": false,
18+
"properties": {
19+
"schemaVersion": { "const": 1 },
20+
"notificationId": { "type": "string", "pattern": "^NTF-[A-Z0-9-]+$" },
21+
"loop": { "const": "issue-dev-loop" },
22+
"runId": { "type": "string", "minLength": 1 },
23+
"type": { "type": "string", "minLength": 1 },
24+
"blocking": { "type": "boolean" },
25+
"summary": { "type": "string", "minLength": 1 },
26+
"requestedAction": { "type": "string", "minLength": 1 },
27+
"targetUrl": { "type": ["string", "null"], "format": "uri" },
28+
"evidenceUrl": { "type": ["string", "null"], "format": "uri" },
29+
"createdAt": { "type": "string", "format": "date-time" },
30+
"delivery": { "type": "object" }
31+
}
32+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
*
2+
!.gitignore

0 commit comments

Comments
 (0)