Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
fe98943
feat: add issue development loop
codeacme17 Jul 22, 2026
998a9ab
fix: harden loop review and owner gates
codeacme17 Jul 22, 2026
fed0804
fix: verify published loop review evidence
codeacme17 Jul 22, 2026
646a7bd
fix: close loop evidence and review bypasses
codeacme17 Jul 22, 2026
20a4e5c
fix: harden loop lifecycle provenance
codeacme17 Jul 22, 2026
9ad7e83
fix: close loop lifecycle review findings
codeacme17 Jul 22, 2026
06320b9
fix: make issue loop durably resumable
codeacme17 Jul 22, 2026
c9bb553
fix: enforce issue loop review gates
codeacme17 Jul 22, 2026
6f4f871
fix: recheck finalized owner merges
codeacme17 Jul 22, 2026
2746eca
fix: bind loop lifecycle to remote proof
codeacme17 Jul 22, 2026
010a040
fix: close remote proof review findings
codeacme17 Jul 22, 2026
2369b94
fix: bind review and evidence attestations
codeacme17 Jul 22, 2026
ad8fa40
fix: require exact visible verification results
codeacme17 Jul 22, 2026
65c05c0
fix: handle unclosed verification comments
codeacme17 Jul 22, 2026
e584caf
fix: preserve visible PR section state
codeacme17 Jul 22, 2026
5f72389
chore(loop): route executor and reviewer identities
codeacme17 Jul 23, 2026
edffa5c
fix(loop): enforce GitHub role boundaries
codeacme17 Jul 23, 2026
6fcbe92
fix(loop): gate descendant GitHub commands
codeacme17 Jul 23, 2026
fd10791
chore(loop): sync bootstrap PR head
codeacme17 Jul 23, 2026
d7b2545
fix(loop): close identity routing bypasses
codeacme17 Jul 23, 2026
c6160e8
fix(loop): bind mutations to durable authorization
codeacme17 Jul 23, 2026
178da39
fix(loop): harden durable GitHub mutations
codeacme17 Jul 23, 2026
ca095c9
fix(loop): close review workflow escapes
codeacme17 Jul 23, 2026
a8ddc34
fix(loop): bind review and transport state
codeacme17 Jul 23, 2026
fbbb6a1
fix(loop): verify every dev pull request
codeacme17 Jul 23, 2026
7b125b2
fix(ci): use repository pnpm version
codeacme17 Jul 23, 2026
e731cfc
fix(ci): isolate evidence fixture environment
codeacme17 Jul 23, 2026
fca53c3
fix(test): tolerate platform font wrapping
codeacme17 Jul 23, 2026
e050a0e
ci: limit Vercel deployments to main and dev
codeacme17 Jul 23, 2026
3a00917
fix: harden issue development loop trust boundaries
Ethandasw Jul 23, 2026
5ebc30e
fix: enforce loop trust and owner boundaries
Ethandasw Jul 23, 2026
aa2b6c2
fix: close loop owner and credential boundaries
codeacme17 Jul 23, 2026
2416f7f
fix: close final loop review boundaries
codeacme17 Jul 23, 2026
21d0f73
fix: persist loop completion proofs
codeacme17 Jul 23, 2026
9e1da73
fix: close durable loop reconciliation gaps
codeacme17 Jul 23, 2026
a52c807
fix: validate restored finalization state
codeacme17 Jul 23, 2026
0366d28
fix: enforce loop trust boundaries
codeacme17 Jul 23, 2026
28d3188
fix: colocate loop agent policies
codeacme17 Jul 23, 2026
1ef5ef6
fix: serialize issue claims and paginate reviews
codeacme17 Jul 23, 2026
792dc80
fix: recover claims and bind review comments
codeacme17 Jul 24, 2026
e25b212
fix: make review and claim proof atomic
codeacme17 Jul 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .codex/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[agents]
max_threads = 3
max_depth = 1

[agents.echo_ui_pr_reviewer]
description = "Fresh-context, read-only reviewer for Echo UI issue pull requests."
config_file = "../loops/issue-dev-loop/agents/echo-ui-pr-reviewer.toml"

