Conversation
Synced from ~/.cursor/ with CLAUDE.md generation and skills symlink for Claude Code compatibility. Includes setup.sh for deploying to new machines.
Document the current repo structure, workflow skills, and shared scripts using the older conventions README format as a template.
task-review: resolve target repo by grepping code for concrete symbols, not task text. Title/description demoted to hints; prefix table kept as an exception shortcut; linked PRs short-circuit; cross-repo work splits into Asana subtasks. pr-create: drop all reviewer-assignment logic. Reviewer choice is a human step; status-setting and review-hour estimation went with it. --asana-attach remains. one-shot: stop defaulting --asana-assign. --asana-attach only. pr-land: add CHANGELOG placement warning handling so dated-release entries can be moved to Unreleased/staging before pushing.
- New slot-fixup.sh: slot HEAD fixup next to its target's group (used by pr-address and bugbot after each lint-commit.sh). - pr-finalize-fixups.sh: derive mode (autosquash | preserve) from the latest human activity on the PR; new squash-stale subcommand for the Fixups-A-before-B trigger; finalize action now push-only in preserve mode, autosquash+force-push in autosquash mode. - pr-address review-mode subcommand: returns mode + latestHumanActivity for shared use. - Simplified human-reviewer detection across pr-address and pr-land scripts: exclude only currentUser + bots (drop prAuthor exclusion). Works uniformly for solo and collab PRs — author gets no special treatment. - pr-address and bugbot SKILL.md: new Step 1.5 (squash-stale before address-pass) and per-fixup slot-fixup.sh after every lint-commit.sh.
- Pull-before-push gate: auto-fetch origin every run; abort --stage/--commit if origin is ahead. - Per-file divergence warnings: compare each affected path's most-recent commit timestamp to the local file's mtime; flag stale-local, deletion, and re-adding-deleted cases. - New JSON output fields: originBranch, originAhead, warnings. - SKILL.md: present new warnings in the dry-run summary; document the new policy and edge cases.
…nch-scan for legacy
| Checkout the PR branch to ensure file reads reflect the PR's code, not the current local branch: | ||
|
|
||
| ```bash | ||
| git fetch origin <headRef> && git checkout <headRef> |
There was a problem hiding this comment.
🔒 Agentic Security Review
Severity: HIGH
<headRef> is inserted unquoted into a shell command (git fetch ... && git checkout ...) even though PR branch names are attacker-controlled input.
Impact: A crafted branch name containing shell metacharacters (for example command substitution) can execute arbitrary commands in the review agent environment.
| Resolve the full 40-char SHA for the PR's head branch: | ||
|
|
||
| ```bash | ||
| HEAD_SHA=$(git rev-parse origin/<BRANCH>) |
There was a problem hiding this comment.
🔒 Agentic Security Review
Severity: HIGH
<BRANCH> is interpolated directly into git rev-parse origin/<BRANCH> in a shell command path that derives branch names from PR metadata.
Impact: Malicious branch names can trigger command injection during bugbot workflow execution, leading to arbitrary code execution in automation context.
| IMPLEMENTOR_NAME="current user" | ||
|
|
||
| # Phase 3: Create the task | ||
| NOTES_JSON=$(python3 -c "import json; print(json.dumps('''$TASK_NOTES'''))") |
There was a problem hiding this comment.
🔒 Agentic Security Review
Severity: HIGH
User/task-controlled content is embedded directly into inline Python source via triple-quoted interpolation ('''$TASK_NOTES''' / '''$TASK_NAME''') in python3 -c calls.
Impact: An input containing ''' can break out of the string literal and inject executable Python statements, resulting in code execution in the automation runtime.
| ext = os.path.splitext(name)[1].lower() | ||
| if ext in DOWNLOAD_EXTS and url: | ||
| os.makedirs(download_dir, exist_ok=True) | ||
| dest = os.path.join(download_dir, name) |
There was a problem hiding this comment.
🔒 Agentic Security Review
Severity: HIGH
Attachment filename name is treated as a trusted path segment via os.path.join(download_dir, name) before writing downloaded content.
Impact: Filenames containing traversal segments (for example ../) can write files outside the intended task download directory, enabling workspace boundary bypass and arbitrary file overwrite in reachable paths.
|
|
||
| <rules> | ||
| <rule id="every-turn">Execute at the end of every chat turn without exception.</rule> | ||
| <rule id="full-response">Send the complete response content, not an abbreviated summary.</rule> |
There was a problem hiding this comment.
🔒 Agentic Security Review
Severity: HIGH
[Privacy Guard] This rule mandates forwarding the complete chat response every turn to an external Telegram sink without user confirmation or selective minimization.
Impact: Sensitive content (credentials, proprietary code, internal data) can be exfiltrated outside the expected trust boundary by policy.
| jq --arg k "$KEY" --arg v "$VALUE" '.[$k] = $v' env.json > env.json.tmp | ||
| mv env.json.tmp env.json | ||
|
|
||
| git add env.json |
There was a problem hiding this comment.
🔒 Agentic Security Review
Severity: HIGH
[Privacy Guard] The script stages and commits env.json updates after accepting raw secret values as direct inputs.
Impact: Secret material can become durable plaintext in git history and propagate through clones/backups, creating long-lived credential exposure risk.
| if (!existsSync(path.join(repoDir, ".git"))) { | ||
| console.error(`Cloning ${repo}...`); | ||
| try { | ||
| execSync(`git clone git@github.com:EdgeApp/${repo}.git "${repoDir}"`, { |
There was a problem hiding this comment.
🔒 Agentic Security Review
Severity: HIGH
repo is interpolated into a shell command string passed to execSync (git clone git@github.com:EdgeApp/${repo}.git ...) without validation or argument separation.
Impact: A crafted repo value can trigger shell command injection and arbitrary code execution in the automation environment.
|
|
||
| function fetchPrBody(repo, prNumber) { | ||
| const endpoint = `repos/EdgeApp/${repo}/pulls/${prNumber}`; | ||
| const result = execSync(`gh api "${endpoint}" --jq '.body'`, { |
There was a problem hiding this comment.
🔒 Agentic Security Review
Severity: HIGH
fetchPrBody() builds a shell command string with untrusted repo (gh api "repos/EdgeApp/${repo}/pulls/${prNumber}" ...) and executes it via execSync.
Impact: Malicious input can exploit shell expansion and execute arbitrary commands while extracting PR metadata.
| COMMIT_MSG="Update $KEY in env.json" | ||
| fi | ||
|
|
||
| ssh "$SERVER" bash -s -- "$KEY" "$VALUE" "$COMMIT_MSG" "$REMOTE_REPO" <<'REMOTE' |
There was a problem hiding this comment.
🔒 Agentic Security Review
Severity: MEDIUM
The secret value is passed as a positional CLI argument ("$VALUE") into ssh ... bash -s -- ..., which exposes it in process arguments and often shell history.
Impact: Sensitive credentials can be disclosed to local observers or process-monitoring tooling on the calling host.
Re-validate eslint after update-eslint-warnings runs; if the staged config fails lint (e.g. a naive graduation of a still-dirty file), restore eslint.config.mjs and abort. With this safety net in place, /pr-land's --skip-lint patch (e21dca8) is no longer needed — revert verify-repo.sh, pr-land-{prepare,merge}.sh, edge-repo.js, SKILL.md back to file-scoped lint. Also fixes pr-land-discover.sh's reviewer-state computation: only APPROVED/CHANGES_REQUESTED/DISMISSED change effective state, so a later COMMENTED reply doesn't shadow a prior APPROVED.
|
|
||
| function checkNpmPublished(packageName, version) { | ||
| try { | ||
| const info = execSync(`npm view ${packageName}@${version} version`, { |
There was a problem hiding this comment.
🔒 Agentic Security Review
Severity: HIGH
checkNpmPublished builds an execSync shell string using packageName and version from package.json (npm view ${packageName}@${version} version) without argument-safe execution.
Impact: A crafted package name/version can trigger command injection and arbitrary code execution in automation environments running this publish flow.
| const fileCount = changedFiles.split("\n").length; | ||
| console.log(`▶ eslint (${fileCount} changed file${fileCount === 1 ? "" : "s"} vs ${baseRef})...`); | ||
| const eslintResult = runCommandWithLog( | ||
| `npx eslint ${fileList}`, |
There was a problem hiding this comment.
🔒 Agentic Security Review
Severity: HIGH
verify-repo.sh builds an eslint command string from git-derived filenames and executes it via execSync; wrapping paths in quotes is not sufficient shell escaping.
Impact: A malicious filename in a branch can break command quoting and execute arbitrary shell commands during repository verification.
| "skills", | ||
| "verify-repo.sh" | ||
| ); | ||
| const baseArg = baseRef != null ? ` --base "${baseRef}"` : ""; |
There was a problem hiding this comment.
🔒 Agentic Security Review
Severity: HIGH
runVerification/related helpers interpolate derived repo/path values into execSync shell strings instead of passing argument arrays.
Impact: If attacker-influenced repo/path metadata reaches these call sites, command injection can occur during pr-land preparation/verification operations.
| </sub-step> | ||
| </step> | ||
|
|
||
| <step id="4" name="Upload to gist and clean up"> |
There was a problem hiding this comment.
🔒 Agentic Security Review
Severity: HIGH
The standup workflow templates externally sourced task/PR text directly into markdown and then requires uploading the rendered output to a persistent GitHub gist, without a mandatory scrub/redaction step.
Impact: Sensitive code-adjacent content (paths, snippets, internal details) from source systems can be durably exfiltrated to an external artifact.
| </sub-step> | ||
| </step> | ||
|
|
||
| <step id="4" name="Upload to gist and clean up"> |
There was a problem hiding this comment.
🔒 Agentic Security Review
Severity: HIGH
The HUDL flow maps GitHub-derived PR text fields into a generated markdown report and uploads it to a gist, but does not require sanitization/minimization of potentially sensitive content.
Impact: Code-related or sensitive operational details can leak into externally stored summaries.
| # Copy file only if changed, respecting --dry-run | ||
| sync_file() { | ||
| local src="$1" dest="$2" | ||
| if [[ ! -f "$dest" ]]; then |
There was a problem hiding this comment.
🔒 Agentic Security Review
Severity: HIGH
sync_file copies directly to destination paths without rejecting symlink destinations or enforcing canonical containment.
Impact: A pre-positioned symlink under the sync tree can redirect writes to unintended files, enabling arbitrary file overwrite as the current user.
| for oc_skill_dir in "$OPENCODE_DIR"/skills/*/; do | ||
| [[ -d "$oc_skill_dir" ]] || continue | ||
| local name | ||
| name=$(basename "$oc_skill_dir") |
There was a problem hiding this comment.
🔒 Agentic Security Review
Severity: HIGH
Cleanup uses rm -rf on globbed skill directories without symlink validation.
Impact: A symlinked directory entry can cause deletion outside the intended sync root, leading to destructive data loss in attacker-chosen paths.
…es), no-slop, asana ↔ GitHub graceful degrade, yarn→npm migration support - skills/build-and-test: real iOS UI test path via maestro for edge-react-gui - skills/debugger: attach to Hermes JS VM via CDP; set file:line breakpoints - skills/no-slop: writing-style rule + bad/good examples - skills/one-shot: yolo-stop-at-pr, pr-watch-loop, agent_status-on-pending-task - skills/asana-task-update: --attach-pr graceful when ASANA_GITHUB_SECRET unset - skills/install-deps.sh, verify-repo.sh: npm vs yarn auto-detect - scripts/tool-sync.sh: strip trailing slash from glob (double-slash fix)
…ts, slots) Host-local snapshot mirrored for paper trail. The live copy under ~/.config/agent-watcher/ is what runs; this directory is not deployed from. Run up to MAX_CONCURRENT (default 2) agent sessions in parallel, each in its own git worktree, on its own cloned iOS sim, on its own Metro port, behind a load/RAM resource guardrail. Slot accounting is by live tmux sessions, not Asana state, so a dead-but-in-flight task frees its lane. Changed for this work: - asana-watcher.js: multi-pick per tick (cap by live sessions, guardrail, pick oldest N Pending, per-task setup -> clone -> allocate -> spawn). --simulate-* dry-run flags. - rc-watchdog.js: completion sweep reaps the slot (delete sim, remove worktree+branch, release slot) via shared helpers. - spawn-test-session.sh: slot mode (--slot-index ...) exports $AGENT_SIM_UDID / $AGENT_METRO_PORT and cwd=worktree; legacy positional mode preserved. - resume-agent.sh: --recover re-provisions a missing slot for an in-flight task. - asana-config.json: new .watcher.* knobs. - NEW lib/slots.js: atomic, lock-guarded slot allocator (lib + CLI). - NEW clone-ios-sim.sh / delete-ios-sim.sh: per-slot sim clone / delete. - NEW setup-task-workspace.sh / cleanup-task-workspace.sh: per-task worktree. - NEW gc-worktrees.sh: manual orphan cleanup. - NEW slots.json: slot state (initial empty template). - NEW README.md: design doc (architecture, knobs, env contract, limitations). All 5 smoke checks pass (cap, guardrail, worktree setup/cleanup, sim clone/boot/delete, atomic concurrent slot writes). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ept-udid, per-task worktree note)
| maxBuffer: 10 * 1024 * 1024, | ||
| }) | ||
| } catch (err) { | ||
| throw new Error(`curl failed for ${method} ${endpoint}: ${err.message}`) |
There was a problem hiding this comment.
🔒 Agentic Security Review
Severity: MEDIUM
err.message from execSync is rethrown verbatim here. In Node, that message includes the failed command string, which in this call contains Authorization: Bearer ${TOKEN}.
Impact: if this error is logged or surfaced, the Asana PAT can be exposed to logs/observers and used for API impersonation.
Reviewed by Cursor Security Reviewer for commit 50180c1. Configure here.
| const slot = slots.allocate({ task_gid: task.gid, worktree_path: worktreePath, sim_udid: simUdid }) | ||
| log(` slot ${slot.slot_index}: metro ${slot.metro_port}, sim ${simUdid}`) | ||
|
|
||
| const r = spawnSync(`${DIR}/spawn-test-session.sh`, [ |
There was a problem hiding this comment.
🔒 Agentic Security Review
Severity: HIGH
This watcher launches unattended --yolo sessions and then auto-accepts the permissions-bypass dialog before injecting /one-shot --yolo from Asana task content.
Impact: anyone who can move/create tasks in the watched project can trigger autonomous privileged command execution on the runner without a human confirmation boundary.
Reviewed by Cursor Security Reviewer for commit 6f8419f. Configure here.
…equired entries, attributable verification failures, dirty-tree autostash policy
…, coordinate taps last resort, standalone gui PR path for dep tasks); playbook minimum-viable amounts + piratechain local-disable mitigation + SideShift US geo-block entry; one-shot stale-prior-history guard for followup runs
…eAwaitingChoice; finalize-gate draft-PR rule, never-self-respawn heartbeat, tested-field no-carry-forward, proof-on-failed-read; ios-rn-build fallback Metro pin, setup-task-workspace existing-branch + gui generated-output backfill, watch-pr repo guard, runaway-guard heartbeat, memory-monitor UTC, asana attach dedupe, timeout sourced-guard; sim-testing-playbook NYM/Breez/Velodrome/clipboard entries + fixed-port slot-safety warning; scoped playbook-proposal rule; eval Actions section + A22 tested-field dimension + model pass-through; all-human-message nudge counter
…-build native-drift stamp guard; neutral-review watch fix; reviewThreads finalize-gate; tested field iOS Sim/Android Sim; pr-land default auto-merge; yolo land-task carve-out; iOS-default + Android assembleDebug testing path; watchdog/runaway-guard/resolve-run hardening
The npm-otp-required rule and publish sub-step now branch on the account 2FA type: TOTP runs with --otp=<code>, passkey/WebAuthn runs npm publish with no --otp flag (auth-type=web browser approval).
…ag commit on publish
…un|partial; A20 frontmatter + unrunnable-asset-proxy rules; watch-pr explicit repo; reorder-commits/worktree-prune/runaway-guard-retention fixes; install drunk-claude
…lti-repo subtask structure (PreToolUse hooks); session-watchdog park-timeout dedupe+escalation (no spam, no auto-shed)
…tart headless slot Metro on full-build path
…un followup capture; concession-validator too-large-unattempted deny; AskUserQuestion-deny + premature-stop hooks with watchdog stuck-escalation; eager subtask bodies in asana-get-context; asana-watcher duplicate-prompt fix
…ots); no-self-respawn PreToolUse hook (ScheduleWakeup/CronCreate/claude-respawn); concession-validator operator-authorized carve-out; Stop-hook marker fast-path + empty-status fail-open + blocking-stall-wait rule; watchdog idle-dirty-sim reclaim + delete-ios-sim --shutdown-only; README orchestration flow + mermaid diagrams
…umes a prior-transcript task on a fresh slot, else fresh-spawns); remove watchdog un-retire sweep; spawn-test-session auto-answers the resume-summary menu; watcher sends /one-shot after a resume so the agent picks up the followup; one-shot followup-reopens-status rewrite + README flow update + resume-task/ensure-sim-pool docs
There was a problem hiding this comment.
Stale comment
Security review rerun on current head completed.
Net-new finding identified (not duplicate of existing automation threads):
- MEDIUM: Unbounded attachment processing in
.cursor/skills/asana-get-context.shcan enable resource-exhaustion (large ZIP/PDF attachments are downloaded, extracted, and converted without hard limits), risking automation availability.Inline anchoring could not be posted in this run because GitHub rejected diff-position operations on this PR due diff size (
PullRequest.diff too_large).Sent by Cursor Security Agent: Security Reviewer
New refresh-master-build.sh keeps the master iOS sim's Edge.app current with origin/develop so pool clones (and the runs that use them) test against a fresh build instead of a pinned-stale one. Hooked into ensure-sim-pool.sh, the single provisioning choke point both the fresh-spawn and resume paths call. Cheap git fetch plus SHA compare against a new master-build.json marker: - develop unchanged: no-op - develop advanced, ios/Podfile.lock unchanged: JS-only, no rebuild (clones bundle JS live from Metro), just bump the marker - develop advanced, Podfile.lock changed (or no marker / --force): rebuild from develop, install on master, restamp, mark not-in_use pool slots dirty so ensure-sim-pool reclones from the fresh master in the same pass Blocking by design (a run waits for a fresh master before provisioning) and non-fatal (a fetch or build failure logs and continues on the last-good master so a broken develop cannot wedge the fleet). Lock-guarded for concurrent slots. Config: master_sim.refresh_on_develop (default true), master_sim.develop_ref (default origin/develop); env SKIP_MASTER_REFRESH=1 disables. New-machine import sets that env so a just-imported build is not immediately rebuilt. convention-sync: exclude *.lock from the agent-watcher extra tree.
… em dashes in chat)
…s PR review threads on a Pending bounce (no manual pr-address); step-1 open-PR guard
…); carry pending pr-land edit
…ize fragile rubric groundings to rule-id anchors; wire drift triage into author post-authoring and eval-run delivery
…l 1M) Adds Sonnet 5 to the agent_model option set and documents the per-task model/effort selection in the README. The resolver itself (spawn-test-session.sh reads agent_model / agent_effort from the task's Asana custom fields and pins --model / --effort, defaulting to .watcher.agent_model = claude-opus-4-8[1m] and .watcher.agent_effort = high) already landed via the prior tree mirror. This completes it: - agent_model options now Opus 4.8, Opus 4.7, Sonnet 5, Sonnet 4.6, all mapped to their [1m] 1M-context CLI strings. Haiku 4.5 was dropped (200K context cannot hold a 400k-token orch session, and it rejects --effort). - agent_effort options low/medium/high/xhigh/max, label = CLI value, default high. - One resolver covers both fresh spawns and follow-up resumes: both paths pass --task-gid to spawn-test-session.sh. Unselected fields fall back to config defaults; the Asana lookup is best-effort (token/API failure keeps defaults). Asana fields agent_model (1216249098365971) and agent_effort (1216249097796648) created on the test Kanban and attached to the project.
Fable 5 is 1M-context and supports --effort, so it joins the selectable agent_model set (top of the dropdown as the premium tier). Options now: Fable 5, Opus 4.8, Opus 4.7, Sonnet 5, Sonnet 4.6, all mapped to their [1m] CLI strings.
…ion acks (UNCOVERED-CHANGED re-flag on acked-rule change)
…lain-language dimension names end-to-end (schema, synthesis, skills); persist results.json; verifiers at low effort
…sh glossary + SHA-pinned permalinks, chat path:line cites)
…-containment, A26 attempt-log-fidelity, A27 shared-account-state-discipline, O10 master-sim-integrity; extend A1/A8/A10/A12/A15; 64 anchors tracked
…lete followup loss)
Root cause of the 1209296431612665 UTXO followup loss: every watcher resume
answers the resume menu with "Resume from summary", which compacts the session
from a summary built BEFORE the re-arm (transcript compact_boundary trigger
"manual", injected the same second resume-task relaunches). So a resumed agent's
memory cannot contain the followup comment that triggered the re-arm. Runs then
asserted "no comment newer than my run-report" from that stale summary, or
short-circuited on ignore-refired-one-shot ("completed moments ago"), and
re-Completed without fetching the task's comments. The re-armed task went
Pending -> Complete twice with zero new work.
Mechanical fix, prose rules alone cannot correct a stale world model:
- check-followup-scope.sh (new): live-fetch stories + attachments, compute the
run-report watermark (latest agent-run-report*.md created_at), enumerate every
comment newer than it, write /tmp/agent-followup-scope-<gid>.json.
- hooks/require-followup-scope-on-complete.sh (new PreToolUse gate, registered
in ~/.claude/settings.json): blocks update-status.sh <gid> Complete in
orchestrated sessions unless the marker exists AND its newest-comment
timestamp still matches the live newest comment (one cheap curl; fails open
on API errors when a marker exists). Scoped by AGENT_TASK_GID like the other
gates; operator shells are not gated.
- one-shot ignore-refired-one-shot: a re-fire on an already-FINISHED task is
not dismissible from memory; run the live scope check first, and only a
"0 newer comments" result earns a re-confirm-Complete.
- one-shot followup-scope-is-the-deliverable: the watermark enumeration must be
the live script fetch; recalled/summarized context never satisfies it.
- agent-eval rubric A1/A23 wording updated to the new expectations;
rubric-drift baseline reconciled (64 anchors clean).
- README: document the resume-compaction behavior and the new gate.
Verified: scope script against the real task (surfaces the missed UTXO ask and
7 other post-watermark comments); hook pipe-tested across allow/no-marker/
stale-marker/non-Complete/unscoped cases (blocks exit 2 with actionable stderr).
…00, MCP daemon +2000), capture-buy-quote device+port pinning (no more raw booted), hook requires both flags; maestro 2.6.1
…one-shot cheese-build-on-green (pointer-only test-* push before Complete); pr-land build-field-routing (field-driven staging cherry-pick, never land test-*); cheese pointer-not-workspace; A28 build-field-honored
…ia pr-land (task-URL handoff for dep ordering); Force Land field (Land Approved) as no-review override; skip cheese when landing; A8 extended
…ixes From the FIO send error eval (1216246093027338): the run drove the sim for 25+ minutes under agent_status=Planning and only set Testing inside its block call. Rather than patch the investigation-only case, one-shot's agent-status-on-pending-task now states the generic principle: the phase status is set when that KIND of work starts (reading/planning=Planning, editing code=Developing, exercising behavior=Testing, PR watched=Reviewing), mapped by work kind on non-feature run shapes; skipping a phase with no work is correct, doing work under an earlier phase's status is not. Rubric status-hygiene (A4) row updated to match. Objective /im review, fixes applied: - read-coding-standards pointed at .cursor/rules/typescript-standards.mdc (repo-relative), which exists in no repo; now the canonical home path. - GIT_BRANCH_PREFIX is unset in orchestrated shells (.zshrc only): branch naming now documents the jon fallback, and spawn-test-session.sh exports it into agent sessions. - no-impl-before-confirm gains the explicit --yolo carve-out one-shot already imposes (rules no longer conflict across skills). - step 0 no longer re-runs /asana-plan when one-shot already produced the plan this session. - step 5 timeout phrasing tool-agnostic (block_until_ms is Cursor-only). - step 6 retrospective folds into the run report on orchestrated runs instead of a separate deliverable. rubric-drift reconciled: 70 anchors clean.
…eted false positives + testing gaps + Force-Land note, else none); Finalize Gate checklist section in run report; bullets-over-prose format; drop reversibility annotations; A9/A20 updated




edge-dev-agents
Complete agent-assisted development workflow for Edge repositories:
slash skills, companion scripts, coding standards, review standards,
and meta-tooling for maintaining the workflow itself.
The distributable Cursor content lives under
.cursor/. This repo is theversioned home for those skills, rules, scripts, and docs.
The canonical local doc lives at
~/.cursor/README.md. During/convention-sync, that file is mirrored toedge-dev-agents/README.md, andthe repo copy should not keep a second
.cursor/README.md.Installation
Fresh machine (one command): clone this repo and run the bootstrap — it
installs everything (cursor skills/rules, the orchestration system, and shared
memories) into your home dir, seeds
credentials.jsonfrom the example, andlinks skills + shared memory:
For incremental onboarding instead of the full bootstrap:
1. Set the required env var in your
~/.zshrc:This drives branch naming and PR discovery across the workflow.
2. Sync the repo copy into
~/.cursor/:This repo treats
~/.cursor/as the canonical working copy. Use/convention-syncto move local changes intoedge-dev-agents, or run thecompanion script directly when onboarding:
~/.cursor/skills/convention-sync/scripts/convention-sync.sh \ --repo-to-user --stage3. Verify prerequisites:
ghCLI:gh auth loginjq:brew install jqASANA_TOKENenv var for Asana-backed workflowsTable of Contents
Orchestration & Memory
The orchestration system runs Asana tasks to PRs autonomously: a watcher picks up
Pendingtasks, spawns one isolated agent session per task, and a watchdog tendsthe live sessions. Post-hoc evals grade what each run did.
How a run flows
agent-watcher/asana-watcher.js, launchd every 120s): polls theproject for
agent_status = Pending. It skips the tick when a resourceguardrail trips (1-minute load over
max_load_avg, or free RAM undermin_free_ram_gb) or the concurrency cap (max_concurrent, default 4) is full.For each pickable task it refreshes the iOS-sim pool and allocates a slot (a git
worktree, a cloned simulator, a Metro port). Before recloning the pool it runs
refresh-master-build.sh: ifdevelophas advanced and its native side(
ios/Podfile.lock) changed, it rebuilds the master sim fromdevelopandreclones so runs test against a current build instead of a pinned-stale one. A
JS-only
developadvance skips the rebuild (clones bundle JS live from Metro);the check is a cheap
git fetchplus a SHA compare, and a build failure isnon-fatal (provisioning continues on the last-good master). A NEVER-run task is spawned fresh:
agent_status = Planning, a tmux sessionclaude-asana-(gid)running/one-shot --yolo (task-url). A task with a PRIOR transcript (a revisit) isRESUMED instead (its conversation restored on the fresh slot, via
resume-task).Note the resume answers the resume menu with "Resume from summary", which
COMPACTS the conversation from a summary built before the re-arm, so a resumed
agent's memory never includes the followup comment that triggered the re-arm.
That is why finalize is gated on a live scope check:
check-followup-scope.shfetches the task's comments and attachments, lists every operator comment newer
than the latest
agent-run-report*.mdwatermark, and writes a marker; therequire-followup-scope-on-complete.shPreToolUse hook blocksupdate-status.sh (gid) Completeunless that marker exists and still matchesthe live newest comment. Recalled context never stands in for the fetch.
So re-engaging a finished task is one signal: set it back to
Pendingand itcontinues with memory plus working resources, never a fresh session. This is the
single re-engagement entry point. Both the fresh-spawn and the resume path route
through
spawn-test-session.sh, which pins the session's model and reasoningeffort from the task's
agent_model/agent_effortAsana fields: the selectedagent_modeloption maps to a CLI model string (all 1M-context: Fable 5, Opus4.8, Opus 4.7, Sonnet 5, Sonnet 4.6), the
agent_effortoption is the CLI level directly(low/medium/high/xhigh/max). Unset falls back to the config defaults
(
.watcher.agent_model= Opus 4.8 1M,.watcher.agent_effort= high), so newtasks and follow-ups honor the same per-task overrides.
/one-shot --yolo, a single agent turn): seven phases, statusadvanced via
update-status.shat each boundary. Planning (/asana-plan),Developing (
/im), local verify (/build-and-test), Reviewing (/pr-create),Testing (CI watch + reviewer bots), Complete. The agent runs hands-off: no
interactive prompts, no self-respawn, every wait a bounded blocking in-turn call.
Completeis set only when every primary PR is CI-green,every reviewer bot is clean on HEAD, and there are zero unresolved bot review
threads (bots matched by GraphQL
__typename == "Bot", so Cursor'scursorandcursor[bot]logins both count).agent-watcher/session-watchdog.js, launchd every 120s): tendslive sessions. RC-bridge revive (only when the bridge is dead), completion sweep
(
Completeretiresclaude-asana-(gid)todone-asana-(gid), freessim/Metro/slot, keeps claude alive for re-engagement), blocked sweep (sheds
sim/Metro while a human is needed), GC (keep newest
keep_completed_sessions/keep_completed_worktrees), orphan-Metro reap, idle-dirty-sim reclaim, andoperator escalation for parked prompts or stuck sessions. It does NOT re-engage
finished tasks: that is the watcher's job (Pending → resume), so the watchdog and
watcher are decoupled.
/resolve-runbuilds an evidence manifest per run;/agent-evalgrades process compliance and outcome honesty (A-dimensions),/orch-evalgrades infrastructure health (O-dimensions), and/eval-runorchestrates a cohort.
flowchart TD A["Asana project (Pending tasks)"] -->|"watcher tick 120s"| B{"guardrail and cap OK?"} B -- no --> A B -- yes --> C["allocate slot: worktree + cloned sim + Metro port"] C --> D["spawn tmux claude-asana-GID (claude --rc --yolo /one-shot)"] D --> E["/one-shot 7 phases: Planning, Developing, Reviewing, Testing"] E --> F{"finalize-gate: CI green + bots clean + 0 unresolved threads"} F -- "not green" --> E F -- green --> G["agent_status = Complete"] G -->|"watchdog completion sweep"| H["retire to done-asana-GID; free sim, Metro, slot; claude kept alive"] H -->|"operator sets Pending (revisit resumes with memory)"| A H -->|"beyond keep_completed_sessions"| I["reaped"]The hands-off contract is enforced by deterministic PreToolUse and Stop hooks
(active only when
AGENT_TASK_GIDis set), not merely documented:Distribution (what syncs)
Beyond cursor skills/rules, this repo mirrors two more portable trees so a
second Mac is reproducible from a single clone +
./bootstrap.sh:agent-watcher/— the autonomous agent orchestration system (Asanawatcher daemon + worktree/iOS-sim pool helpers + watchdog). Canonical home is
~/.config/agent-watcher(XDG config;~/.agentsis not an establishedstandard). Committed: scripts,
*.js,asana-config.json,README.md,oom-repro/HANDOFF.md+scripts/, andcredentials.example.json. Nevercommitted:
credentials.json(secret) and machine-local state(
pool.json,slots.json,watchdog-state.json,*.state,*.log,oom-repro/forensics,oom-repro/logs).memory-shared/+bin/link-shared-memory.sh— cross-cutting Claudememory notes that should surface regardless of working directory. Canonical
home
~/.claude/memory-shared;link-shared-memory.shsymlinks them into theper-project auto-memory dirs (
~/.claude/projects/<project>/memory/) andmaintains a managed block in each
MEMORY.md. Claude auto-memory itself ismachine-local (per Anthropic docs) and is intentionally NOT synced — only the
shared store is. The only officially global Claude file is
~/.claude/CLAUDE.md(generated here from always-apply rules).
/convention-synckeeps all of the above in sync (home → repo);bootstrap.shdoes the reverse (repo → home) on a new machine.
Architecture
Separation of concerns:
SKILL.md) define workflows, rules, and step ordering..sh,.js) handle deterministic work like git,GitHub, Asana, and JSON processing.
.mdc) provide persistent guidance that gets loaded by context.together.
All GitHub API work uses
ghCLI. Deterministic git operations should live inscripts, not be re-described independently across skills.
Skills (Slash Skills)
Core Implementation
/im/one-shot/pr-create/dep-pr/changelogPlanning and Context
/asana-plan/task-review/qReview and Landing
/pr-review/pr-address/pr-land/staging-cherry-pickAsana and Utility
/asana-task-update/standup/chat-audit/convention-sync~/.cursor/with this repo, mirror the local README to repo root, and update PR descriptions fromREADME.md/author/fix-eslintCompanion Scripts
PR Operations
pr-create.shgh pr createpr-address.shgh apiREST + GraphQLgithub-pr-review.shgh pr view+gh apigithub-pr-activity.shgh api graphqlPR Landing Pipeline (
/pr-land)pr-land-discover.shpr-land-comments.shgit-branch-ops.shpr-land-prepare.shpr-land-merge.shpr-land-publish.shpr-land-extract-asana-task.shupgrade-dep.shdevelopfirst.staging-cherry-pick.shstagingverify-repo.shBuild, Lint, and Analysis
lint-commit.shlint-warnings.shinstall-deps.shcursor-chat-extract.jsAsana and Portability
asana-get-context.shasana-task-update.shasana-create-dep-task.shasana-whoami.shconvention-sync.sh~/.cursor/andedge-dev-agentsin either direction, mirroring~/.cursor/README.mdto repo rootREADME.mdgenerate-claude-md.sh~/.claude/CLAUDE.mdfrom always-apply rulestool-sync.shport-to-opencode.shShared Modules
edge-repo.jsghhelpers for thepr-landpipelineRules (
.mdcfiles)workflow-halt-on-error.mdcload-standards-by-filetype.mdcanswer-questions-first.mdcno-format-lint.mdctypescript-standards.mdcreview-standards.mdceslint-warnings.mdcafter_each_chat.mdcDesign Principles
work belongs in shared scripts.
ghover raw GitHub HTTP calls. Use the authenticated CLI for GitHubworkflows.
should live in one script and be consumed by multiple skills.
evaluating lint/type failures.
script instead of patching around it in an ad-hoc way.
~/.cursor/is the working source of truth;edge-dev-agentsis the distribution and review copy.