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.
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
…mand-detection) From the CEX Signing BTC followup (1216403654258303): the agent resolved the operator ask /code-review by listing skill directories on disk, missed it (code-review is a bundled Claude Code skill with no SKILL.md anywhere), and fell back to the similarly-named pr-review until the operator interrupted. The rule itself taught that path: it predates bundled skills and hard-coded 'read ~/.cursor/skills/<word>/SKILL.md, else not found'. slash-command-detection now: - resolves in order: session skill listing (Skill tool; the ONLY place bundled/plugin skills exist) -> ~/.cursor/skills on disk -> not-found - forbids nearest-name substitution (no-silent-substitution applied to skills: pr-review is not code-review) - normalizes em/en dashes in flag positions to -- before interpreting args (phone keyboards auto-convert; the verbatim em dash silently dropped --comment, so the PR-454 review ran without posting) - applies to slash commands in ANY text channel (Asana comments surfaced as followup scope, not just the composer) ~/.claude/CLAUDE.md regenerated from the always-apply rules.
| const path = require("path") | ||
|
|
||
| const files = process.argv.slice(1) | ||
| const cmd = "./node_modules/.bin/eslint --format json " + files.map(f => JSON.stringify(f)).join(" ") |
There was a problem hiding this comment.
🔒 Agentic Security Review
Severity: HIGH
execSync is invoked with shell command strings built from filenames (files.map(f => JSON.stringify(f)).join(" ")). Quoting with JSON.stringify is not shell-safe against command substitution, so a filename like $(touch /tmp/pwned).ts can execute commands when this lint helper runs.
Impact: Running the commit helper on attacker-controlled branch content can trigger arbitrary command execution in the caller's environment.
Reviewed by Cursor Security Reviewer for commit a11aebc. Configure here.
| CMD=$(printf '%s' "$IN" | jq -r '.tool_input.command // empty' 2>/dev/null || true) | ||
| # claude re-launch (resume/print) or a backgrounded claude or a /loop invocation. | ||
| if printf '%s' "$CMD" | grep -qE '(^|[;&|]|[[:space:]])claude([[:space:]]+[^|;&]*)?[[:space:]]+(--resume|-p|--print)([[:space:]]|$)' \ | ||
| || printf '%s' "$CMD" | grep -qE '(^|[;&|]|[[:space:]])claude[^|;]*&[[:space:]]*$' \ |
There was a problem hiding this comment.
🔒 Agentic Security Review
Severity: MEDIUM
The backgrounded-claude detector only matches when & is at end-of-command. Variants like claude ... & disown or claude ... & wait bypass this regex and evade the self-respawn block.
Impact: Task-controlled instructions can bypass the anti-respawn guard and launch additional autonomous sessions, undermining the intended containment control.
Reviewed by Cursor Security Reviewer for commit a11aebc. Configure here.
… form regression) Followup reports written after compaction reverted to pre-fix form (5x Reversible. trailers on 2026-07-09) while the rule's one-line summary sat in fresh context: the template's inline contract only binds when the template is read, and a prior read does not survive compaction. - one-shot report-as-attachment: RE-READ the template immediately before writing EVERY report, including followups N compactions later. - hooks/require-clean-run-report.sh (new PreToolUse gate): lints the report file when asana-task-update.sh attaches an agent-run-report*: blocks reversibility annotations (Irreversible allowed), em dashes, and missing template sections (headings read live from the template so the gate never drifts). Form-only: the block message forbids deleting facts to pass. Formatter-ready: the checks define the form contract a future form-only formatter subagent would enforce; the gate stays as backstop. - rubric report-discipline (A9) grounding cites the re-read + gate; drift baseline reconciled. Pipe-tested: bad report (Reversible. + em dash + missing sections) blocks with per-line citations; irreversible-note passes; clean template-complete report passes; non-report attaches and operator shells unaffected.
…) instead of whole-transcript grep; ambiguity guard lists multi-task matches; --chat mode forks a past run into a watchdog-covered RC discussion session; fix listing SIGPIPE
…laude chat panes (transcripts survive; resurrect via resume-agent --chat)
…ck + scorecard) The 2026-07-09 cohort showed testing OUTCOMES are now honest (testing-depth 5 GOOD / 1 BAD vs 4-of-7 skips on 2026-06-10) while saying nothing about HOW runs got there: the live Nym session ground through 6 hook blocks, repeated builds, and an hour of Testing with zero drives, invisible to every outcome dimension. Compliance-through-forced-retry and first-try correctness graded identically. - resolve-run: manifest gains a friction block, collected in the existing single-pass probe scan: hook_blocks (total + by_hook), tool_errors, build_invocations, maestro_run_calls, compact_boundaries, and first_*_ts timestamps (Testing transition, first drive, first hook block) so graders and scripts can compute deltas. probe_index unchanged otherwise. - agent-eval rubric: new process-friction dimension (A29) grades effort shape from that block: same-hook 3+ blocks, error retry loops, drive-before-Testing (also status-hygiene), hour-scale pre-drive grind. A cross-run recurring footgun is flagged as a skill/infra finding: the fix is making the footgun impossible, not catching it repeatedly. NA for pre-friction manifests. - friction-scorecard.sh (new, resolve-run/scripts): zero-LLM TSV trend table per run (hook blocks, errors, builds, compactions, drives, attempt walls/ successes, Testing-to-first-drive minutes) for between-cohort tracking. Verified on the FIO run: 6 hook blocks across 4 hooks, 16 tool errors, 31 drives, and testing_to_drive_min = -25 (the drive preceded the Testing transition), the status-hygiene finding now computable without a grader.
| - `word` contains only lowercase letters and hyphens (e.g., `/im`, `/pr-create`, `/author`) | ||
| - `/word` is NOT inside a file path, URL, or code block | ||
|
|
||
| This applies to slash commands arriving through ANY text channel — a typed message, an Asana comment surfaced as followup scope, a task description — not just the composer. |
There was a problem hiding this comment.
🔒 Agentic Security Review
Severity: HIGH
This rule expands slash-command execution to untrusted external text channels (for example Asana follow-up comments and task descriptions) and then routes detected /word tokens directly into skill execution.
Impact: anyone who can inject task/comment text into that intake path can trigger privileged automation workflows under the agent identity, creating an unauthorized command-execution path across trust boundaries.
Reviewed by Cursor Security Reviewer for commit 87f0942. Configure here.
…o resume-agent --chat discussion forks are never resolved (and graded) as the run; herestring+grep -a to dodge pipefail SIGPIPE and BSD binary detection
VERSION TRACEABILITY (new): every spawn/resume segment now records which orch version governs it. stamp-orch-version.sh computes content digests (mtime-free) of the behavior-governing trees (run-facing skills, rules, watcher, hooks, settings hooks section) plus a separate eval_digest so rubric edits do not fork the orch version; ties to the synced repo via repo_head + advisory dirty components; captures claude CLI version, model, effort, session uuid. Appends to versions/<gid>.jsonl, exports AGENT_ORCH_VERSION (new run-report frontmatter field), and resolve-run lifts the stamps into the manifest as `versions`. Cohort evals can now slice findings by the version actually in force per segment; "run predates rule X" becomes mechanical via repo_head. Zero token cost: pure local hashing at spawn (<1s). FOOTGUN FIXES (from the combined eval's top recurrences): - block-piped-watcher-scripts.sh now REWRITES the command (PreToolUse updatedInput) instead of blocking: strips the pointless `| tail/head` segment and lets the corrected call run, ending the most recurrent friction finding (11 fires in one run). Concession-shaped commands (--blocked / Complete / pr-create) still hard-block so the rewrite can never short-circuit require-concession-validation; complex pipelines fall back to the block. 5 pipe-test cases. - require-concession-validation.sh hash bug: PreToolUse sees commands UNEXPANDED, so --reason "$(cat file)" hashed the literal substitution text and genuine ALLOW verdicts bounced (the FIO double-block). The gate now expands $(cat F) / $(< F) / backtick-cat by reading the file, blocks with guidance when the file is unreadable. 3 test cases incl. the exact FIO shape. - bare-npm/sfw bounce deferred: socket-guard.mjs is security tooling outside the orch trees; not modified. TESTING FRICTION (first mechanization): slot-preflight.sh answers, in ONE call, the questions runs kept re-deriving expensively: sim exists/booted (boots it), Metro port free/mine/squatted, app installed, native drift (installed .agent-native-build-stamp vs worktree ios/Podfile.lock), node_modules present. Emits a final PLAN line (ready | install | js-only | full-rebuild) the agent must obey instead of guessing build tiers. Wired as build-and-test rule preflight-before-build-decisions; anchored into the process-friction (A29) rubric grounding. Verified live against the master sim (booted it, detected the real post-Jul-3 native drift, correct full-rebuild plan). Also carried: eval-run.workflow.js (companion workflow materialized during the 2026-07-09 combined eval). rubric-drift reconciled, 72 anchors clean.
| CMD=$(jq -r '.tool_input.command // empty' 2>/dev/null || true) | ||
| [ -n "$CMD" ] || exit 0 | ||
|
|
||
| case "$CMD" in |
There was a problem hiding this comment.
🔒 Agentic Security Review
Severity: HIGH
The allowlist check exits early on any command string that contains lint-commit.sh. This is a fail-open pattern because the hook never reaches raw git commit blocking logic after this match.
Impact: A crafted command can bypass commit guardrails and run unrestricted raw commits that policy intends to block.
Reviewed by Cursor Security Reviewer for commit c466b05. Configure here.
|
|
||
| echo "$CMD" | grep -q "resolveReviewThread" || exit 0 | ||
|
|
||
| case "$CMD" in |
There was a problem hiding this comment.
🔒 Agentic Security Review
Severity: HIGH
Thread-resolution blocking is bypassable because exemptions are substring matches (for example pr-address.sh, bugbot) over the raw command text, with immediate allow on match.
Impact: A crafted command can execute raw resolveReviewThread mutations outside the intended controlled workflow, weakening review integrity controls.
Reviewed by Cursor Security Reviewer for commit c466b05. Configure here.
|
|
||
| # Determine whether this command is a gated concession, and its reason. | ||
| KIND="" REASON="" | ||
| case "$CMD" in |
There was a problem hiding this comment.
🔒 Agentic Security Review
Severity: HIGH
Critical enforcement gates depend on script-name substring matching (for example *update-status.sh*, *pr-create.sh*) instead of validated executable identity. Invocation indirection can evade these checks.
Impact: Policy gates for blocked state, completion, and concession validation can be bypassed, allowing unauthorized workflow state transitions.
Reviewed by Cursor Security Reviewer for commit c466b05. Configure here.
Playbook (sim-testing-playbook.md): the 2026-07-09 combined eval's testing gotchas, each rediscovered at ~10min/run, now permanent: - Money/accounts: edge-funds funding snapshot (BTC empty; funded list), EVM second-send block. - Investigate-cheap: bitcoinjs-message byte-compare for signature fixes; Houdini fund-free route verification via the partner API. - Driving mechanics: EVM send-flow recipe (lowercase 0x, confirmSliderThumb); edge-exchange-plugins installed-bundle verification. - Asset/provider: TON funded wallets + self-send amount, toncenter 429 cooldown, Maya first-fresh-quote rule + USDT->ETH pair, SideShift per-egress geo-gate check, Xgram XMR-only pair recipe with floors. (11th proposal deduped: Botanix/BSV keys-only exclusion already in the playbook.) Preflight bridge: slot-preflight.sh now prints the exact INVOKE: command for its PLAN (ios-rn-build with/without --force-rebuild), and the preflight-before-build-decisions rule requires running it verbatim: no flag-guessing tier decisions left to the agent.
| } | ||
|
|
||
| function git(cmd) { | ||
| return execSync(`git ${cmd}`, { encoding: "utf8" }).trim(); |
There was a problem hiding this comment.
🔒 Agentic Security Review
Severity: HIGH
git() executes execSync(git ${cmd}) and later call sites interpolate defaultBranch into cmd (for example log origin/${defaultBranch}..HEAD and diff origin/${defaultBranch}..HEAD). Because this is shell string execution, a crafted ref name can inject shell metacharacters.
Impact: Running PR creation in a repository with attacker-controlled branch metadata can lead to arbitrary command execution in the local automation/runtime user context.
Reviewed by Cursor Security Reviewer for commit 722baac. Configure here.




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.