[agents.echo_ui_review_adjudicator]
description = "Read-only adjudicator for disputed high-severity Echo UI review findings."
config_file = "../loops/issue-dev-loop/agents/echo-ui-review-adjudicator.toml"

[agents.echo_ui_loop_evolver]
description = "Fresh-context evaluator that improves Echo UI loops from measured history."
config_file = "../loops/issue-dev-loop/agents/echo-ui-loop-evolver.toml"
213 changes: 213 additions & 0 deletions .github/workflows/issue-dev-loop-evidence.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
name: Issue dev loop evidence

on:
pull_request:
branches: [dev]
types: [opened, synchronize, reopened, ready_for_review]

permissions:
contents: read

concurrency:
group: issue-dev-loop-evidence-${{ github.event.pull_request.number }}
cancel-in-progress: true

jobs:
bootstrap-evidence:
if: >-
github.event_name == 'pull_request' &&
github.head_ref == 'codex/issue-dev-loop' &&
github.event.pull_request.head.repo.full_name == github.repository &&
github.event.pull_request.user.login == 'codeacme17'
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- name: Check out bootstrap PR head
uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
persist-credentials: false

- name: Assert immutable bootstrap head
run: test "$(git rev-parse HEAD)" = "${{ github.event.pull_request.head.sha }}"

- name: Set up pnpm
uses: pnpm/action-setup@v6

- name: Set up Node
uses: actions/setup-node@v6
with:
node-version: 24
cache: pnpm

- name: Install dependencies
run: pnpm install --frozen-lockfile --ignore-scripts

- name: Run bootstrap verification
run: pnpm verify

evidence:
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'
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- name: Check out owner-merged control plane
uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.base.sha }}
fetch-depth: 0
path: control
persist-credentials: false

- name: Check out exact PR head
uses: actions/checkout@v6
with:
repository: ${{ github.event.pull_request.head.repo.full_name }}
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
path: candidate
persist-credentials: false

- name: Set up trusted Node
uses: actions/setup-node@v6
with:
node-version: 24

- name: Resolve loop run
id: run
env:
PR_HEAD_REF: ${{ github.event.pull_request.head.ref }}
run: >-
node control/loops/issue-dev-loop/scripts/resolve-run.mjs --loop-root candidate/loops/issue-dev-loop --branch "$PR_HEAD_REF"

- name: Require one active run
run: test "${{ steps.run.outputs.has_run }}" = "true"

- name: Check out frozen owner-merged baseline
uses: actions/checkout@v6
with:
ref: ${{ steps.run.outputs.base_sha }}
fetch-depth: 1
path: trusted
persist-credentials: false

- name: Assert immutable head and trusted baseline
env:
FROZEN_BASE_SHA: ${{ steps.run.outputs.base_sha }}
LIVE_BASE_SHA: ${{ github.event.pull_request.base.sha }}
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
test "$(git -C candidate rev-parse HEAD)" = "${PR_HEAD_SHA}"
test "$(git -C trusted rev-parse HEAD)" = "${FROZEN_BASE_SHA}"
git -C control merge-base --is-ancestor "${FROZEN_BASE_SHA}" "${LIVE_BASE_SHA}"

- name: Protect trusted control plane
if: steps.run.outputs.has_run == 'true'
run: >-
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 }}"

- name: Protect append-only loop history
if: steps.run.outputs.has_run == 'true'
run: >-
node trusted/loops/issue-dev-loop/scripts/validate-history.mjs --loop-root candidate/loops/issue-dev-loop --base-ref "${{ steps.run.outputs.base_sha }}"

- name: Build trusted verifier image
run: >-
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

