@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,47 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# envelope-post-attempt.sh — built-in post_attempt default (PRD #207, issue
|
|
3
|
+
# #212; renamed from post_worker in #226).
|
|
4
|
+
#
|
|
5
|
+
# Reconciles the iteration's intermediate envelope state on the AFK state
|
|
6
|
+
# file as soon as the runner returns — before any user `post_attempt` hook
|
|
7
|
+
# runs, so a user notifier/pager integration that reads state already sees
|
|
8
|
+
# the worker's terminal result classified.
|
|
9
|
+
#
|
|
10
|
+
# Contract:
|
|
11
|
+
# stdin — JSON object: post_attempt context with `.result.status` of
|
|
12
|
+
# "success" or "fail".
|
|
13
|
+
# stdout — empty (context unchanged). Pure side-effect on the state file.
|
|
14
|
+
# exit — 0. Never aborts the chain.
|
|
15
|
+
#
|
|
16
|
+
# Env contract (set by afk.sh before dispatch):
|
|
17
|
+
# RED_AFK_STATE_FILE — path to the iteration's afk.state.json;
|
|
18
|
+
# when unset/missing, the default is a no-op.
|
|
19
|
+
# RED_AFK_RESULT_STATUS — redundant fallback for tests / runners that
|
|
20
|
+
# can't shape a ctx; if ctx.result.status is
|
|
21
|
+
# absent the env var is used instead.
|
|
22
|
+
#
|
|
23
|
+
# Disable with `afk.hooks.defaults.envelope: false` in `.red/config.yaml`.
|
|
24
|
+
|
|
25
|
+
set -uo pipefail
|
|
26
|
+
|
|
27
|
+
ctx="$(timeout "${RED_SKILLS_HOOK_STDIN_TIMEOUT_S:-5s}" cat 2>/dev/null || true)"
|
|
28
|
+
|
|
29
|
+
state_file="${RED_AFK_STATE_FILE:-}"
|
|
30
|
+
[[ -z "$state_file" || ! -f "$state_file" ]] && exit 0
|
|
31
|
+
|
|
32
|
+
status=""
|
|
33
|
+
if [[ -n "$ctx" ]]; then
|
|
34
|
+
status="$(printf '%s' "$ctx" | jq -r '.result.status // empty' 2>/dev/null || true)"
|
|
35
|
+
fi
|
|
36
|
+
[[ -z "$status" ]] && status="${RED_AFK_RESULT_STATUS:-}"
|
|
37
|
+
[[ -z "$status" ]] && exit 0
|
|
38
|
+
|
|
39
|
+
tmp="${state_file}.tmp.$$"
|
|
40
|
+
if jq --arg s "$status" '.current = ((.current // {}) + {result_status: $s})' \
|
|
41
|
+
"$state_file" > "$tmp" 2>/dev/null; then
|
|
42
|
+
mv "$tmp" "$state_file"
|
|
43
|
+
else
|
|
44
|
+
rm -f "$tmp"
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
exit 0
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# gradle-pre-worktree.sh — built-in pre_worktree default for Gradle 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 build.gradle* is present at
|
|
7
|
+
# $PROJECT_ROOT, OR when RED_AFK_GRADLE_USER_HOME_BASE is unset
|
|
8
|
+
# (opt-in by env var — we do not claim a path on the user's
|
|
9
|
+
# filesystem without their consent). Otherwise a JSON object
|
|
10
|
+
# that merges {env: {GRADLE_USER_HOME: <slot dir>}} into ctx.
|
|
11
|
+
# exit — 0 on either branch. Never aborts the chain.
|
|
12
|
+
#
|
|
13
|
+
# Side effect: mkdir -p the slot Gradle home so daemons / caches are isolated
|
|
14
|
+
# between worker slots.
|
|
15
|
+
#
|
|
16
|
+
# Disable with `afk.hooks.defaults.gradle: 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
|
+
shopt -s nullglob
|
|
25
|
+
matches=("$project_root"/build.gradle*)
|
|
26
|
+
shopt -u nullglob
|
|
27
|
+
if [[ "${#matches[@]}" -eq 0 ]]; then
|
|
28
|
+
exit 0
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
if [[ -z "${RED_AFK_GRADLE_USER_HOME_BASE:-}" ]]; then
|
|
32
|
+
exit 0
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
slot="${RED_AFK_SLOT:-0}"
|
|
36
|
+
home_dir="${RED_AFK_GRADLE_USER_HOME_BASE}/slot-${slot}"
|
|
37
|
+
|
|
38
|
+
mkdir -p "$home_dir" 2>/dev/null || true
|
|
39
|
+
|
|
40
|
+
printf '%s' "$ctx" | jq -c \
|
|
41
|
+
--arg h "$home_dir" \
|
|
42
|
+
'. + {env: ((.env // {}) + {GRADLE_USER_HOME: $h})}'
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# heartbeat-post-attempt.sh — built-in post_attempt default (PRD #207, issue
|
|
3
|
+
# #212; renamed from post_worker in #226).
|
|
4
|
+
#
|
|
5
|
+
# Contract:
|
|
6
|
+
# stdin — JSON object: post_attempt context (issue, workspace, result).
|
|
7
|
+
# stdout — empty (context unchanged). The default is a pure side-effect:
|
|
8
|
+
# it stops the orchestrator's per-minute heartbeat side-channel
|
|
9
|
+
# and writes the "iteration stopped" boundary marker into the
|
|
10
|
+
# iteration log, migrating the inline `heartbeat_stop` that
|
|
11
|
+
# used to fire from afk.sh right after `run_inner` returned.
|
|
12
|
+
# exit — 0. Never aborts the post_attempt chain (the dispatcher already
|
|
13
|
+
# routes post_attempt rc through `continue` policy, but the
|
|
14
|
+
# default also belt-and-braces this with a 0 exit).
|
|
15
|
+
#
|
|
16
|
+
# Env contract (set by afk.sh before dispatch):
|
|
17
|
+
# RED_AFK_HEARTBEAT_PID — pid of the orchestrator heartbeat sub-shell;
|
|
18
|
+
# empty when no heartbeat was started for this
|
|
19
|
+
# iteration (smoke paths). Killing a stale pid
|
|
20
|
+
# is a no-op via `kill -0` guard.
|
|
21
|
+
# RED_AFK_ITER_LOG — path to the iteration log; the boundary line
|
|
22
|
+
# is appended only when the file exists.
|
|
23
|
+
#
|
|
24
|
+
# Disable with `afk.hooks.defaults.heartbeat: false` in `.red/config.yaml`.
|
|
25
|
+
|
|
26
|
+
set -uo pipefail
|
|
27
|
+
|
|
28
|
+
# Drain stdin (context flows through unchanged — emit nothing).
|
|
29
|
+
timeout "${RED_SKILLS_HOOK_STDIN_TIMEOUT_S:-5s}" cat >/dev/null 2>&1 || true
|
|
30
|
+
|
|
31
|
+
pid="${RED_AFK_HEARTBEAT_PID:-}"
|
|
32
|
+
if [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null; then
|
|
33
|
+
kill "$pid" 2>/dev/null || true
|
|
34
|
+
# Best-effort reap; the sub-shell may already be exiting.
|
|
35
|
+
wait "$pid" 2>/dev/null || true
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
iterlog="${RED_AFK_ITER_LOG:-}"
|
|
39
|
+
if [[ -n "$iterlog" && -f "$iterlog" ]]; then
|
|
40
|
+
printf '[heartbeat] iteration stopped at %s\n' "$(date -Is)" >> "$iterlog"
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
exit 0
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# validation-post-merge.sh — built-in post_merge default (PRD #207, issue #213).
|
|
3
|
+
#
|
|
4
|
+
# Runs CI/smoke validation against the merged primary checkout so user
|
|
5
|
+
# post_merge hooks see the validation status already reconciled into the
|
|
6
|
+
# context. Migrated from the inline `feedback`-style call: the pre-merge
|
|
7
|
+
# `feedback()` in afk.sh stays as the mechanism-owned safety gate (ADR 0008
|
|
8
|
+
# — anything that must abort the merge loudly is mechanism, not a hook), and
|
|
9
|
+
# this default exposes a post-merge re-run hook authors can disable, observe,
|
|
10
|
+
# or replace.
|
|
11
|
+
#
|
|
12
|
+
# Contract:
|
|
13
|
+
# stdin — JSON object: post_merge context (issue, workspace, merge_commit).
|
|
14
|
+
# stdout — JSON object: the same context with
|
|
15
|
+
# `.result = {validation_status: "passed"|"failed"|"skipped",
|
|
16
|
+
# validation_summary: <one-line string>}` merged into
|
|
17
|
+
# it. Non-JSON stdin → pass-through with no validation attempt.
|
|
18
|
+
# exit — 0. Post_merge policy is `continue`; even a failing validation
|
|
19
|
+
# only logs (it cannot roll back the merge — that ship has sailed
|
|
20
|
+
# at this lifecycle point).
|
|
21
|
+
#
|
|
22
|
+
# Env contract (set by afk.sh before dispatch):
|
|
23
|
+
# RED_AFK_WORKSPACE — primary checkout (post-merge tip). When unset or
|
|
24
|
+
# not a directory, the default is a no-op.
|
|
25
|
+
# RED_AFK_MERGE_COMMIT — full sha of the merge commit on `target`. Not
|
|
26
|
+
# consumed by this default but documented so user
|
|
27
|
+
# hooks reading env directly see a consistent view.
|
|
28
|
+
#
|
|
29
|
+
# Disable with `afk.hooks.defaults.validation: false` in `.red/config.yaml`.
|
|
30
|
+
|
|
31
|
+
set -uo pipefail
|
|
32
|
+
|
|
33
|
+
ctx="$(timeout "${RED_SKILLS_HOOK_STDIN_TIMEOUT_S:-5s}" cat 2>/dev/null || true)"
|
|
34
|
+
ws="${RED_AFK_WORKSPACE:-}"
|
|
35
|
+
|
|
36
|
+
emit_ctx() {
|
|
37
|
+
local status="$1" summary="$2"
|
|
38
|
+
if [[ -z "$ctx" ]]; then
|
|
39
|
+
return 0
|
|
40
|
+
fi
|
|
41
|
+
printf '%s' "$ctx" \
|
|
42
|
+
| jq --arg s "$status" --arg sum "$summary" \
|
|
43
|
+
'.result = ((.result // {}) + {validation_status: $s, validation_summary: $sum})' \
|
|
44
|
+
2>/dev/null \
|
|
45
|
+
|| printf '%s' "$ctx"
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if [[ -z "$ws" || ! -d "$ws" ]]; then
|
|
49
|
+
emit_ctx "skipped" "no workspace"
|
|
50
|
+
exit 0
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
if ! [[ -f "$ws/package.json" ]]; then
|
|
54
|
+
emit_ctx "skipped" "no package.json"
|
|
55
|
+
exit 0
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
# Run pnpm test/typecheck/lint/build at the workspace root if the script
|
|
59
|
+
# exists. This mirrors the mechanism-owned `feedback()` step shape but
|
|
60
|
+
# operates on the merged tip rather than the worker's worktree.
|
|
61
|
+
overall="passed"
|
|
62
|
+
parts=""
|
|
63
|
+
for script in test typecheck lint build; do
|
|
64
|
+
if jq -e --arg s "$script" '.scripts[$s] // empty' "$ws/package.json" >/dev/null 2>&1; then
|
|
65
|
+
if ( cd "$ws" && pnpm -s "$script" >/dev/null 2>&1 ); then
|
|
66
|
+
parts+="${script}:✓ "
|
|
67
|
+
else
|
|
68
|
+
parts+="${script}:✗ "
|
|
69
|
+
overall="failed"
|
|
70
|
+
fi
|
|
71
|
+
else
|
|
72
|
+
parts+="${script}:skip "
|
|
73
|
+
fi
|
|
74
|
+
done
|
|
75
|
+
|
|
76
|
+
emit_ctx "$overall" "${parts% }"
|
|
77
|
+
exit 0
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# afk detectors
|
|
2
|
+
|
|
3
|
+
Shipped detectors invoked by the AFK runtime's hook orchestrator
|
|
4
|
+
at the `pre-spawn` hook point. Each
|
|
5
|
+
detector is a self-contained shell script that decides, on its own,
|
|
6
|
+
whether it applies to the current project; if so, it writes
|
|
7
|
+
`KEY=value` exports into `$RED_AFK_HOOK_ENV_FILE` and exits 0. The
|
|
8
|
+
orchestrator sources that env-file back into the spawn shell, so a
|
|
9
|
+
detector's exports propagate to the worker.
|
|
10
|
+
|
|
11
|
+
## Convention
|
|
12
|
+
|
|
13
|
+
A detector is a single `*.sh` file in this directory. Project-local
|
|
14
|
+
detectors at `<project>/.red/hooks/detectors/*.sh` follow the same
|
|
15
|
+
convention. Each script must:
|
|
16
|
+
|
|
17
|
+
1. Run an **applicability check** first. If the detector does not
|
|
18
|
+
apply to the current project (or required opt-in env vars are
|
|
19
|
+
unset), `exit 1` immediately. Exit code 1 means "not applicable"
|
|
20
|
+
to the orchestrator — it is never an error and never appears in
|
|
21
|
+
the boot-log.
|
|
22
|
+
2. If applicable, write zero-or-more `KEY=value` lines to
|
|
23
|
+
`$RED_AFK_HOOK_ENV_FILE`, then `exit 0`. The env-file is unique per
|
|
24
|
+
invocation and the orchestrator deletes it after sourcing.
|
|
25
|
+
3. Any other exit code is treated as an error. On a `pre-*` hook the
|
|
26
|
+
orchestrator aborts; on a `post-*` hook it logs and continues.
|
|
27
|
+
|
|
28
|
+
The orchestrator runs detectors in C-locale alphabetical order:
|
|
29
|
+
shipped first, then project. Detectors share no state — each runs in
|
|
30
|
+
its own subprocess with a fresh `$RED_AFK_HOOK_ENV_FILE`.
|
|
31
|
+
|
|
32
|
+
## Available env
|
|
33
|
+
|
|
34
|
+
The orchestrator exports the standard AFK contract before invoking a
|
|
35
|
+
detector:
|
|
36
|
+
|
|
37
|
+
- `RED_AFK_SLOT` — supervisor slot index (defaults to `0` when not run
|
|
38
|
+
under the supervisor).
|
|
39
|
+
- `RED_AFK_PLUGIN_DIR`, `PROJECT_ROOT` — discovery roots.
|
|
40
|
+
- `RED_AFK_HOOK_ENV_FILE` — the path the detector must write to.
|
|
41
|
+
|
|
42
|
+
## Disabling a shipped detector
|
|
43
|
+
|
|
44
|
+
Set the matching key under `afk.hooks.defaults` in `.red/config.yaml`
|
|
45
|
+
to `false`:
|
|
46
|
+
|
|
47
|
+
```yaml
|
|
48
|
+
afk:
|
|
49
|
+
hooks:
|
|
50
|
+
defaults:
|
|
51
|
+
cargo: false
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Project detectors are never gated by config — they only exist when
|
|
55
|
+
the project authored the file.
|
|
56
|
+
|
|
57
|
+
## Shipped detectors
|
|
58
|
+
|
|
59
|
+
- **`cargo.sh`** — applies on Rust projects (`Cargo.toml` present).
|
|
60
|
+
Exports `CARGO_TARGET_DIR=${RED_AFK_CARGO_TARGET_BASE:-/opt/cargo-target}/slot-${RED_AFK_SLOT}`,
|
|
61
|
+
pre-creating the directory with `mkdir -p` so the first run on a
|
|
62
|
+
fresh host succeeds. Override the base with `RED_AFK_CARGO_TARGET_BASE`.
|
|
63
|
+
- **`gradle.sh`** — applies on Gradle projects (`build.gradle*`
|
|
64
|
+
present) **and** only when `RED_AFK_GRADLE_USER_HOME_BASE` is set in the
|
|
65
|
+
supervisor's env. Without that base var the detector is a no-op
|
|
66
|
+
(exits 1) — opt-in so we never claim a path on the user's
|
|
67
|
+
filesystem without consent. When both conditions hold, exports
|
|
68
|
+
`GRADLE_USER_HOME=${RED_AFK_GRADLE_USER_HOME_BASE}/slot-${RED_AFK_SLOT}`,
|
|
69
|
+
pre-creating the directory.
|
|
70
|
+
|
|
71
|
+
Both detectors give each worker slot its own build cache so Cargo
|
|
72
|
+
and Gradle never serialize on shared lock files.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# cargo.sh — afk pre-spawn detector for Rust projects.
|
|
3
|
+
#
|
|
4
|
+
# Applies when a Cargo.toml exists at PROJECT_ROOT. Exports
|
|
5
|
+
# CARGO_TARGET_DIR=${RED_AFK_CARGO_TARGET_BASE:-/opt/cargo-target}/slot-${RED_AFK_SLOT}
|
|
6
|
+
# so each worker slot gets its own target directory and Cargo
|
|
7
|
+
# never serializes on .cargo-lock between workers.
|
|
8
|
+
#
|
|
9
|
+
# Exit codes:
|
|
10
|
+
# 1 — not a Rust project (no Cargo.toml). The orchestrator treats
|
|
11
|
+
# this as "skip silently".
|
|
12
|
+
# 0 — applies; KEY=value line(s) written to $RED_AFK_HOOK_ENV_FILE.
|
|
13
|
+
|
|
14
|
+
set -u
|
|
15
|
+
|
|
16
|
+
project_root="${PROJECT_ROOT:-$(pwd)}"
|
|
17
|
+
[ -f "$project_root/Cargo.toml" ] || exit 1
|
|
18
|
+
|
|
19
|
+
slot="${RED_AFK_SLOT:-0}"
|
|
20
|
+
base="${RED_AFK_CARGO_TARGET_BASE:-/opt/cargo-target}"
|
|
21
|
+
target_dir="${base}/slot-${slot}"
|
|
22
|
+
|
|
23
|
+
mkdir -p "$target_dir"
|
|
24
|
+
|
|
25
|
+
: "${RED_AFK_HOOK_ENV_FILE:?RED_AFK_HOOK_ENV_FILE not set}"
|
|
26
|
+
printf 'CARGO_TARGET_DIR=%s\n' "$target_dir" >> "$RED_AFK_HOOK_ENV_FILE"
|
|
27
|
+
exit 0
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# gradle.sh — afk pre-spawn detector for Gradle projects.
|
|
3
|
+
#
|
|
4
|
+
# Applies when a build.gradle* file exists at PROJECT_ROOT AND the
|
|
5
|
+
# operator has opted in by setting RED_AFK_GRADLE_USER_HOME_BASE. Exports
|
|
6
|
+
# GRADLE_USER_HOME=${RED_AFK_GRADLE_USER_HOME_BASE}/slot-${RED_AFK_SLOT} so each
|
|
7
|
+
# worker slot gets its own Gradle home (caches, daemons, lockfiles).
|
|
8
|
+
#
|
|
9
|
+
# Opt-in by env var is deliberate: we will not claim a path on the
|
|
10
|
+
# user's filesystem without their consent. Without
|
|
11
|
+
# RED_AFK_GRADLE_USER_HOME_BASE the detector is a no-op (exit 1).
|
|
12
|
+
#
|
|
13
|
+
# Exit codes:
|
|
14
|
+
# 1 — not a Gradle project, or RED_AFK_GRADLE_USER_HOME_BASE unset.
|
|
15
|
+
# 0 — applies; KEY=value line written to $RED_AFK_HOOK_ENV_FILE.
|
|
16
|
+
|
|
17
|
+
set -u
|
|
18
|
+
|
|
19
|
+
project_root="${PROJECT_ROOT:-$(pwd)}"
|
|
20
|
+
|
|
21
|
+
shopt -s nullglob
|
|
22
|
+
matches=("$project_root"/build.gradle*)
|
|
23
|
+
shopt -u nullglob
|
|
24
|
+
[ "${#matches[@]}" -gt 0 ] || exit 1
|
|
25
|
+
|
|
26
|
+
[ -n "${RED_AFK_GRADLE_USER_HOME_BASE:-}" ] || exit 1
|
|
27
|
+
|
|
28
|
+
slot="${RED_AFK_SLOT:-0}"
|
|
29
|
+
home_dir="${RED_AFK_GRADLE_USER_HOME_BASE}/slot-${slot}"
|
|
30
|
+
|
|
31
|
+
mkdir -p "$home_dir"
|
|
32
|
+
|
|
33
|
+
: "${RED_AFK_HOOK_ENV_FILE:?RED_AFK_HOOK_ENV_FILE not set}"
|
|
34
|
+
printf 'GRADLE_USER_HOME=%s\n' "$home_dir" >> "$RED_AFK_HOOK_ENV_FILE"
|
|
35
|
+
exit 0
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# AFK boot-time sweeps (reference)
|
|
2
|
+
|
|
3
|
+
> Extracted from `afk/SKILL.md` for progressive disclosure. Consulted on demand — not the agent's step-by-step loop.
|
|
4
|
+
>
|
|
5
|
+
> How the bundle reclaims stale tmp-lane state at boot. Durable AFK process state
|
|
6
|
+
> lives in the state tier under `.red/state/castle/`; the agent never runs these by
|
|
7
|
+
> hand because they are the bundle's startup hygiene. Referenced from *Bootstrap*
|
|
8
|
+
> and the *Per-Issue Loop* close step.
|
|
9
|
+
|
|
10
|
+
The lane janitor only acts on registered disposable lanes under `.red/tmp/`.
|
|
11
|
+
Tmp lane cleanup never deletes `.red/state/`, plugin stores, tracked config, or
|
|
12
|
+
`.red/researches/`.
|
|
13
|
+
|
|
14
|
+
## Orphan Cleanup (boot-time)
|
|
15
|
+
|
|
16
|
+
Right after bootstrap and before *Straggler Check*, `/afk` runs two passes. First it reconciles leftover **legacy flat** `.red/tmp/work-*` dirs from the pre-nested scheme (the drain-first cutover, issue #252). This sweep is pid-guarded and slug-sparing: slug-named manual worktrees are left alone, and numeric legacy work dirs are removed only when their recorded or inferable process is no longer live. Then it sweeps the nested attempt dirs in every worker namespace (`.red/tmp/workers/*/*/`, `.red/tmp/go-workers/*/*/`, and `.red/tmp/scout-workers/*/*/`) whose parent worker's `worker.pid` is dead, and afterwards removes dead empty worker shells across those namespaces: dead/corrupt/missing `worker.pid` files plus the worker dir when it contains no attempt dirs or only empty attempt dirs. Live `worker.pid` dirs and non-empty preserved attempt dirs are left untouched. For each orphaned attempt dir:
|
|
17
|
+
|
|
18
|
+
1. **(Slice D — heartbeat sub-shell retired.)** No zombie reap step is needed; older state files may still carry a `heartbeat_pid` but it's vestigial and ignored.
|
|
19
|
+
2. **Decide fate from issue state.** `gh issue view N --json labels,state`:
|
|
20
|
+
- `state == CLOSED` → `rm -rf`. Work landed; nothing to inspect.
|
|
21
|
+
- label `ready-for-human` → **split TTL** based on `envelope.posted` in the attempt state file (see *Terminal-Event Envelope* below):
|
|
22
|
+
- `envelope.posted == true` → 1-day TTL. The issue thread already carries the canonical record; the local dir is pure redundancy.
|
|
23
|
+
- `envelope.posted == false` or field missing → 7-day TTL. The envelope POST failed (or this dir predates the envelope writer), so the local notes/log are the only copy.
|
|
24
|
+
- label `running` (orchestrator crashed mid-issue) → restore `ready-for-agent`, post a recovery comment, then `rm -rf`. Leaving the issue eternally `running` is worse than losing the dir.
|
|
25
|
+
- any other state → `rm -rf`.
|
|
26
|
+
3. **Fallback on gh failure.** Network / rate-limit error → fall back to mtime TTL: 7 days for dirs with a state file, 1 day for dirs without one. Conservative enough to survive transient outages without losing artefacts the human wanted.
|
|
27
|
+
|
|
28
|
+
This removes the manual "remember to clean `.red/tmp/`" discipline. Blocker dirs persist until their TTL expires; everything else self-collects on the next `/afk` run.
|
|
29
|
+
|
|
30
|
+
## Attempt Cap (boot-time, issue #257)
|
|
31
|
+
|
|
32
|
+
The *Completion sweep* (close step 11) only fires when an issue completes. Issues that **never** complete — blocked-forever work that accumulates retries — would otherwise leak attempt dirs indefinitely. Right after *Orphan Cleanup*, `cap_issue_attempts` walks every attempt dir across all workers, groups them by issue, and per issue prunes (newest attempt kept first) anything over either cap:
|
|
33
|
+
|
|
34
|
+
- **Age cap** — fixed at 14 days. An attempt dir whose mtime is older than this is reclaimed.
|
|
35
|
+
- **Count cap** — fixed at 5. Only the newest five attempts (by attempt number) for one issue are retained; older ones are reclaimed.
|
|
36
|
+
|
|
37
|
+
Both caps share the completion sweep's invariant: a **live** worker's active attempt (state file carrying a live `pid`) is never counted toward the cap nor removed.
|
|
38
|
+
|
|
39
|
+
## On-Demand Branch Reaper (issue #275)
|
|
40
|
+
|
|
41
|
+
Run `/afk reap` (the bundle's `reap` command) to perform branch hygiene without starting a worker, claiming an issue, or firing lifecycle hooks. The command first prints one line:
|
|
42
|
+
|
|
43
|
+
```text
|
|
44
|
+
afk branch counts: remote-afk=N local-afk=N
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
It then applies the same live-branch reapers used during `/afk` boot: remote `afk/*` and local `afk/*`. Open issues and transiently unclassified issues are kept; local branches checked out by any worktree are kept. Each successful deletion logs the branch, issue number, and classification reason. Re-running is a natural no-op once stale refs are gone.
|
|
48
|
+
|
|
49
|
+
## Docs Sweep (boot-time)
|
|
50
|
+
|
|
51
|
+
After branch cleanup and before the dependency Unblock Sweep, `/afk` verifies
|
|
52
|
+
that source-of-truth docs under `.red/` are visible from the freshly fetched
|
|
53
|
+
base. The sweep covers glossary docs (`.red/CONTEXT.md`,
|
|
54
|
+
`.red/CONTEXT-MAP.md`, `.red/contexts/**`) and ADR docs (`.red/adr/**`) from
|
|
55
|
+
three signals:
|
|
56
|
+
|
|
57
|
+
- working-tree changes and deletions;
|
|
58
|
+
- untracked or ignored docs, including adopter repos that ignore `.red/`;
|
|
59
|
+
- doc paths changed by commits ahead of `origin/<base>`.
|
|
60
|
+
|
|
61
|
+
Operational surfaces are excluded: `.red/tmp/`, `.red/wiki/`, `.red/memory/`,
|
|
62
|
+
`.red/brain/`, and `.red/state/`.
|
|
63
|
+
|
|
64
|
+
When every stranded doc is publishable, `/afk` creates one isolated worktree
|
|
65
|
+
under `.red/tmp/docs-sweep/` from `origin/<base>`, copies only those doc paths,
|
|
66
|
+
creates one `docs:` commit, pushes a branch, opens one PR, merges it, and fetches
|
|
67
|
+
the base afterward. It never commits in the primary checkout, switches branches,
|
|
68
|
+
stashes, or resets.
|
|
69
|
+
|
|
70
|
+
When the base cannot be fetched, the PR lane fails, or an ignored doc path class
|
|
71
|
+
has no tracked precedent on `origin/<base>`, boot halts before issue selection or
|
|
72
|
+
fleet slot spawn. The halt message carries the explicit relative file list so a
|
|
73
|
+
maintainer can land or remove the stranded docs deliberately.
|
|
74
|
+
|
|
75
|
+
## Fleet mode: the supervisor owns the boot (issue #623)
|
|
76
|
+
|
|
77
|
+
The sweeps above (*Orphan Cleanup*, *Attempt Cap*, live branch cleanup, *Docs Sweep*, the *Unblock Sweep*, and the *Straggler Check*) all read and mutate **shared** `.red/tmp` / branch / `gh` state. When several workers boot at once they would race over that state — the observed failure mode was a fleet collapsing to a single live worker because peers fast-died in their boot sweeps.
|
|
78
|
+
|
|
79
|
+
So in **fleet mode** the **supervisor** runs the full sweep suite **exactly once, before it spawns any worker**, and logs the result to `.red/tmp/supervisors/default/supervisor.log.toonl` (`boot sweeps complete: orphans … | attempt-cap … | branches … | docs-sweep … | unblocked … | stragglers …`). Every worker the supervisor spawns carries a `RED_AFK_SWEEPS_DONE=1` marker; a marked worker's boot is **bootstrap + claim only** — it ensures its dirs/gitignore/`worker.pid` and goes straight to claiming an issue, skipping every shared sweep. This makes respawns cheap and keeps workers from racing each other over boot state. The supervisor runs the sweeps a single time per lifetime: a worker that exits and is respawned does **not** re-trigger them.
|
|
80
|
+
|
|
81
|
+
A **solo** `/afk run` (no supervisor, no marker) is unchanged — it runs the full sweep suite itself, exactly as described above. `/afk run --boot-only` reports which path it took: `sweeps ran` for a solo boot, `sweeps skipped (supervisor-owned)` for a marked worker.
|