@reddb-io/red-skills-dev 2.75.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +30 -0
  3. package/package.json +32 -0
  4. package/skills/engineering/README.md +36 -0
  5. package/skills/engineering/_report-runtime/WRAPPER.md +47 -0
  6. package/skills/engineering/afk/AGENT-PROMPT.md +295 -0
  7. package/skills/engineering/afk/SAFETY.md +103 -0
  8. package/skills/engineering/afk/SKILL.md +160 -0
  9. package/skills/engineering/afk/TROUBLESHOOTING.md +260 -0
  10. package/skills/engineering/afk/actions-lane.md +213 -0
  11. package/skills/engineering/afk/bin/README.md +29 -0
  12. package/skills/engineering/afk/bin/afk.mjs +51 -0
  13. package/skills/engineering/afk/defaults/cargo-pre-worktree.sh +37 -0
  14. package/skills/engineering/afk/defaults/envelope-post-attempt.sh +47 -0
  15. package/skills/engineering/afk/defaults/gradle-pre-worktree.sh +42 -0
  16. package/skills/engineering/afk/defaults/heartbeat-post-attempt.sh +43 -0
  17. package/skills/engineering/afk/defaults/validation-post-merge.sh +77 -0
  18. package/skills/engineering/afk/detectors/README.md +72 -0
  19. package/skills/engineering/afk/detectors/cargo.sh +27 -0
  20. package/skills/engineering/afk/detectors/gradle.sh +35 -0
  21. package/skills/engineering/afk/docs/BOOT-SWEEPS.md +81 -0
  22. package/skills/engineering/afk/docs/CONFIG.md +238 -0
  23. package/skills/engineering/afk/docs/ENVELOPE.md +177 -0
  24. package/skills/engineering/afk/docs/HANDOFF.md +70 -0
  25. package/skills/engineering/afk/docs/LIVENESS.md +56 -0
  26. package/skills/engineering/afk/docs/OPERATIONS.md +409 -0
  27. package/skills/engineering/afk/examples/only-mine.sh +32 -0
  28. package/skills/engineering/afk/examples/red-afk-attempt-action.yml +53 -0
  29. package/skills/engineering/afk/examples/rs-afk-attempt.yml +76 -0
  30. package/skills/engineering/afk/fleet.md +127 -0
  31. package/skills/engineering/afk/hooks/red-cargo +44 -0
  32. package/skills/engineering/afk/hooks/red-envelope +50 -0
  33. package/skills/engineering/afk/hooks/red-gradle +50 -0
  34. package/skills/engineering/afk/hooks/red-heartbeat +46 -0
  35. package/skills/engineering/afk/hooks/red-validation +79 -0
  36. package/skills/engineering/afk/monitor.md +170 -0
  37. package/skills/engineering/afk/runner-claude-minimax.md +71 -0
  38. package/skills/engineering/afk/runner-claude.md +67 -0
  39. package/skills/engineering/afk/runner-codex.md +138 -0
  40. package/skills/engineering/afk/runner-hermes.md +40 -0
  41. package/skills/engineering/afk/runner-opencode.md +142 -0
  42. package/skills/engineering/ask-red/SKILL.md +137 -0
  43. package/skills/engineering/audit-skills/SKILL.md +46 -0
  44. package/skills/engineering/code-review/SKILL.md +106 -0
  45. package/skills/engineering/context/SKILL.md +142 -0
  46. package/skills/engineering/curate/SKILL.md +133 -0
  47. package/skills/engineering/daily-review/SKILL.md +68 -0
  48. package/skills/engineering/dashboard/SKILL.md +44 -0
  49. package/skills/engineering/diagnose/SKILL.md +155 -0
  50. package/skills/engineering/diagnose/scripts/hitl-loop.template.sh +41 -0
  51. package/skills/engineering/go/SKILL.md +144 -0
  52. package/skills/engineering/go/TROUBLESHOOTING.md +81 -0
  53. package/skills/engineering/ground-truth/SKILL.md +84 -0
  54. package/skills/engineering/hitl/SKILL.md +171 -0
  55. package/skills/engineering/hitl/TROUBLESHOOTING.md +76 -0
  56. package/skills/engineering/implement/SKILL.md +69 -0
  57. package/skills/engineering/improve-codebase-architecture/DEEPENING.md +37 -0
  58. package/skills/engineering/improve-codebase-architecture/INTERFACE-DESIGN.md +44 -0
  59. package/skills/engineering/improve-codebase-architecture/LANGUAGE.md +53 -0
  60. package/skills/engineering/improve-codebase-architecture/SKILL.md +81 -0
  61. package/skills/engineering/model-tier-policy/SKILL.md +83 -0
  62. package/skills/engineering/prototype/LOGIC.md +79 -0
  63. package/skills/engineering/prototype/SKILL.md +30 -0
  64. package/skills/engineering/prototype/UI.md +112 -0
  65. package/skills/engineering/red-doctor/APPLY.md +35 -0
  66. package/skills/engineering/red-doctor/SKILL.md +172 -0
  67. package/skills/engineering/red-gains/SKILL.md +49 -0
  68. package/skills/engineering/red-setup/INTERVIEW.md +266 -0
  69. package/skills/engineering/red-setup/ISSUE-SWEEP.md +18 -0
  70. package/skills/engineering/red-setup/REFERENCE.md +35 -0
  71. package/skills/engineering/red-setup/SKILL.md +58 -0
  72. package/skills/engineering/red-setup/WORKFLOWS.md +74 -0
  73. package/skills/engineering/red-setup/WRITE-CONTRACT.md +151 -0
  74. package/skills/engineering/red-setup/config-template.yaml +174 -0
  75. package/skills/engineering/red-setup/domain.md +52 -0
  76. package/skills/engineering/red-setup/issue-tracker-github.md +88 -0
  77. package/skills/engineering/red-setup/rsp-retirement-runbook.md +35 -0
  78. package/skills/engineering/red-setup/scripts/install-runtime-shim.sh +189 -0
  79. package/skills/engineering/red-setup/triage-labels.md +176 -0
  80. package/skills/engineering/red-setup/workflows/red-issues-needs-triage.yml +36 -0
  81. package/skills/engineering/red-statusline/HOST-NOTES.md +180 -0
  82. package/skills/engineering/red-statusline/SKILL.md +58 -0
  83. package/skills/engineering/report-bug/SKILL.md +155 -0
  84. package/skills/engineering/resolving-merge-conflicts/SKILL.md +143 -0
  85. package/skills/engineering/retake/SKILL.md +184 -0
  86. package/skills/engineering/review-adrs/SKILL.md +125 -0
  87. package/skills/engineering/start/ADR-FORMAT.md +47 -0
  88. package/skills/engineering/start/CONTEXT-FORMAT.md +77 -0
  89. package/skills/engineering/start/DOC-LANDING-FINALIZER.md +9 -0
  90. package/skills/engineering/start/SKILL.md +179 -0
  91. package/skills/engineering/tdd/SKILL.md +96 -0
  92. package/skills/engineering/tdd/deep-modules.md +33 -0
  93. package/skills/engineering/tdd/interface-design.md +31 -0
  94. package/skills/engineering/tdd/mocking.md +59 -0
  95. package/skills/engineering/tdd/tests.md +85 -0
  96. package/skills/engineering/to-spec/SKILL.md +104 -0
  97. package/skills/engineering/to-tickets/SKILL.md +158 -0
  98. package/skills/engineering/triage/AGENT-BRIEF.md +207 -0
  99. package/skills/engineering/triage/OUT-OF-SCOPE.md +101 -0
  100. package/skills/engineering/triage/SKILL.md +176 -0
  101. package/skills/engineering/verify/SKILL.md +136 -0
  102. package/skills/engineering/wayfinder/SKILL.md +128 -0
  103. package/skills/engineering/zoom-out/SKILL.md +51 -0
  104. package/skills/engineering/zoom-out/scripts/graph-mode-plumbing.sh +36 -0
  105. package/skills/engineering/zoom-out/scripts/tests/contract.test.sh +168 -0
  106. package/skills/knowledge/README.md +5 -0
  107. package/skills/knowledge/research/SKILL.md +76 -0
  108. package/skills/misc/README.md +8 -0
  109. package/skills/misc/branch-lock/SKILL.md +187 -0
  110. package/skills/misc/branch-lock/scripts/branch-lock-hook.sh +113 -0
  111. package/skills/misc/branch-lock/scripts/branch-lock-session-start.sh +81 -0
  112. package/skills/misc/branch-lock/scripts/branch-lock.sh +68 -0
  113. package/skills/misc/branch-lock/scripts/lib/dev-config.sh +126 -0
  114. package/skills/misc/branch-lock/scripts/lib/git-command-classifier.sh +198 -0
  115. package/skills/misc/branch-lock/scripts/lib/lock-store.sh +67 -0
  116. package/skills/misc/branch-lock/scripts/lib/scope-resolver.sh +43 -0
  117. package/skills/misc/branch-lock/scripts/tests/branch-lock-cli.test.sh +114 -0
  118. package/skills/misc/branch-lock/scripts/tests/claude-plugin-hook.test.sh +117 -0
  119. package/skills/misc/branch-lock/scripts/tests/codex-hook.test.sh +229 -0
  120. package/skills/misc/branch-lock/scripts/tests/dev-config.test.sh +99 -0
  121. package/skills/misc/branch-lock/scripts/tests/git-command-classifier.test.sh +187 -0
  122. package/skills/misc/branch-lock/scripts/tests/lock-store.test.sh +90 -0
  123. package/skills/misc/branch-lock/scripts/tests/scope-resolver.test.sh +63 -0
  124. package/skills/misc/branch-lock/scripts/tests/session-start.test.sh +123 -0
  125. package/skills/misc/git-guardrails-claude-code/SKILL.md +105 -0
  126. package/skills/misc/git-guardrails-claude-code/scripts/block-dangerous-git.sh +231 -0
  127. package/skills/misc/git-guardrails-claude-code/scripts/tests/block-dangerous-git.test.sh +143 -0
  128. package/skills/misc/migrate-to-shoehorn/SKILL.md +96 -0
  129. package/skills/misc/setup-pre-commit/SKILL.md +94 -0
  130. package/skills/productivity/README.md +8 -0
  131. package/skills/productivity/ff/SKILL.md +116 -0
  132. package/skills/productivity/handoff/SKILL.md +37 -0
  133. package/skills/productivity/reflect/SKILL.md +18 -0
  134. package/skills/productivity/write-a-skill/SKILL.md +190 -0
  135. package/skills/productivity/write-a-skill/WRITING-STYLE.md +62 -0