- name: Prepare isolated candidate and baseline volumes
shell: bash
run: |
candidate_volume="issue-dev-loop-candidate-${{ github.run_id }}-${{ github.run_attempt }}"
baseline_volume="issue-dev-loop-baseline-${{ github.run_id }}-${{ github.run_attempt }}"
image="echo-ui-loop-verifier:${{ github.run_id }}-${{ github.run_attempt }}"
docker volume create "${candidate_volume}"
docker volume create "${baseline_volume}"
docker run --rm \
--mount "type=volume,src=${candidate_volume},dst=/work" \
--mount "type=bind,src=${GITHUB_WORKSPACE}/candidate,dst=/source,readonly" \
"${image}" \
sh -ceu 'cp -a /source/. /work/; cd /work; pnpm install --frozen-lockfile --ignore-scripts'
docker run --rm \
--mount "type=volume,src=${baseline_volume},dst=/work" \
--mount "type=bind,src=${GITHUB_WORKSPACE}/trusted,dst=/source,readonly" \
"${image}" \
sh -ceu 'cp -a /source/. /work/; cd /work; pnpm install --frozen-lockfile --ignore-scripts'

- name: Run authoritative verification
id: verify
shell: bash
run: |
mkdir -p "${RUNNER_TEMP}/issue-dev-evidence"
started_at="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
candidate_volume="issue-dev-loop-candidate-${{ github.run_id }}-${{ github.run_attempt }}"
baseline_volume="issue-dev-loop-baseline-${{ github.run_id }}-${{ github.run_attempt }}"
image="echo-ui-loop-verifier:${{ github.run_id }}-${{ github.run_attempt }}"
set +e
docker run --rm --network none \
--mount "type=volume,src=${candidate_volume},dst=/work" \
"${image}" \
pnpm verify 2>&1 | tee "${RUNNER_TEMP}/issue-dev-evidence/pnpm-verify.log"
candidate_exit_code="${PIPESTATUS[0]}"
set -e
baseline_status=blocked
baseline_exit_code=1
if [[ "${candidate_exit_code}" == "0" ]]; then
set +e
docker run --rm --network none \
--mount "type=volume,src=${baseline_volume},dst=/work" \
"${image}" \
pnpm test 2>&1 | tee "${RUNNER_TEMP}/issue-dev-evidence/owner-merged-baseline-tests.log"
baseline_exit_code="${PIPESTATUS[0]}"
set -e
if [[ "${baseline_exit_code}" == "0" ]]; then
baseline_status=passed
else
baseline_status=failed
fi
fi
if [[ "${candidate_exit_code}" == "0" && "${baseline_exit_code}" == "0" ]]; then
exit_code=0
else
exit_code=1
fi
finished_at="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
if [[ "${exit_code}" == "0" ]]; then verdict=passed; else verdict=failed; fi
echo "started_at=${started_at}" >> "${GITHUB_OUTPUT}"
echo "finished_at=${finished_at}" >> "${GITHUB_OUTPUT}"
echo "exit_code=${exit_code}" >> "${GITHUB_OUTPUT}"
echo "verdict=${verdict}" >> "${GITHUB_OUTPUT}"
echo "baseline_status=${baseline_status}" >> "${GITHUB_OUTPUT}"

- name: Remove isolated verifier volumes
if: always()
run: |
docker volume rm --force "issue-dev-loop-candidate-${{ github.run_id }}-${{ github.run_attempt }}"
docker volume rm --force "issue-dev-loop-baseline-${{ github.run_id }}-${{ github.run_attempt }}"

- name: Generate exact-head manifest
if: steps.run.outputs.has_run == 'true' && always()
run: >-
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"

- name: Upload review evidence
if: steps.run.outputs.has_run == 'true' && always()
id: artifact
uses: actions/upload-artifact@v4
with:
name: issue-dev-loop-${{ steps.run.outputs.run_id }}-${{ github.event.pull_request.head.sha }}
path: |
${{ runner.temp }}/issue-dev-evidence
candidate/loops/issue-dev-loop/screen-shots/${{ steps.run.outputs.run_id }}
candidate/loops/issue-dev-loop/logs/runs/${{ steps.run.outputs.run_id }}
if-no-files-found: error
retention-days: 30

- name: Publish artifact link
if: steps.run.outputs.has_run == 'true' && always()
run: echo "Evidence artifact — ${{ steps.artifact.outputs.artifact-url }}" >> "${GITHUB_STEP_SUMMARY}"

