@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.
- package/LICENSE +202 -0
- package/README.md +30 -0
- package/package.json +32 -0
- package/skills/engineering/README.md +36 -0
- package/skills/engineering/_report-runtime/WRAPPER.md +47 -0
- package/skills/engineering/afk/AGENT-PROMPT.md +295 -0
- package/skills/engineering/afk/SAFETY.md +103 -0
- package/skills/engineering/afk/SKILL.md +160 -0
- package/skills/engineering/afk/TROUBLESHOOTING.md +260 -0
- package/skills/engineering/afk/actions-lane.md +213 -0
- package/skills/engineering/afk/bin/README.md +29 -0
- package/skills/engineering/afk/bin/afk.mjs +51 -0
- package/skills/engineering/afk/defaults/cargo-pre-worktree.sh +37 -0
- package/skills/engineering/afk/defaults/envelope-post-attempt.sh +47 -0
- package/skills/engineering/afk/defaults/gradle-pre-worktree.sh +42 -0
- package/skills/engineering/afk/defaults/heartbeat-post-attempt.sh +43 -0
- package/skills/engineering/afk/defaults/validation-post-merge.sh +77 -0
- package/skills/engineering/afk/detectors/README.md +72 -0
- package/skills/engineering/afk/detectors/cargo.sh +27 -0
- package/skills/engineering/afk/detectors/gradle.sh +35 -0
- package/skills/engineering/afk/docs/BOOT-SWEEPS.md +81 -0
- package/skills/engineering/afk/docs/CONFIG.md +238 -0
- package/skills/engineering/afk/docs/ENVELOPE.md +177 -0
- package/skills/engineering/afk/docs/HANDOFF.md +70 -0
- package/skills/engineering/afk/docs/LIVENESS.md +56 -0
- package/skills/engineering/afk/docs/OPERATIONS.md +409 -0
- package/skills/engineering/afk/examples/only-mine.sh +32 -0
- package/skills/engineering/afk/examples/red-afk-attempt-action.yml +53 -0
- package/skills/engineering/afk/examples/rs-afk-attempt.yml +76 -0
- package/skills/engineering/afk/fleet.md +127 -0
- package/skills/engineering/afk/hooks/red-cargo +44 -0
- package/skills/engineering/afk/hooks/red-envelope +50 -0
- package/skills/engineering/afk/hooks/red-gradle +50 -0
- package/skills/engineering/afk/hooks/red-heartbeat +46 -0
- package/skills/engineering/afk/hooks/red-validation +79 -0
- package/skills/engineering/afk/monitor.md +170 -0
- package/skills/engineering/afk/runner-claude-minimax.md +71 -0
- package/skills/engineering/afk/runner-claude.md +67 -0
- package/skills/engineering/afk/runner-codex.md +138 -0
- package/skills/engineering/afk/runner-hermes.md +40 -0
- package/skills/engineering/afk/runner-opencode.md +142 -0
- package/skills/engineering/ask-red/SKILL.md +137 -0
- package/skills/engineering/audit-skills/SKILL.md +46 -0
- package/skills/engineering/code-review/SKILL.md +106 -0
- package/skills/engineering/context/SKILL.md +142 -0
- package/skills/engineering/curate/SKILL.md +133 -0
- package/skills/engineering/daily-review/SKILL.md +68 -0
- package/skills/engineering/dashboard/SKILL.md +44 -0
- package/skills/engineering/diagnose/SKILL.md +155 -0
- package/skills/engineering/diagnose/scripts/hitl-loop.template.sh +41 -0
- package/skills/engineering/go/SKILL.md +144 -0
- package/skills/engineering/go/TROUBLESHOOTING.md +81 -0
- package/skills/engineering/ground-truth/SKILL.md +84 -0
- package/skills/engineering/hitl/SKILL.md +171 -0
- package/skills/engineering/hitl/TROUBLESHOOTING.md +76 -0
- package/skills/engineering/implement/SKILL.md +69 -0
- package/skills/engineering/improve-codebase-architecture/DEEPENING.md +37 -0
- package/skills/engineering/improve-codebase-architecture/INTERFACE-DESIGN.md +44 -0
- package/skills/engineering/improve-codebase-architecture/LANGUAGE.md +53 -0
- package/skills/engineering/improve-codebase-architecture/SKILL.md +81 -0
- package/skills/engineering/model-tier-policy/SKILL.md +83 -0
- package/skills/engineering/prototype/LOGIC.md +79 -0
- package/skills/engineering/prototype/SKILL.md +30 -0
- package/skills/engineering/prototype/UI.md +112 -0
- package/skills/engineering/red-doctor/APPLY.md +35 -0
- package/skills/engineering/red-doctor/SKILL.md +172 -0
- package/skills/engineering/red-gains/SKILL.md +49 -0
- package/skills/engineering/red-setup/INTERVIEW.md +266 -0
- package/skills/engineering/red-setup/ISSUE-SWEEP.md +18 -0
- package/skills/engineering/red-setup/REFERENCE.md +35 -0
- package/skills/engineering/red-setup/SKILL.md +58 -0
- package/skills/engineering/red-setup/WORKFLOWS.md +74 -0
- package/skills/engineering/red-setup/WRITE-CONTRACT.md +151 -0
- package/skills/engineering/red-setup/config-template.yaml +174 -0
- package/skills/engineering/red-setup/domain.md +52 -0
- package/skills/engineering/red-setup/issue-tracker-github.md +88 -0
- package/skills/engineering/red-setup/rsp-retirement-runbook.md +35 -0
- package/skills/engineering/red-setup/scripts/install-runtime-shim.sh +189 -0
- package/skills/engineering/red-setup/triage-labels.md +176 -0
- package/skills/engineering/red-setup/workflows/red-issues-needs-triage.yml +36 -0
- package/skills/engineering/red-statusline/HOST-NOTES.md +180 -0
- package/skills/engineering/red-statusline/SKILL.md +58 -0
- package/skills/engineering/report-bug/SKILL.md +155 -0
- package/skills/engineering/resolving-merge-conflicts/SKILL.md +143 -0
- package/skills/engineering/retake/SKILL.md +184 -0
- package/skills/engineering/review-adrs/SKILL.md +125 -0
- package/skills/engineering/start/ADR-FORMAT.md +47 -0
- package/skills/engineering/start/CONTEXT-FORMAT.md +77 -0
- package/skills/engineering/start/DOC-LANDING-FINALIZER.md +9 -0
- package/skills/engineering/start/SKILL.md +179 -0
- package/skills/engineering/tdd/SKILL.md +96 -0
- package/skills/engineering/tdd/deep-modules.md +33 -0
- package/skills/engineering/tdd/interface-design.md +31 -0
- package/skills/engineering/tdd/mocking.md +59 -0
- package/skills/engineering/tdd/tests.md +85 -0
- package/skills/engineering/to-spec/SKILL.md +104 -0
- package/skills/engineering/to-tickets/SKILL.md +158 -0
- package/skills/engineering/triage/AGENT-BRIEF.md +207 -0
- package/skills/engineering/triage/OUT-OF-SCOPE.md +101 -0
- package/skills/engineering/triage/SKILL.md +176 -0
- package/skills/engineering/verify/SKILL.md +136 -0
- package/skills/engineering/wayfinder/SKILL.md +128 -0
- package/skills/engineering/zoom-out/SKILL.md +51 -0
- package/skills/engineering/zoom-out/scripts/graph-mode-plumbing.sh +36 -0
- package/skills/engineering/zoom-out/scripts/tests/contract.test.sh +168 -0
- package/skills/knowledge/README.md +5 -0
- package/skills/knowledge/research/SKILL.md +76 -0
- package/skills/misc/README.md +8 -0
- package/skills/misc/branch-lock/SKILL.md +187 -0
- package/skills/misc/branch-lock/scripts/branch-lock-hook.sh +113 -0
- package/skills/misc/branch-lock/scripts/branch-lock-session-start.sh +81 -0
- package/skills/misc/branch-lock/scripts/branch-lock.sh +68 -0
- package/skills/misc/branch-lock/scripts/lib/dev-config.sh +126 -0
- package/skills/misc/branch-lock/scripts/lib/git-command-classifier.sh +198 -0
- package/skills/misc/branch-lock/scripts/lib/lock-store.sh +67 -0
- package/skills/misc/branch-lock/scripts/lib/scope-resolver.sh +43 -0
- package/skills/misc/branch-lock/scripts/tests/branch-lock-cli.test.sh +114 -0
- package/skills/misc/branch-lock/scripts/tests/claude-plugin-hook.test.sh +117 -0
- package/skills/misc/branch-lock/scripts/tests/codex-hook.test.sh +229 -0
- package/skills/misc/branch-lock/scripts/tests/dev-config.test.sh +99 -0
- package/skills/misc/branch-lock/scripts/tests/git-command-classifier.test.sh +187 -0
- package/skills/misc/branch-lock/scripts/tests/lock-store.test.sh +90 -0
- package/skills/misc/branch-lock/scripts/tests/scope-resolver.test.sh +63 -0
- package/skills/misc/branch-lock/scripts/tests/session-start.test.sh +123 -0
- package/skills/misc/git-guardrails-claude-code/SKILL.md +105 -0
- package/skills/misc/git-guardrails-claude-code/scripts/block-dangerous-git.sh +231 -0
- package/skills/misc/git-guardrails-claude-code/scripts/tests/block-dangerous-git.test.sh +143 -0
- package/skills/misc/migrate-to-shoehorn/SKILL.md +96 -0
- package/skills/misc/setup-pre-commit/SKILL.md +94 -0
- package/skills/productivity/README.md +8 -0
- package/skills/productivity/ff/SKILL.md +116 -0
- package/skills/productivity/handoff/SKILL.md +37 -0
- package/skills/productivity/reflect/SKILL.md +18 -0
- package/skills/productivity/write-a-skill/SKILL.md +190 -0
- 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})}'
|