@@ -0,0 +1,260 @@
1
+ # /afk Troubleshooting
2
+
3
+ Use these playbooks when `/afk` reaches a confusing terminal state. Follow the `write-a-skill` TROUBLESHOOTING convention: Symptom -> Confirm -> Recover -> Root fix.
4
+
5
+ ## Gate census when ready-for-agent is empty
6
+
7
+ ### Symptom
8
+
9
+ `/afk` reports zero `ready-for-agent` issues while the open non-Spec backlog is
10
+ not empty.
11
+
12
+ ### Confirm
13
+
14
+ 1. Run the read-only queue view: `red-skills-dev monitor --once`.
15
+ 2. Compare `ready-for-agent` with open non-Spec issues in the issue tracker.
16
+ 3. Census the gates by label family: `blocked:dependency`, `needs-triage`,
17
+ `ready-for-human`, `type:spec`, and any unsupported `blocked:*` label.
18
+ 4. For each `blocked:dependency`, inspect every `req:N` target. A delivered but
19
+ still-open Spec can strand dependents even when the dependency is complete.
20
+
21
+ ### Recover
22
+
23
+ 1. Do not stop as a clean drain while executable backlog remains.
24
+ 2. Clear the highest-leverage false gate first: finish triage, close delivered
25
+ dependency targets, resolve stale human parks, or move eligible work back to
26
+ `ready-for-agent`.
27
+ 3. Re-run the queue view after each batch and continue until every open
28
+ executable issue is either agent-ready or gated for a real pending reason.
29
+
30
+ ### Root fix
31
+
32
+ This manual census is a stopgap for the queue reconciler tracked in #1739.
33
+
34
+ ## False main-red verification
35
+
36
+ ### Symptom
37
+
38
+ Validation claims `main` is red, but the failure appears only in a local or
39
+ agent-specific probe environment.
40
+
41
+ ### Confirm
42
+
43
+ 1. Check the actual GitHub Actions runs for `main`, not only the local command
44
+ tail.
45
+ 2. Check whether recent release tags were cut from the same `main` lineage.
46
+ 3. Re-run the exact configured gate command. Do not add wider target sets,
47
+ stricter flags, or extra lint rules.
48
+ 4. If the exact gate is green and the stricter local probe is the only failure,
49
+ classify the finding as a probe-environment mirage.
50
+
51
+ ### Recover
52
+
53
+ 1. Close the validation park with concrete evidence: current `main` CI state,
54
+ relevant release tag, and the exact gate command that passed.
55
+ 2. Link the probe-environment bug instead of blocking the validation lane on a
56
+ check the gate does not run.
57
+ 3. Resume or requeue the issue only after the real configured gate is green.
58
+
59
+ ### Root fix
60
+
61
+ This manual verification is a stopgap for the queue reconciler and validation
62
+ authority hardening tracked in #1739.
63
+
64
+ ## Scout and worker salvage after crashed or no-sentinel runs
65
+
66
+ ### Symptom
67
+
68
+ An attempt is marked crashed or no-sentinel, but the worker lane may contain a
69
+ completed report, useful notes, or finished work.
70
+
71
+ ### Confirm
72
+
73
+ 1. Inspect the attempt log and grep for `<promise>DONE</promise>` or
74
+ `<promise>BLOCKED</promise>` in `afk.log`.
75
+ 2. Extract final `kind=text` events from the agent lane and read the last useful
76
+ agent message.
77
+ 3. Check whether the branch contains unique commits or an uncommitted diff.
78
+ 4. Distinguish envelope failure from real failure: a completed report or DONE
79
+ followed only by sentinel handling failure is salvageable; an explicit
80
+ unresolved blocker remains blocked.
81
+
82
+ ### Recover
83
+
84
+ 1. For scout reports, post the sanitized report to the disposable scout issue
85
+ and close it. Scout work has no PR to land.
86
+ 2. For worker branches with useful commits, salvage the branch through the
87
+ normal PR and CI path.
88
+ 3. For no useful work, close the disposable issue with the sanitized evidence
89
+ and requeue the original tracked issue.
90
+ 4. Record recurrence against the root-fix issue so repeated envelope failures
91
+ are visible.
92
+
93
+ ### Root fix
94
+
95
+ This manual salvage is a stopgap for the scout and worker envelope hardening
96
+ tracked in #1695.
97
+
98
+ ## Park-resolution contract
99
+
100
+ ### Symptom
101
+
102
+ An issue's labels are flipped out of a parked state without a matching
103
+ `## Current blocker` update, or the blocker block is edited while labels still
104
+ claim the old state.
105
+
106
+ ### Confirm
107
+
108
+ 1. Read the issue body and find the `## Current blocker` block.
109
+ 2. Compare the blocker kind with the live labels: `ready-for-human`,
110
+ `blocked:*`, `ready-for-agent`, and any `req:N` dependency edges.
111
+ 3. Check comments for a HITL directive or maintainer decision that resolves the
112
+ parked reason.
113
+
114
+ ### Recover
115
+
116
+ 1. Resolve both surfaces in the same operation: update `## Current blocker` and
117
+ flip labels together.
118
+ 2. Move the resolved blocker into the resolved-blockers history when the issue
119
+ template has that section.
120
+ 3. Never perform a raw label-only requeue or unblock. The issue body is the
121
+ durable contract that explains why the labels changed.
122
+
123
+ ### Root fix
124
+
125
+ This paired-update procedure is a durable manual procedure. The broader
126
+ automation reconciliation is tracked in #1739.
127
+
128
+ ## Base-stale decision procedure
129
+
130
+ ### Symptom
131
+
132
+ An issue or PR is parked as `base-stale`, or an old AFK branch exists after
133
+ `main` has moved.
134
+
135
+ ### Confirm
136
+
137
+ 1. Fast-forward local `main` from origin before comparing branches.
138
+ 2. Find the parked branch and compute its merge-base with current `main`.
139
+ 3. Inspect commits unique to the parked branch. Also check for uncommitted
140
+ worker artifacts if the attempt directory still exists.
141
+ 4. Decide whether the branch contains user-facing work, only generated churn, or
142
+ no unique work.
143
+
144
+ ### Recover
145
+
146
+ 1. If unique work exists, adopt it through a normal branch, PR, and CI run
147
+ against current `main`.
148
+ 2. If the branch is empty or contains no useful work, do a plain requeue of the
149
+ tracked issue instead of preserving the stale branch.
150
+ 3. If generated churn obscures the decision, isolate the human-authored files
151
+ and document what was kept or discarded.
152
+
153
+ ### Root fix
154
+
155
+ This decision tree is a durable manual procedure. Automatic stale-branch
156
+ reconciliation remains part of the broader reconciler tracked in #1739.
157
+
158
+ ## Requeue escalation map
159
+
160
+ ### Symptom
161
+
162
+ A parked issue carries `blocked:<kind>` outside the supported automatic requeue
163
+ set, or a HITL card refuses `/requeue` for that blocker kind.
164
+
165
+ ### Confirm
166
+
167
+ 1. Identify the exact blocker label and the issue body's `## Current blocker`.
168
+ 2. Try the narrowest supported CLI verb first when one exists, such as `/afk
169
+ retake N` for resumable AFK work.
170
+ 3. If the CLI cannot act, inspect the HITL card for supported comment verbs:
171
+ `/approve`, `/approve-ci`, `/reject`, and `/requeue`.
172
+ 4. If the card rejects the blocker kind, classify it as a full-contract manual
173
+ resolution rather than repeating the same unsupported command.
174
+
175
+ ### Recover
176
+
177
+ 1. Use the CLI verb when the blocker kind is supported.
178
+ 2. Use a HITL card comment verb when the card supports the transition.
179
+ 3. For unsupported `blocked:<kind>` labels, manually update the blocker-state
180
+ block and labels together, then add a concise sanitized comment explaining
181
+ the resolution.
182
+ 4. Re-run the gate census so the issue returns to the correct lane.
183
+
184
+ ### Root fix
185
+
186
+ This escalation map is a stopgap for unsupported blocker reconciliation tracked
187
+ in #1739.
188
+
189
+ ## Release-pipeline playbook
190
+
191
+ ### Symptom
192
+
193
+ The release pipeline does not publish the expected fix, or a change needs a
194
+ release trigger even though the code diff is already on `main`.
195
+
196
+ ### Confirm
197
+
198
+ 1. Check conventional-commit bump rules for the merged commits. A `fix:` commit
199
+ should trigger a patch release; `feat:` should trigger a minor release.
200
+ 2. Check whether AFK fleet workers are still running. Defer release-trigger
201
+ commits while fleet activity could race with branch or tag state.
202
+ 3. If no bump-worthy commit exists for an already-merged fix, prepare an empty
203
+ `fix:` trigger commit that references the issue.
204
+ 4. After the release appears, verify the fix-in-tag with an ancestor check from
205
+ the release tag back to the fixing commit.
206
+
207
+ ### Recover
208
+
209
+ 1. Wait for fleet workers to drain before creating release-trigger commits.
210
+ 2. Use an empty `fix:` commit only when the code change is already present on
211
+ `main` and the release pipeline needs a conventional-commit signal.
212
+ 3. Verify the published tag contains the fix before closing the release lane.
213
+ 4. If the tag does not contain the fix, stop and repair the release lineage
214
+ instead of announcing success.
215
+
216
+ ### Root fix
217
+
218
+ This playbook establishes the documentation contract for release troubleshooting
219
+ in #1863; the durable release policy remains the conventional-commit pipeline.
220
+
221
+ ## Fleet stop and takeover verification
222
+
223
+ ### Symptom
224
+
225
+ A fleet supervisor was stopped, killed, relaunched, or watchdog-respawned, and
226
+ you need to prove whether workers were cleanly stopped or adopted by the new
227
+ supervisor.
228
+
229
+ ### Confirm
230
+
231
+ 1. Run `red-skills-dev fleet status` and read the supervisor health, target,
232
+ slot counts, and live worker list.
233
+ 2. Check `.red/tmp/supervisors/default/state.toon` for `slot_pids`. A relaunched
234
+ supervisor uses this map to adopt live detached workers into their original
235
+ slots before spawning replacements.
236
+ 3. For a clean stop, verify `afk-supervisor.pid` is gone and no live worker pids
237
+ remain under `.red/tmp/workers/*/worker.pid`.
238
+ 4. For takeover, compare the pre-relaunch `slot_pids` with the post-relaunch
239
+ state: live pids should remain in slots, dead pids should disappear, and
240
+ `spawns_this_tick` should fill only the missing remainder.
241
+ 5. Check `.red/state/castle/supervisors/` for stale `s<PID>` snapshot dirs. Dead
242
+ supervisor dirs should be removed on the next supervisor boot.
243
+
244
+ ### Recover
245
+
246
+ 1. Prefer `red-skills-dev fleet stop` for a clean stop; it writes the stop file,
247
+ waits for the supervisor, then sweeps detached orphan workers.
248
+ 2. If the supervisor died unexpectedly, let `red-skills-dev fleet` or the
249
+ watchdog relaunch it. The relaunch should report why it respawned, including
250
+ ready work stranded and live workers versus target.
251
+ 3. If live workers were not adopted, stop the fleet, verify no pids remain live,
252
+ then relaunch from a clean state.
253
+ 4. If stale castle supervisor dirs remain after boot, remove only dead `s<PID>`
254
+ dirs after confirming the pid is not live.
255
+
256
+ ### Root fix
257
+
258
+ Supervisor snapshots persist slot pids so takeover is automatic. This playbook
259
+ is the manual verification path for #2067 and should stay aligned with the
260
+ state snapshot contract.
@@ -0,0 +1,213 @@
1
+ # AFK Actions lane — running `/afk` from GitHub Actions
2
+
3
+ The **AFK Actions lane** runs one AFK attempt per issue from GitHub Actions, in
4
+ any repository — not just `reddb-io/red-skills`. It is the same per-issue routine
5
+ as a local `/afk --issues N --once`: **one attempt, one issue, one PR per
6
+ invocation, no fleet, no admin-merge.** The PR is the deliverable; the merge
7
+ decision stays human. ADR 0059 (the lane + OpenCode as the CI runner) and ADR
8
+ 0062 (the packaging below).
9
+
10
+ ## Architecture — three layers
11
+
12
+ | Layer | What it owns | Artifact |
13
+ |---|---|---|
14
+ | **Trigger + policy** | *when* it runs + the trust gate | reusable workflow `reusable-afk-attempt.yml` |
15
+ | **Execution** | run one attempt (Node, runner CLI, launcher) | composite action `.github/actions/afk-attempt` |
16
+ | **Runtime distribution** | fetch the versioned `dev` bundle | the `afk.mjs` launcher + GitHub Release (ADR 0038/0039) |
17
+
18
+ The composite action is repo-portable: `uses:
19
+ reddb-io/red-skills/.github/actions/afk-attempt@<ref>` makes GitHub fetch the
20
+ red-skills tree to run the action, so the committed `afk.mjs` + `plugin.json`
21
+ ride along and the launcher resolves its version and fetches the matching `dev`
22
+ bundle (red-castle inlined, ADR 0061) — **no workspace build, no submodule, in
23
+ any repo.** Execution runs against the caller's checkout; the launcher lives in
24
+ the action's own checkout. Pin `@v1` to track the latest compatible v1 release,
25
+ or pin a SHA when the caller needs a fully immutable action/runtime pair.
26
+
27
+ External dependencies are only **GitHub-official** actions (`actions/checkout`,
28
+ `actions/setup-node`, `actions/github-script`) plus our own action — no
29
+ third-party action repos.
30
+
31
+ ## Two ways to adopt
32
+
33
+ ### A. Turnkey — the reusable workflow (triggers + trust gate included)
34
+
35
+ Drop the reusable into your repo (or call it). It ships the issue/label triggers
36
+ and the ADR 0085 trust gate. Template:
37
+ [`examples/rs-afk-attempt.yml`](./examples/rs-afk-attempt.yml).
38
+
39
+ Install the caller as `rs-afk-attempt.yml` (the `rs-*`
40
+ installed-name convention — see [WORKFLOWS.md](../red-setup/WORKFLOWS.md);
41
+ only the filename changes, the `uses:` ref keeps the `reusable-` source name).
42
+
43
+ ```yaml
44
+ # .github/workflows/rs-afk-attempt.yml in your repo
45
+ jobs:
46
+ attempt:
47
+ uses: reddb-io/red-skills/.github/workflows/reusable-afk-attempt.yml@v1
48
+ with:
49
+ issue_number: ${{ inputs.issue_number }} # or wire your own trigger
50
+ runner: opencode
51
+ model: "" # e.g. minimax/MiniMax-M3 (empty = repo config)
52
+ allowlist_authors: "your-login"
53
+ allowlist_label_actors: "your-login"
54
+ secrets:
55
+ minimax_api_key: ${{ secrets.MINIMAX_API_KEY }}
56
+ ```
57
+
58
+ ### B. Composable — the composite action (your triggers + gating)
59
+
60
+ Use the execution primitive directly and own the trigger/gate. Template:
61
+ [`examples/red-afk-attempt-action.yml`](./examples/red-afk-attempt-action.yml).
62
+
63
+ ```yaml
64
+ on: { issues: { types: [labeled] } }
65
+ permissions: { contents: write, issues: write, pull-requests: write }
66
+ jobs:
67
+ attempt:
68
+ if: github.event.label.name == 'ready-for-agent'
69
+ runs-on: ubuntu-latest
70
+ steps:
71
+ - uses: actions/checkout@v4
72
+ - uses: reddb-io/red-skills/.github/actions/afk-attempt@v1
73
+ with:
74
+ issue: ${{ github.event.issue.number }}
75
+ runner: opencode
76
+ model: minimax/MiniMax-M3 # optional override
77
+ minimax-api-key: ${{ secrets.MINIMAX_API_KEY }}
78
+ ```
79
+
80
+ ## Triggers (reusable workflow)
81
+
82
+ 1. `workflow_call` — a thin caller in your repo.
83
+ 2. `workflow_dispatch` — manual run from the Actions UI. Pass an `issue_number`, **or leave it empty to auto-pick the oldest open `ready-for-agent` issue** (the queue head). If the queue is empty the run is a clean no-op.
84
+ 3. `issues: labeled` — fires when **`ready-for-agent`** is applied.
85
+ 4. `issues: opened` — fires when an issue is **created already carrying**
86
+ `ready-for-agent` (a pre-delegated issue). Raw label-less issues are NOT run
87
+ — they fall through to `red-issues-needs-triage` and the labeled path.
88
+
89
+ ## Trust gate (ADR 0085)
90
+
91
+ The reusable refuses to claim unless the issue **author** AND the
92
+ **label-applier** (the opener, for the `opened` path) are both in the allowlist
93
+ (`allowlist_authors` / `allowlist_label_actors`). A failure logs, comments, and
94
+ exits without a claim or PR. `enforce_trust_gate: "false"` bypasses it — for
95
+ private repos only, never a public one. (Until #621, the auto-trigger path falls
96
+ back to a hard-coded maintainer allowlist.)
97
+
98
+ ## Inputs
99
+
100
+ | Input | Action | Reusable | Notes |
101
+ |---|---|---|---|
102
+ | `issue` / `issue_number` | ✓ | ✓ | the reusable accepts it **empty** on `workflow_dispatch`/`workflow_call` → auto-picks the oldest open `ready-for-agent` issue (no-op if none); the composite action needs an explicit number |
103
+ | `runner` | ✓ | ✓ | `claude` \| `codex` \| `opencode` (CI default `opencode`) |
104
+ | `model` | ✓ | ✓ | override every tier, e.g. `minimax/MiniMax-M3` (empty = repo config) |
105
+ | `effort` | ✓ | ✓ | reasoning effort/variant override |
106
+ | `lanes` | ✓ | ✓ | execution-lane tag (`actions`\|`k8s`), surfaced as `RED_AFK_LANE` for observability; default `actions` |
107
+ | `runs_on` | — | ✓ | runner label for the attempt job; default `ubuntu-latest`. Set e.g. `blacksmith-2vcpu-ubuntu-2404` to run on Blacksmith (see below). Composable path: set the job's own `runs-on:` directly. |
108
+ | `*-api-key` secrets | ✓ (inputs) | ✓ (secrets) | the auth keys — passed as **action inputs** (composite actions can't read `secrets.*`) |
109
+ | `allowlist_*`, `enforce_trust_gate` | — | ✓ | trust gate (policy layer) |
110
+
111
+ ## Runner host — GitHub-hosted or Blacksmith
112
+
113
+ The attempt job runs on `ubuntu-latest` (GitHub-hosted) by default. To run it on
114
+ [Blacksmith](https://blacksmith.sh) managed runners — a drop-in, faster, cheaper
115
+ host — pass the `runs_on` input the matching label:
116
+
117
+ ```yaml
118
+ with:
119
+ runs_on: blacksmith-2vcpu-ubuntu-2404 # smallest Blacksmith tier
120
+ ```
121
+
122
+ `blacksmith-2vcpu-ubuntu-2404` is the **smallest** Blacksmith shape — there is no
123
+ "nano"; 2 vCPU is the floor (other shapes: `-4vcpu-`, `-8vcpu-`, …). The
124
+ **Blacksmith GitHub App must be installed on the org/repo** first — a Blacksmith
125
+ label with no app installed leaves the job **queued forever**. red-skills' own
126
+ `rs-afk-attempt.yml` caller runs on `blacksmith-2vcpu-ubuntu-2404`.
127
+
128
+ ## Runner + auth
129
+
130
+ CI uses **opencode** by default — the API-auth runner with no host session (ADR
131
+ 0059). It is endpoint-agnostic: the model slug's leading segment routes the
132
+ endpoint, and AFK forwards the first-set auth key. Precedence (first non-empty
133
+ wins; `""` is treated as unset):
134
+
135
+ | precedence | env / secret | slug prefix | endpoint |
136
+ |---|---|---|---|
137
+ | 1 | `OPENAI_API_KEY` | `openai/…` | OpenAI direct |
138
+ | 2 | `MINIMAX_API_KEY` | `minimax/…` | MiniMax subscription |
139
+ | 3 | `OPENROUTER_API_KEY` | `openrouter/<vendor>/…` | OpenRouter relay |
140
+
141
+ So to drive a **MiniMax** subscription: wire `minimax-api-key` and set
142
+ `model: minimax/<your-model>` — nothing else. (claude/codex runners need their
143
+ own CLIs + `ANTHROPIC_API_KEY`/`OPENAI_API_KEY`.) Details:
144
+ [`runner-opencode.md`](./runner-opencode.md), and the model tiers/override in
145
+ [`../model-tier-policy/SKILL.md`](../model-tier-policy/SKILL.md).
146
+
147
+ ## Configuring secrets (per provider)
148
+
149
+ Pick **one** provider, wire **its** key, and set a matching `model` slug. The
150
+ resolver takes the first key that is set, so wiring more than one just makes the
151
+ precedence above decide.
152
+
153
+ | Provider | Repo secret | `model` slug | Caller wiring |
154
+ |---|---|---|---|
155
+ | MiniMax | `MINIMAX_API_KEY` | `minimax/MiniMax-M3` | `secrets: { minimax_api_key: ${{ secrets.MINIMAX_API_KEY }} }` |
156
+ | OpenAI | `OPENAI_API_KEY` | `openai/<model>` | `secrets: { openai_api_key: ${{ secrets.OPENAI_API_KEY }} }` |
157
+ | OpenRouter | `OPENROUTER_API_KEY` | `openrouter/<vendor>/<model>` | `secrets: { openrouter_api_key: ${{ secrets.OPENROUTER_API_KEY }} }` |
158
+
159
+ **Repo vs org secret.** A **repo** secret is simplest; an **org** secret is
160
+ preferred when several org repos run the lane (set it once — mind the public-repo
161
+ *Repository access* caveat in the gotcha below). Don't keep both a repo **and** an
162
+ org secret of the **same name** on one repo: the repo secret wins, so the two can
163
+ silently drift — pick one.
164
+
165
+ Set the key (the value is yours to provision — RedSkills never sets it for you):
166
+
167
+ ```bash
168
+ gh secret set MINIMAX_API_KEY --repo OWNER/REPO # paste the key when prompted
169
+ ```
170
+
171
+ > **⚠️ Public-repo gotcha — org secrets don't reach public repos by default.**
172
+ > GitHub does **not** expose organization secrets to a public repository unless
173
+ > the secret's **Repository access** explicitly includes it. An org secret left
174
+ > at the default scope resolves to an **empty string with no error** — the lane
175
+ > fires, `opencode` starts, and auth fails as if the key were never set. If your
176
+ > repo is public, either:
177
+ > - set the key as a **repo secret** (`gh secret set … --repo OWNER/REPO`), or
178
+ > - open the org secret's *Repository access* and add the repo (or set it to
179
+ > *All repositories*).
180
+ >
181
+ > Verify it actually reached the repo: `gh secret list --repo OWNER/REPO` should
182
+ > list the key. An empty list on a public repo is the tell-tale of this gotcha.
183
+
184
+ The `claude` / `codex` runners are not API-key-only — they need their own CLI on
185
+ the runner plus `ANTHROPIC_API_KEY` / `OPENAI_API_KEY`. The CI default is
186
+ `opencode` precisely because it is pure API-auth.
187
+
188
+ ## CI invariants (baked into the action)
189
+
190
+ `RED_AFK_SANDBOX=none` (never a nested container, overrides any repo
191
+ `afk.sandbox`), `GH_TOKEN = github.token`, a `github-actions[bot]` committer
192
+ identity, and `--once`. Required permissions: exactly `contents: write`,
193
+ `issues: write`, `pull-requests: write` — no `id-token`, no `actions: write`.
194
+
195
+ ## Issue ↔ PR link (auto-close on merge)
196
+
197
+ The PR body carries `Closes #<issue>`, so GitHub links the PR to the issue and
198
+ **auto-closes the issue when the PR is merged** into the default branch — the
199
+ human just merges, no separate close step. (The local admin-merge lane closes the
200
+ issue itself; both are idempotent.)
201
+
202
+ ## What it does NOT do
203
+
204
+ No fleet, no admin-merge, no auto-merge — the human merges the PR (merging
205
+ auto-closes the linked issue, above). It also does not yet claim atomically
206
+ against a concurrently-running local fleet (#622), and the config-sourced
207
+ allowlist predicate is pending (#621).
208
+
209
+ ## See also
210
+
211
+ - ADR 0059 (lane + OpenCode runner), ADR 0062 (composite-action packaging),
212
+ ADR 0038/0039 (launcher + Release distribution), ADR 0085 (trust gate).
213
+ - [`SKILL.md`](./SKILL.md) — the local `/afk` runtime contract this mirrors.
@@ -0,0 +1,29 @@
1
+ # `bin/` — AFK launcher entrypoint
2
+
3
+ `afk.mjs` is a small, **dependency-free launcher** — NOT the AFK implementation and
4
+ NOT the runtime bundle. Do not read it to understand `/afk`; the behavioural
5
+ contract is `../SKILL.md`.
6
+
7
+ The dev runtime itself is a single esbuild bundle (`dev.bundle.min.mjs`) that ships
8
+ as a **GitHub Release asset** (ADR 0038), fetched into a version-keyed cache by the
9
+ SessionStart hook — it is **not committed to git**. This launcher resolves that
10
+ bundle and delegates (`node bin/afk.mjs <cmd>` → `node <resolved bundle> <cmd>`):
11
+
12
+ 1. version-keyed cache — `~/.cache/red-skills/bundles/dev-<version>.bundle.min.mjs`
13
+ 2. repo-root `dist/dev.bundle.min.mjs` (local development)
14
+ 3. cold cache → fetches the Release asset once, then re-checks
15
+
16
+ `afk.mjs` is **generated**, not hand-edited: it is built from the shared
17
+ `packages/shared/entrypoint-cli.ts` with the `run:dev` role — the same source the
18
+ fetcher `plugins/dev/hooks/red-fetch.mjs` is built from with the `fetch` role
19
+ (ADR 0039). Both are tiny (~6 KB), versionless, and deterministic.
20
+
21
+ Build the runtime bundle locally (the launcher needs no separate build):
22
+
23
+ ```bash
24
+ pnpm -C apps/dev install
25
+ pnpm -C apps/dev run bundle # writes ../../dist/dev.bundle.min.mjs
26
+ ```
27
+
28
+ See ADR 0039 (one entrypoint source, two roles), ADR 0038 (fetched asset, supersedes
29
+ the committed-bundle decision in ADR 0032), and ADR 0034.
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire as __cr } from 'node:module'; const require = __cr(import.meta.url);
3
+ import{spawn as Zt,spawnSync as We}from"node:child_process";import{createHash as Yt}from"node:crypto";import{existsSync as qe,readFileSync as Je}from"node:fs";import{appendFile as Xt,mkdir as me,readFile as en,readdir as tn,rename as nn,writeFile as rn}from"node:fs/promises";import{homedir as sn}from"node:os";import{dirname as ze,join as R}from"node:path";import{fileURLToPath as on}from"node:url";var $="@reddb-io/red-skills",Ye="https://registry.npmjs.org",Xe="canary",b=class extends Error{kind;cause;constructor(t,n,r){super(n),this.name="BundleFetchError",this.kind=t,this.cause=r}};function Z(e,t,n="stable"){return`${e}-${n==="canary"?"canary":t}.bundle.min.mjs`}function v(e){let{plugin:t,version:n,cacheDir:r,channel:i}=e;return H(r,Z(t,n,i))}function et(e){return e==="dev"?["rsp"]:[]}function tt(e){return`${e}.bundle.min.mjs`}function nt(e){return H("dist",tt(e))}function rt(e,t="stable"){return`${$}@${t==="canary"?Xe:e}`}function it(e=$){return`${Ye}/${e.replace("/","%2F")}`}async function C(e,t){let{plugin:n,version:r,cacheDir:i,channel:a="stable"}=t,s=v({plugin:n,version:r,cacheDir:i,channel:a}),o=et(n),c=o.map(d=>v({plugin:d,version:r,cacheDir:i,channel:a}));if(a!=="canary"&&await e.exists(s)&&await st(e,c))return s;let u=rt(r,a),f=H(i,`.staging-${n}-${a==="canary"?"canary":r}`),h;try{h=await e.materialize(u,f)}catch(d){throw Se(d,u)}for(let d of[n,...o]){let g=nt(d),p=H(h,g);if(!await e.exists(p))throw new b("bundle-missing",`npm package ${u} has no ${g}`);let S=d===n?s:v({plugin:d,version:r,cacheDir:i,channel:a});await e.writeFile(S,await e.readFile(p))}return s}async function st(e,t){for(let n of t)if(!await e.exists(n))return!1;return!0}async function be(e,t,n=$){let r;try{r=await e.fetchText(it(n))}catch(i){throw Se(i,n)}return at(ot(r),t)}function ot(e){let t;try{t=JSON.parse(e)}catch{return[]}if(typeof t!="object"||t===null)return[];let n=t.versions;return typeof n!="object"||n===null?[]:Object.keys(n)}function at(e,t){let n=xe(t);if(n===null)return null;let r=null;for(let i of e)xe(i)===n&&(r===null||ct(i,r)>0)&&(r=i);return r}function xe(e){let t=/^(\d+)\.(\d+)\.(\d+)/.exec(String(e).trim());return t?Number(t[1]):null}function ct(e,t){let n=/^(\d+)\.(\d+)\.(\d+)/.exec(e.trim()),r=/^(\d+)\.(\d+)\.(\d+)/.exec(t.trim());return!n||!r?0:Number(n[1])-Number(r[1])||Number(n[2])-Number(r[2])||Number(n[3])-Number(r[3])}function Se(e,t){let n=e instanceof Error?e.message:String(e);return/ENOENT|command not found|spawn npm/i.test(n)?new b("npm-unavailable",`npm is unavailable while resolving ${t}: ${n}`,e):/\b404\b|E404|not found|no matching version|notarget/i.test(n)?new b("package-missing",`npm could not resolve ${t}: ${n}`,e):new b("network",`failed to resolve ${t}: ${n}`,e)}function H(e,t){return e.endsWith("/")?`${e}${t}`:`${e}/${t}`}var lt=["stable","canary"],ut="stable",ft="RED_SKILLS_CHANNEL",dt="canary";function ke(e){if(typeof e!="string")return null;let t=e.trim().toLowerCase();return lt.includes(t)?t:null}function ve(e={}){let t=ke(e.env?.[ft]);if(t)return t;let n=ke(e.configValue);return n||ut}function Te(e,t){return e==="canary"?dt:`v${t}`}import{existsSync as ht,readFileSync as pt}from"node:fs";import{dirname as mt,join as Oe}from"node:path";function L(e,t){let n=e;for(let r=0;r<16;r++){let i=Oe(n,t);if(ht(i))return i;let a=mt(n);if(a===n)break;n=a}return null}function gt(e){let t=e.indexOf(" #");return(t>=0?e.slice(0,t):e).trim()}function V(e,t){let n=[];for(let r of e.split(`
4
+ `)){let i=r.replace(/\r$/,""),a=i.trim();if(!a||a.startsWith("#"))continue;let s=i.indexOf(":");if(s<0)continue;let o=i.length-i.trimStart().length,c=i.slice(0,s).trim();if(!c)continue;let u=i.slice(s+1).trim();for(;n.length&&n[n.length-1].indent>=o;)n.pop();if(n.push({indent:o,key:c}),u&&n.map(h=>h.key).join(".")===t)return gt(u)}}function yt(e,t){return V(e,`plugins.${t}.enabled`)==="true"}function W(e,t){let n=L(e,Oe(".red","config.yaml"));if(!n)return!1;let r;try{r=pt(n,"utf8")}catch{return!1}return yt(r,t)}var q=class extends Error{constructor(t,n){super(t===0?n:`line ${t}: ${n}`),this.name="ToonError",this.line=t,this.reason=n}};function l(e,t){return new q(e,t)}var m=",";function Y(e){let t=e.split(`
5
+ `);return t.length>0&&t[t.length-1]===""&&t.pop(),t.map(n=>n.endsWith("\r")?n.slice(0,-1):n)}function w(e){return l(e,"invalid quoted string")}function T(e,t){if(e==="")return"";if(e.startsWith('"'))return $e(e,t);if(e.includes('"'))throw w(t);return e==="true"?!0:e==="false"?!1:e==="null"?null:wt(e)?Number(e):e}function E(e,t){let n=e.trim();if(n.startsWith('"'))return[$e(n,t),!0];if(n.includes('"')||/\s/.test(n))throw l(t,"expected non-empty field name");return[n,!1]}function $e(e,t){if(e[0]!=='"')throw w(t);let n="",r=1;for(;r<e.length;){let i=e[r];if(r+=1,i==='"'){if(e.slice(r).trim()==="")return n;throw w(t)}if(i==="\\"){let a=e[r];switch(r+=1,a){case'"':n+='"';break;case"\\":n+="\\";break;case"n":n+=`
6
+ `;break;case"r":n+="\r";break;case"t":n+=" ";break;case"u":{let s=e.slice(r,r+4);if(!/^[0-9a-fA-F]{4}$/.test(s))throw w(t);let o=Number.parseInt(s,16);if(o>=55296&&o<=57343)throw w(t);n+=String.fromCharCode(o),r+=4;break}default:throw w(t)}continue}if(i<" "&&i!==" ")throw w(t);n+=i}throw w(t)}function F(e,t,n){if(e==="")return[];let r=[],i=0,a=!1,s=!1;for(let o=0;o<e.length;o+=1){let c=e[o];if(s){s=!1;continue}c==="\\"&&a?s=!0:c==='"'?a=!a:c===t&&!a&&(r.push(e.slice(i,o).trim()),i=o+1)}if(a||s)throw w(n);return r.push(e.slice(i).trim()),r}function y(e,t,n){let r=!1,i=!1;for(let a=0;a<e.length;a+=1){let s=e[a];if(i){i=!1;continue}if(s==="\\"&&r)i=!0;else if(s==='"')r=!r;else if(s===t&&!r)return a}if(r||i)throw w(n);return-1}function wt(e){return/^-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?$/.test(e)}function xt(e){return/^-?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?$/.test(e)}function bt(e){if(Object.is(e,-0))return"0";if(!Number.isFinite(e))throw l(0,"number is not representable in TOON");return String(e)}function J(e){return e===null||typeof e=="boolean"||typeof e=="number"||typeof e=="string"}function O(e,t){if(e===null)return"null";if(typeof e=="boolean")return e?"true":"false";if(typeof e=="number")return bt(e);if(typeof e=="string")return kt(e,t);throw l(0,"not a primitive")}function St(e){return/^[A-Za-z_][A-Za-z0-9_.]*$/.test(e)}function x(e){return St(e)?e:Ee(e)}function kt(e,t){return vt(e,t)?Ee(e):e}function vt(e,t){return e===""||e.trim()!==e||e==="true"||e==="false"||e==="null"||xt(e)||/[:"\\[\]{}]/.test(e)||/[\u0000-\u001f]/.test(e)||e.includes(t)||e.startsWith("-")}function Ee(e){let t='"';for(let n of e)switch(n){case'"':t+='\\"';break;case"\\":t+="\\\\";break;case`
7
+ `:t+="\\n";break;case"\r":t+="\\r";break;case" ":t+="\\t";break;default:n<" "?t+=`\\u${n.charCodeAt(0).toString(16).padStart(4,"0")}`:t+=n}return`${t}"`}function N(e,t,n){Object.defineProperty(e,t,{value:n,writable:!0,enumerable:!0,configurable:!0})}var se=2;function Tt(e={}){return{indent:Math.max(1,e.indent??se),strict:e.strict??!0,expandPaths:e.expandPaths==="safe"||e.expandPaths===!0}}function Ot(e,t){let n=[],r=!1;return Y(e).forEach((i,a)=>{let s=a+1;if(i.trim()===""){r=!0;return}let o=i.length-i.replace(/^ +/,"").length;if(i[o]===" ")throw l(s,"invalid indentation");if(t.strict&&o%t.indent!==0)throw l(s,"invalid indentation");n.push({number:s,depth:Math.floor(o/t.indent),content:i.slice(o),blankBefore:r}),r=!1}),n}function j(e,t){let n=Tt(t),r=Ot(e,n),i=r[0];if(i===void 0)return{};if(i.depth!==0)throw l(i.number,"invalid indentation");let a=r.length===1;if(a&&i.content.trim()==="[]")return[];if(i.content.startsWith("[")){let u;try{u=ce(i.content,y(i.content,":",i.number))}catch(f){if(n.strict)throw re(f,i.number);u=void 0}if(u!==void 0)return Rt(u,r,n)}if(a&&y(i.content,":",i.number)===-1)return T(i.content.trim(),i.number);let s={index:0},o=oe(r,s,0,n),c=r[s.index];if(c!==void 0)throw l(c.number,"expected end of document");return o}function oe(e,t,n,r){let i={};for(;t.index<e.length;){let a=e[t.index];if(a.depth<n)break;if(a.depth>n)throw l(a.number,"invalid indentation");let{key:s,quoted:o,value:c}=$t(e,t,n,r);Pe(i,s,o,c,r,a.number)}return i}function $t(e,t,n,r){let i=e[t.index],a=i.content,s=y(a,":",i.number);if(s===-1)throw l(i.number,"expected `key: value`");let o=a.slice(0,s),c=a.slice(s+1),u=y(o,"[",i.number),f=y(o,"{",i.number);if(f!==-1&&(u===-1||f<u)){let p;try{p=Ct(o)}catch(M){if(r.strict)throw re(M,i.number);t.index+=1;let k=X(e,t,n,c,i.number,r);return{key:o.trim(),quoted:!1,value:k}}if(c.trim()!=="")throw l(i.number,"expected keyed map rows");let S=Et(p,e,t,n+1,r);return{key:p.key,quoted:p.keyQuoted,value:S}}if(u!==-1){let p;try{p=ce(o,s)}catch(M){if(r.strict)throw re(M,i.number);t.index+=1;let k=X(e,t,n,c,i.number,r);return{key:o.trim(),quoted:!1,value:k}}if(p.key===""&&!p.keyQuoted)throw l(i.number,"expected non-empty field name");let S=ae(p,c,e,t,n,r);return{key:p.key,quoted:p.keyQuoted,value:S}}let[h,d]=E(o,i.number);if(h===""&&!d)throw l(i.number,"expected non-empty field name");t.index+=1;let g=X(e,t,n,c,i.number,r);return{key:h,quoted:d,value:g}}function X(e,t,n,r,i,a){let s=r.trim();if(s==="[]")return[];if(s!=="")return T(s,i);let o=e[t.index];return o!==void 0&&o.depth>n?oe(e,t,n+1,a):{}}function Et(e,t,n,r,i){let a={};for(n.index+=1;n.index<t.length;){let s=t[n.index];if(s.depth<r)break;if(s.depth>r)throw l(s.number,"invalid indentation");if(s.blankBefore&&i.strict)throw l(s.number,"blank line inside keyed map");let o=y(s.content,":",s.number);if(o===-1)throw l(s.number,"expected `key: value`");let[c,u]=E(s.content.slice(0,o),s.number);if(c===""&&!u)throw l(s.number,"expected non-empty field name");let f=F(s.content.slice(o+1).trim(),e.delimiter,s.number);if(f.length!==e.fields.length)throw l(s.number,"keyed map row length mismatch");let h={};e.fields.forEach((d,g)=>{I(h,d,T(f[g],s.number),i,s.number)}),Pe(a,c,u,h,i,s.number),n.index+=1}return a}function Rt(e,t,n){let r=t[0],i=y(r.content,":",r.number);if(i===-1)throw l(r.number,"expected `key: value`");let a=r.content.slice(i+1),s={index:0},o=ae(e,a,t,s,0,n),c=t[s.index];if(c!==void 0)throw l(c.number,"expected end of document");return o}function ae(e,t,n,r,i,a){let s=n[r.index].number,o=t.trim();if(r.index+=1,e.fields!==void 0){if(o!=="")throw l(s,"expected tabular rows");return At(e,e.fields,n,r,i+1,a)}if(o!==""){let c=F(o,e.delimiter,s).map(u=>T(u,s));if(c.length!==e.len)throw l(s,"array length mismatch");return c}return Pt(e,n,r,i+1,a)}function At(e,t,n,r,i,a){let s=[];for(;s.length<e.len;){let c=n[r.index];if(c===void 0)throw te(n,r.index);if(c.depth<i)break;if(c.depth>i)throw l(c.number,"invalid indentation");if(c.blankBefore&&a.strict)throw l(c.number,"blank line inside array");if(!Re(c.content,e.delimiter,c.number))break;let u=F(c.content,e.delimiter,c.number);if(u.length!==t.length)throw l(c.number,"array row length mismatch");let f={};t.forEach((h,d)=>{I(f,h,T(u[d],c.number),a,c.number)}),s.push(f),r.index+=1}if(s.length!==e.len)throw te(n,r.index);let o=n[r.index];if(o!==void 0&&o.depth>=i&&Re(o.content,e.delimiter,o.number))throw l(o.number,"array length mismatch");return s}function Re(e,t,n){let r=y(e,":",n);if(r===-1)return!0;let i=y(e,t,n);return i===-1?!1:i<r}function Pt(e,t,n,r,i){let a=[];for(;a.length<e.len;){let o=t[n.index];if(o===void 0||o.depth<r)throw te(t,n.index);if(o.depth>r)throw l(o.number,"invalid indentation");if(o.blankBefore&&i.strict)throw l(o.number,"blank line inside array");a.push(Mt(t,n,r,i))}let s=t[n.index];if(s!==void 0&&s.depth>=r)throw l(s.number,"array length mismatch");return a}function Mt(e,t,n,r){let i=e[t.index];if(!i.content.startsWith("-"))throw l(i.number,"expected array item");let a=i.content.slice(1).replace(/^\s+/,"");if(a==="")return t.index+=1,{};if(a.startsWith("[")){let s=y(a,":",i.number),o;try{o=ce(a,s)}catch{o=void 0}if(o!==void 0){let c=a.slice(s+1);return ae(o,c,e,t,n,r)}}if(y(a,":",i.number)!==-1){let s=[{number:i.number,depth:n+1,content:a,blankBefore:!1}];for(t.index+=1;t.index<e.length;){let o=e[t.index];if(o.depth<=n)break;s.push(o),t.index+=1}return oe(s,{index:0},n+1,r)}return t.index+=1,T(a,i.number)}function te(e,t){let n=e[t]??e[e.length-1];return l(n===void 0?1:n.number,"array length mismatch")}function ce(e,t){if(t===-1)throw l(0,"array header missing colon");let n=e.slice(0,t),r=y(n,"[",0);if(r===-1)throw l(0,"invalid array header");let i,a;try{[i,a]=E(n.slice(0,r),0)}catch{throw l(0,"invalid array header")}let s=n.slice(r+1),o=s.indexOf("]");if(o===-1)throw l(0,"invalid array header");let c=s.slice(0,o),u=/^[0-9]*/.exec(c)[0];if(u===""||u.length>1&&u.startsWith("0"))throw l(0,"invalid array header");let f=Number.parseInt(u,10),h;switch(c.slice(u.length)){case"":h=",";break;case" ":h=" ";break;case"|":h="|";break;default:throw l(0,"invalid array header")}let d=s.slice(o+1),g;if(d==="")g=void 0;else if(d.startsWith("{")&&d.endsWith("}")&&d.length>=2)try{g=Ae(d.slice(1,-1),h)}catch(p){throw l(0,p.reason==="duplicate key"?"duplicate key":"invalid array header")}else throw l(0,"invalid array header");return{key:i,keyQuoted:a,len:f,delimiter:h,fields:g}}function Ct(e){let t=y(e,"{",0);if(t===-1||!e.endsWith("}"))throw l(0,"invalid keyed map header");let n,r;try{[n,r]=E(e.slice(0,t),0)}catch{throw l(0,"invalid keyed map header")}if(n===""&&!r)throw l(0,"expected non-empty field name");let i;try{i=Ae(e.slice(t+1,-1),m)}catch(a){throw l(0,a.reason==="duplicate key"?"duplicate key":"invalid keyed map header")}return{key:n,keyQuoted:r,delimiter:m,fields:i}}function Ae(e,t){let n=[],r=0;function i(o,c){let u=0;for(;r<e.length&&!(c&&e[r]==="}");){if(e[r]===t||e[r]==="}")throw l(0,"invalid array header");let f=r;for(;r<e.length;){let d=e[r];if(d==='"'){a();continue}if(d===t||d==="{"||d==="}")break;r+=1}let[h]=E(e.slice(f,r),0);if(h==="")throw l(0,"invalid array header");if(u+=1,e[r]==="{"){r+=1;let d=n.length;if(i([...o,h],!0),e[r]!=="}"||n.length===d)throw l(0,"invalid array header");r+=1}else n.push([...o,h]);if(e[r]===t){if(r+=1,r>=e.length||e[r]==="}")throw l(0,"invalid array header");continue}if(c?e[r]==="}":r===e.length)break;throw l(0,"invalid array header")}if(u===0)throw l(0,"invalid array header")}function a(){for(r+=1;r<e.length;){let o=e[r];if(r+=1,o==="\\")r+=1;else if(o==='"')return}}if(i([],!1),r!==e.length)throw l(0,"invalid array header");let s=new Set;for(let o=0;o<n.length;o+=1)for(let c=o+1;c<n.length;c+=1)if(Lt(n[o],n[c])||ne(n[o],n[c])||ne(n[c],n[o]))throw l(0,"duplicate key");for(let o of n){let c=o.join("\0");if(s.has(c))throw l(0,"duplicate key");s.add(c)}return n}function Lt(e,t){return e.length===t.length&&ne(e,t)}function ne(e,t){return t.every((n,r)=>e[r]===n)}function re(e,t){return l(t,e.reason??String(e.message))}function Pe(e,t,n,r,i,a){if(i.expandPaths&&!n&&t.includes(".")){let s=t.split(".");if(s.every(Ft)){I(e,s,r,i,a);return}}I(e,[t],r,i,a)}function I(e,t,n,r,i){let a=t[0],s=Object.prototype.hasOwnProperty.call(e,a);if(t.length===1){if(s&&r.strict)throw l(i,"duplicate key");N(e,a,n);return}if(s&&!A(e[a])){if(r.strict)throw l(i,"path expansion conflict");N(e,a,{})}else s||N(e,a,{});I(e[a],t.slice(1),n,r,i)}function Ft(e){return/^[A-Za-z_][A-Za-z0-9_]*$/.test(e)}function A(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function P(e,t={}){let n={nestedTabularHeaders:t.nestedTabularHeaders===!0,keyedMapCollapse:t.keyedMapCollapse===!0},r=[];return Array.isArray(e)?le(r,void 0,e,0,!1,n):A(e)?Me(r,e,0,n):r.push(O(e,m)),r.join("")}function D(e,t){e.push(" ".repeat(t*se))}function Me(e,t,n,r){for(let[i,a]of Object.entries(t))D(e,n),ie(e,i,a,n,r)}function ie(e,t,n,r,i){if(Array.isArray(n)){le(e,t,n,r,!1,i);return}if(A(n)){let a=i.keyedMapCollapse?It(n,i):void 0;if(a!==void 0){e.push(x(t),"{",a.fields.map(ue).join(m),`}:
8
+ `);for(let[s,o]of Object.entries(n))D(e,r+1),e.push(x(s),": ",a.paths.map(c=>O(Ne(o,c),m)).join(m),`
9
+ `);return}e.push(x(t),`:
10
+ `),Me(e,n,r+1,i);return}e.push(x(t),": ",O(n,m),`
11
+ `)}function le(e,t,n,r,i,a){if(n.length===0){t!==void 0?e.push(x(t),`: []
12
+ `):i?e.push(`[0]:
13
+ `):e.push(`[]
14
+ `);return}if(n.every(J)){ee(e,t,n.length,void 0),e.push(" "),e.push(n.map(o=>O(o,m)).join(m)),e.push(`
15
+ `);return}let s=i?void 0:Ce(n,a);if(s!==void 0){ee(e,t,n.length,s.fields),e.push(`
16
+ `);for(let o of n)D(e,r+1),e.push(s.paths.map(c=>O(Ne(o,c),m)).join(m)),e.push(`
17
+ `);return}ee(e,t,n.length,void 0),e.push(`
18
+ `);for(let o of n)D(e,r+1),Nt(e,o,r+1,a)}function Nt(e,t,n,r){if(Array.isArray(t)){e.push("- "),le(e,void 0,t,n,!0,r);return}if(A(t)){let i=Object.entries(t);if(i.length===0){e.push(`-
19
+ `);return}e.push("- "),ie(e,i[0][0],i[0][1],n+1,r);for(let[a,s]of i.slice(1))D(e,n+1),ie(e,a,s,n+1,r);return}e.push("- ",O(t,m),`
20
+ `)}function ee(e,t,n,r){t!==void 0&&e.push(x(t)),e.push("[",String(n),"]"),r!==void 0&&e.push("{",r.map(ue).join(m),"}"),e.push(":")}function Ce(e,t){let n=Le(e.map(r=>({value:r})),t);return n===void 0?void 0:{fields:n,paths:Fe(n)}}function It(e,t){let n=Object.values(e);if(!(n.length<2))return Ce(n,t)}function Le(e,t){let n=e[0]?.value;if(!A(n))return;let r=Object.keys(n).map(i=>({key:i}));if(r.length!==0){for(let{value:i}of e)if(!A(i)||Object.keys(i).length!==r.length||r.some(s=>!Object.prototype.hasOwnProperty.call(i,s.key)))return;for(let i of r){let a=e.map(({value:o})=>({value:o[i.key]}));if(a.every(({value:o})=>J(o)))continue;if(!t.nestedTabularHeaders)return;let s=Le(a,t);if(s===void 0)return;i.children=s}return r}}function Fe(e,t=[]){return e.flatMap(n=>{let r=[...t,n.key];return n.children===void 0?[r]:Fe(n.children,r)})}function Ne(e,t){let n=e;for(let r of t)n=n[r];return n}function ue(e){return e.children===void 0?x(e.key):`${x(e.key)}{${e.children.map(ue).join(m)}}`}var In=new TextEncoder,Dn=new TextDecoder;function _(e){let t=/^(\d+)\.(\d+)\.(\d+)/.exec(String(e).trim());return t?{major:Number(t[1]),minor:Number(t[2]),patch:Number(t[3])}:null}function U(e,t){let n=_(e),r=_(t);return!n||!r?0:n.major-r.major||n.minor-r.minor||n.patch-r.patch}function he(e,t){let n=_(e),r=_(t);return!!n&&!!r&&n.major===r.major}function jt(e){let{installed:t,current:n,candidate:r,channel:i}=e;return i!=="stable"||!he(t,r)||U(r,n)<=0?null:r}function _t(e){return`${e}-stable.current`}function Ie(e,t){return Be(e,_t(t))}function Ut(e){return`${e}-stable.self-update.json`}function De(e,t){return Be(e,Ut(t))}function Bt(e){return new TextEncoder().encode(JSON.stringify({version:e}))}function Ht(e){let t=e.trim();try{let n=JSON.parse(t);if(n&&typeof n.version=="string")return n.version.trim()}catch{}return/^\d+\.\d+\.\d+/.test(t)?t:""}function Vt(e){try{let t=e.trim();if(!t)return{};let n;try{n=JSON.parse(t)}catch{let i=j(t);n=i&&typeof i=="object"&&!Array.isArray(i)?i:{}}let r={};return Number.isFinite(n.lastCheckAtMs)&&(r.lastCheckAtMs=Number(n.lastCheckAtMs)),Number.isFinite(n.lastSuccessAtMs)&&(r.lastSuccessAtMs=Number(n.lastSuccessAtMs)),Number.isFinite(n.lastFailureAtMs)&&(r.lastFailureAtMs=Number(n.lastFailureAtMs)),typeof n.lastError=="string"&&(r.lastError=n.lastError),(n.lastStatus==="updated"||n.lastStatus==="up-to-date"||n.lastStatus==="skipped-channel"||n.lastStatus==="error")&&(r.lastStatus=n.lastStatus),r}catch{return{}}}async function je(e,t,n){let r=De(t,n);try{return await e.exists(r)?Vt(new TextDecoder().decode(await e.readFile(r))):{}}catch{return{}}}async function Wt(e,t,n,r){let i=await je(e,t,n);await e.writeFile(De(t,n),new TextEncoder().encode(`${P(de({...i,...r}))}
21
+ `))}async function fe(e,t,n,r){try{await Wt(e,t,n,r)}catch{}}var qt=4*60*60*1e3;async function Jt(e,t){return(await pe(e,t)).version}async function pe(e,t,n={}){let{plugin:r,installedVersion:i,cacheDir:a,channel:s}=t;if(s!=="stable"||!i)return{version:i,logNotes:[]};let o=Ie(a,r),c="";try{await e.exists(o)&&(c=Ht(new TextDecoder().decode(await e.readFile(o))))}catch{c=""}let u=i,f;if(c&&he(i,c)&&U(c,i)>=0){let Ze=v({plugin:r,version:c,cacheDir:a,channel:s});await e.exists(Ze)&&(u=c,f=c)}let h=[],d=await Kt(e,{plugin:r,installedVersion:i,cacheDir:a}),g;d&&U(d,u)>0&&(await Ue(e,a,r,d),g={from:f,to:d},h.push(`self-update reconciled pointer ${f??"none"} -> ${d} from cached lane`),u=d,f=d);let p=await je(e,a,r),S=n.nowMs??Date.now(),M=n.staleAfterMs??qt,k;return p.lastFailureAtMs!==void 0&&p.lastFailureAtMs>(p.lastSuccessAtMs??0)&&S-p.lastFailureAtMs>M&&(k={ageMs:S-p.lastFailureAtMs,lastError:p.lastError},h.push(`self-update stale: last check failed ${Qt(k.ageMs)} ago`)),{version:u,...f?{pointerVersion:f}:{},...d?{laneNewestVersion:d}:{},...g?{reconciled:g}:{},...k?{staleFailure:k}:{},logNotes:h}}async function zt(e,t,n={}){let{plugin:r,installedVersion:i,repo:a,cacheDir:s,channel:o}=t,c=n.nowMs??Date.now;if(o!=="stable"||!_(i))return{status:"skipped-channel"};try{let u=await Jt(e,{plugin:r,installedVersion:i,cacheDir:s,channel:o}),f=await be(e,i),h=f?jt({installed:i,current:u,candidate:f,channel:o}):null;return h?(await C(e,{plugin:r,version:h,repo:a,cacheDir:s,channel:o}),await Ue(e,s,r,h),await fe(e,s,r,{lastCheckAtMs:c(),lastSuccessAtMs:c(),lastStatus:"updated",lastError:void 0}),{status:"updated",version:h}):(await fe(e,s,r,{lastCheckAtMs:c(),lastSuccessAtMs:c(),lastStatus:"up-to-date",lastError:void 0}),{status:"up-to-date",version:u})}catch(u){let f=u instanceof Error?u.message:String(u);return await fe(e,s,r,{lastCheckAtMs:c(),lastFailureAtMs:c(),lastError:f,lastStatus:"error"}),{status:"error",error:f}}}async function _e(e,t,n={}){let r=Math.max(1,Math.floor(n.maxAttempts??4)),i=Math.max(0,Math.floor(n.initialDelayMs??3e4)),a=Math.max(i,Math.floor(n.maxDelayMs??15*6e4)),s=n.sleep??(u=>new Promise(f=>setTimeout(f,u))),o=1,c=i;for(;;){let u=await zt(e,t,n);if(u.status!=="error"||o>=r)return{...u,attempts:o};await n.onRetry?.({attempt:o,nextAttempt:o+1,delayMs:c,error:u.error}),await s(c),c=Math.min(a,Math.max(c*2,i)),o+=1}}async function Ue(e,t,n,r){let i=Ie(t,n),a=`${i}.${r}.tmp`;await e.writeFile(a,Bt(r)),await e.rename(a,i)}async function Kt(e,t){let n,r;try{r=await e.readdir(t.cacheDir)}catch{return}let i=new RegExp(`^${Gt(t.plugin)}-(\\d+\\.\\d+\\.\\d+(?:[-+][A-Za-z0-9.-]+)?)\\.bundle\\.min\\.mjs$`);for(let a of r){let s=i.exec(a);if(!s)continue;let o=s[1];he(t.installedVersion,o)&&(U(o,t.installedVersion)<=0||(n===void 0||U(o,n)>0)&&(n=o))}return n}function Gt(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function de(e){if(e==null)return null;if(typeof e=="string"||typeof e=="boolean")return e;if(typeof e=="number")return Number.isFinite(e)?e:null;if(Array.isArray(e))return e.map(t=>de(t));if(typeof e=="object"){let t={};for(let[n,r]of Object.entries(e))r!==void 0&&(t[n]=de(r));return t}return String(e)}function Qt(e){let t=Math.floor(Math.max(0,e)/1e3);if(t<60)return`${t}s`;let n=Math.floor(t/60);return n<60?`${n}m`:`${Math.floor(n/60)}h`}function Be(e,t){return e.endsWith("/")?`${e}${t}`:`${e}/${t}`}var z="reddb-io/red-skills";function an(){try{return"run:dev"}catch{return""}}function ye(e){return e||(process.env.RED_SKILLS_CACHE_DIR?process.env.RED_SKILLS_CACHE_DIR:process.env.XDG_CACHE_HOME?R(process.env.XDG_CACHE_HOME,"red-skills","bundles"):R(sn(),".cache","red-skills","bundles"))}var we={async materialize(e,t){await me(t,{recursive:!0});let n=We("npm",["install",e,"--prefix",t,"--no-save","--no-audit","--no-fund","--ignore-scripts","--loglevel=error"],{stdio:["ignore","ignore","pipe"],encoding:"utf8"});if(n.error)throw n.error;if(n.status!==0)throw new Error(`npm install ${e} -> ${n.status}: ${(n.stderr||"").trim()}`);return R(t,"node_modules",...$.split("/"))},async readFile(e){return new Uint8Array(await en(e))},async writeFile(e,t){await me(ze(e),{recursive:!0}),await rn(e,t)},async exists(e){return qe(e)},sha256(e){return Yt("sha256").update(e).digest("hex")},async fetchText(e){let t=await fetch(e,{redirect:"follow"});if(!t.ok)throw new Error(`GET ${e} -> ${t.status}`);return await t.text()}},Ke={...we,async readdir(e){return await tn(e)},async rename(e,t){await nn(e,t)}};async function B(e,t){try{await me(e,{recursive:!0}),await Xt(R(e,"red-fetch.log"),`[${new Date().toISOString()}] ${t}
22
+ `)}catch{}process.stderr.write(`entrypoint: ${t}
23
+ `)}function ge(e){let t={mode:"fetch",repo:z,help:!1},n=[];for(let r=0;r<e.length;r++){let i=e[r];i==="--help"||i==="-h"?t.help=!0:i==="--repo"?t.repo=e[++r]??t.repo:i==="--cache-dir"?t.cacheDir=e[++r]:i.startsWith("-")||n.push(i)}return t.plugin=n[0],t.version=n[1],t}function cn(e,t){return t.startsWith("run:")?{mode:"run",plugin:t.slice(4),rest:[...e],repo:z}:e[0]==="run"?{mode:"run",plugin:e[1],rest:e.slice(2),repo:z}:e[0]==="fetch"?ge(e.slice(1)):ge(e)}function ln(e){return V(e,"plugins.dev.afk.release.channel")??V(e,"afk.release.channel")}function G(e,t){return ve({env:e,configValue:t?ln(t):void 0})}function Q(){let e=L(process.cwd(),R(".red","config.yaml"));if(e)try{return Je(e,"utf8")}catch{return}}var Ge=(()=>{try{return ze(on(import.meta.url))}catch{return process.cwd()}})();function un(){let e=L(Ge,R(".claude-plugin","plugin.json"));if(!e)return"";try{return JSON.parse(Je(e,"utf8")).version||""}catch{return""}}function He(e,t,n,r){if(!t&&r!=="canary")return null;let i=v({plugin:e,version:t,cacheDir:n,channel:r});return qe(i)?i:null}function Ve(e){return L(Ge,R("dist",`${e}.bundle.min.mjs`))}function K(e){return e==="code-nav"?"dev":e}var fn=new Set(["route-model-tier","statusline"]);async function dn(e){let{plugin:t}=e;t||(process.stderr.write("entrypoint: `run` requires a <plugin> name.\n"),process.exit(1)),W(process.cwd(),K(t))||(fn.has(e.rest[0]??"")||process.stderr.write(`entrypoint: ${t} is not enabled in this directory (no \`plugins.${K(t)}.enabled: true\` in .red/config.yaml). Run /red-setup to enable it.
24
+ `),process.exit(0));let n=ye(e.cacheDir),r=un(),i=G(process.env,Q()),a=await pe(Ke,{plugin:t,installedVersion:r,cacheDir:n,channel:i}),s=a.version,o=a.logNotes.length>0?`; ${a.logNotes.join("; ")}`:"";process.stderr.write(`entrypoint: resolving ${t} via ${i} channel (${Te(i,s)})${o}
25
+ `);let c=He(t,s,n,i)??Ve(t);if(!c&&(s||i==="canary"))try{c=await C(we,{plugin:t,version:s,repo:e.repo,cacheDir:n,channel:i})}catch(f){let h=f instanceof b?f.kind:"unknown",d=f instanceof Error?f.message:String(f);await B(n,`${h}: ${d} (run plugin=${t} version=${s} channel=${i})`),c=He(t,s,n,i)??Ve(t)}if(!c){let f=s||i==="canary"?Z(t,s,i):`${t}-<version>.bundle.min.mjs`;process.stderr.write(`entrypoint: could not resolve the ${t} runtime bundle (${f}).
26
+ Looked in cache ${n} and repo-root dist/.
27
+ The bundle ships inside the ${$} npm package (ADR 0091),
28
+ resolved via npm on first run; ensure network access, or build locally:
29
+ pnpm -C apps/${t} run bundle
30
+ `),process.exit(1)}let u=We(process.execPath,[c,...e.rest],{stdio:"inherit"});u.signal&&process.kill(process.pid,u.signal),process.exit(u.status??1)}var hn=`entrypoint (fetch) \u2014 resolve a plugin's built bundle from the ${$} npm package into a local cache.
31
+
32
+ Usage:
33
+ node <entrypoint> fetch <plugin> <version> [--repo owner/name] [--cache-dir DIR]
34
+ node red-fetch.mjs <plugin> <version> [--repo owner/name] [--cache-dir DIR]
35
+
36
+ Arguments:
37
+ <plugin> plugin name (e.g. dev, memory)
38
+ <version> plugin version without the leading v (e.g. 1.140.0)
39
+
40
+ Options:
41
+ --repo GitHub repo publishing the release (default: ${z})
42
+ --cache-dir override the bundle cache dir
43
+ -h, --help show this help
44
+
45
+ Best-effort: never blocks session start. On any failure it logs to
46
+ <cache-dir>/red-fetch.log and exits 0.`;async function pn(e){let t=ye(e.cacheDir);(e.help||!e.plugin||!e.version)&&(process.stdout.write(`${hn}
47
+ `),!e.help&&(!e.plugin||!e.version)&&process.stdout.write(`
48
+ entrypoint: missing <plugin> and/or <version>; nothing to do.
49
+ `),process.exit(0));let{plugin:n,version:r}=e;W(process.cwd(),K(n))||process.exit(0);let i=G(process.env,Q()),a=v({plugin:n,version:r,cacheDir:t,channel:i});try{let s=await C(we,{plugin:n,version:r,repo:e.repo,cacheDir:t,channel:i});process.stdout.write(`entrypoint: bundle ready at ${s} (${i})
50
+ `)}catch(s){let o=s instanceof b?s.kind:"unknown",c=s instanceof Error?s.message:String(s);await B(t,`${o}: ${c} (fetch plugin=${n} version=${r} channel=${i})`),process.stdout.write(`entrypoint: could not fetch ${n}@${r} (${o}); expected cache path ${a}. Continuing \u2014 fetch is best-effort.
51
+ `)}mn(n,r,e.repo,e.cacheDir),process.exit(0)}var Qe="__self-update";function mn(e,t,n,r){if(G(process.env,Q())!=="stable"||!t)return;let a=process.argv[1];if(a)try{let s=[a,Qe,e,t,"--repo",n];r&&s.push("--cache-dir",r),Zt(process.execPath,s,{detached:!0,stdio:"ignore"}).unref()}catch{}}async function gn(e){let t=ge(e),n=t.plugin,r=t.version??"",i=ye(t.cacheDir);(!n||!W(process.cwd(),K(n)))&&process.exit(0);let a=G(process.env,Q()),s=await _e(Ke,{plugin:n,installedVersion:r,repo:t.repo,cacheDir:i,channel:a},{onRetry:async({attempt:o,nextAttempt:c,delayMs:u,error:f})=>{await B(i,`self-update: ${n} check failed on attempt ${o} (${f}); retry ${c} in ${Math.round(u/1e3)}s`)}});s.status==="updated"?await B(i,`self-update: ${n} swapped to ${s.version} for next boot after ${s.attempts??1} attempt(s)`):s.status==="error"&&await B(i,`self-update: ${n} check failed after ${s.attempts??1} attempt(s) (${s.error}); cached bundle keeps serving`),process.exit(0)}async function yn(){let e=process.argv.slice(2);if(e[0]===Qe){await gn(e.slice(1));return}let t=cn(e,an());t.mode==="run"?await dn(t):await pn(t)}import.meta.url===`file://${process.argv[1]}`&&yn();export{ln as configuredChannelValue,K as gatePluginName,cn as parseEntrypoint,G as resolveLauncherChannel};
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env bash
2
+ # cargo-pre-worktree.sh — built-in pre_worktree default for Rust projects.
3
+ #
4
+ # Contract (PRD #207, issue #211):
5
+ # stdin — JSON object: pre_worktree context (issue, target, branch, env).
6
+ # stdout — empty (context unchanged) when no Cargo.toml is present at
7
+ # $PROJECT_ROOT, OR a JSON object that merges
8
+ # {env: {CARGO_TARGET_DIR: <slot dir>}} into the context.
9
+ # exit — 0 on either branch. Never aborts the chain.
10
+ #
11
+ # Side effect: mkdir -p the slot target dir so cargo can use it immediately.
12
+ # CARGO_TARGET_DIR=${RED_AFK_CARGO_TARGET_BASE:-/opt/cargo-target}/slot-${RED_AFK_SLOT:-0}
13
+ # so each worker slot gets its own target directory and Cargo never
14
+ # serializes on .cargo-lock between workers.
15
+ #
16
+ # Disable with `afk.hooks.defaults.cargo: false` in `.red/config.yaml`.
17
+
18
+ set -uo pipefail
19
+
20
+ project_root="${PROJECT_ROOT:-$(pwd)}"
21
+ ctx="$(timeout "${RED_SKILLS_HOOK_STDIN_TIMEOUT_S:-5s}" cat 2>/dev/null || true)"
22
+ [[ -z "$ctx" ]] && ctx='{}'
23
+
24
+ if [[ ! -f "$project_root/Cargo.toml" ]]; then
25
+ # Not a Rust project — pass-through.
26
+ exit 0
27
+ fi
28
+
29
+ slot="${RED_AFK_SLOT:-0}"
30
+ base="${RED_AFK_CARGO_TARGET_BASE:-/opt/cargo-target}"
31
+ target_dir="${base}/slot-${slot}"
32
+
33
+ mkdir -p "$target_dir" 2>/dev/null || true
34
+
35
+ printf '%s' "$ctx" | jq -c \
36
+ --arg t "$target_dir" \
37
+ '. + {env: ((.env // {}) + {CARGO_TARGET_DIR: $t})}'