- name: Enforce verification result
if: steps.verify.outputs.exit_code != '0'
run: exit 1
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
logs
!loops/issue-dev-loop/logs/
!loops/issue-dev-loop/logs/**
*.log
npm-debug.log*
yarn-debug.log*
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ Hook specially designed for audio interaction and analysis applications, which c

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

## Documentation development

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.

## License

[MIT](./LICENSE) © 2023-Present [leyoonafr](https://github.com/codeacme17)
16 changes: 16 additions & 0 deletions loops/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Echo UI engineering loops

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.

| Loop | Purpose | Default trigger |
| --- | --- | --- |
| [`issue-dev-loop`](./issue-dev-loop/LOOP.md) | Develop one `codex-ready` issue through an owner-reviewed PR | Cheap preflight, then scheduled Codex run |

## Repository rules

- Treat each loop directory as the canonical source for its own workflow.
- 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.
- 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.
- 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.
- Never grant a loop authority to approve, auto-merge, or merge a PR.
- Use `pnpm loop:issue-dev:validate` before changing loop infrastructure.
25 changes: 25 additions & 0 deletions loops/_shared/owner-channel/CHANNEL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Owner communication channel

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.

## Blocking events

`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.

`--dry-run` stages a simulated payload only. It never records `owner_notified`, never pauses the run, and never satisfies a blocking delivery gate.

`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.

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.

## Runtime setup

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.
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.
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.
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.
5. Enable GitHub notifications for mentions and review requests for `codeacme17`.
6. Optionally set `ECHO_UI_LOOP_OWNER_WEBHOOK_URL` to an endpoint that accepts the notification JSON with `Content-Type: application/json`.
7. Never store profile paths, webhook URLs, or credentials in this repository.

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.
25 changes: 25 additions & 0 deletions loops/_shared/owner-channel/channel.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"schemaVersion": 1,
"ownerGitHubLogin": "codeacme17",
"automationGitHubLogin": "Ethandasw",
"reviewerGitHubLogin": "Traviinam",
"automationGitHubConfigEnvironmentVariable": "ECHO_UI_LOOP_AUTOMATION_GH_CONFIG_DIR",
"reviewerGitHubConfigEnvironmentVariable": "ECHO_UI_LOOP_REVIEWER_GH_CONFIG_DIR",
"stateIssueNumber": 105,
"repository": "codeacme17/echo-ui",
"canonicalChannel": "github",
"webhookEnvironmentVariable": "ECHO_UI_LOOP_OWNER_WEBHOOK_URL",
"untrustedRootsEnvironmentVariable": "ECHO_UI_LOOP_UNTRUSTED_ROOTS",
"informationalImmediateTypes": [
"pr_completed"
],
"immediateTypes": [
"approval_required",
"clarification_required",
"blocked",
"review_dispute",
"pr_ready_for_review",
"pr_updated_for_review",
"loop_failed"
]
}
32 changes: 32 additions & 0 deletions loops/_shared/owner-channel/notification.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://echoui.dev/schemas/loop-notification.schema.json",
"title": "Echo UI loop owner notification",
"type": "object",
"required": [
"schemaVersion",
"notificationId",
"loop",
"runId",
"type",
"blocking",
"summary",
"requestedAction",
"createdAt"
],
"additionalProperties": false,
"properties": {
"schemaVersion": { "const": 1 },
"notificationId": { "type": "string", "pattern": "^NTF-[A-Z0-9-]+$" },
"loop": { "const": "issue-dev-loop" },
"runId": { "type": "string", "minLength": 1 },
"type": { "type": "string", "minLength": 1 },
"blocking": { "type": "boolean" },
"summary": { "type": "string", "minLength": 1 },
"requestedAction": { "type": "string", "minLength": 1 },
"targetUrl": { "type": ["string", "null"], "format": "uri" },
"evidenceUrl": { "type": ["string", "null"], "format": "uri" },
"createdAt": { "type": "string", "format": "date-time" },
"delivery": { "type": "object" }
}
}
2 changes: 2 additions & 0 deletions loops/_shared/owner-channel/outbox/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*
!.gitignore
Loading