@reddb-io/red-skills 4.4.1 → 4.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/bin/build-info.mjs +87 -0
  2. package/bin/cli-frontdoor.mjs +14 -0
  3. package/bin/red-skills-brain.mjs +17 -22
  4. package/bin/red-skills-content.mjs +17 -0
  5. package/bin/red-skills-hook.mjs +45 -0
  6. package/bin/red-skills-memory.mjs +17 -22
  7. package/bin/red-skills-redskilled-mcp.mjs +2 -0
  8. package/bin/red-skills-resource.mjs +21 -0
  9. package/bin/runtime-compatibility.mjs +17 -0
  10. package/bin/runtime-paths.mjs +13 -0
  11. package/dist/code-nav.bundle.min.mjs +1 -1
  12. package/dist/opencode-host.bundle.min.mjs +1 -1
  13. package/dist/redskilled-mcp.bundle.min.mjs +36 -36
  14. package/dist/redskilled-web.bundle.min.mjs +311 -0
  15. package/dist/redskilled.bundle.min.mjs +306 -292
  16. package/dist/release.bundle.min.mjs +1 -1
  17. package/dist/rsp-core.bundle.min.mjs +1 -1
  18. package/dist/rsp.bundle.min.mjs +1 -1
  19. package/dist/statusline.bundle.min.mjs +1 -1
  20. package/package.json +12 -4
  21. package/runtime/hook-routes.json +125 -0
  22. package/runtime/plugins/brain/scripts/bootstrap.mjs +656 -0
  23. package/runtime/plugins/brain/scripts/structural-smoke.sh +7 -0
  24. package/runtime/plugins/dev/hooks/branch-lock-codex.sh +105 -0
  25. package/runtime/plugins/dev/hooks/code-nav-mcp.sh +63 -0
  26. package/runtime/plugins/dev/hooks/command-guard.sh +893 -0
  27. package/runtime/plugins/dev/hooks/ensure-codex-statusline.mjs +144 -0
  28. package/runtime/plugins/dev/hooks/red-fetch.mjs +38 -0
  29. package/runtime/plugins/dev/hooks/redskilled-mcp.sh +64 -0
  30. package/runtime/plugins/dev/hooks/rsp-hook.sh +343 -0
  31. package/runtime/plugins/dev/hooks/tests/command-guard.test.sh +378 -0
  32. package/runtime/plugins/dev/hooks/tests/rsp-hook.test.sh +229 -0
  33. package/runtime/plugins/dev/scripts/memory-bridge.sh +230 -0
  34. package/runtime/plugins/dev/scripts/structural-smoke.sh +7 -0
  35. package/runtime/plugins/dev/scripts/tests/memory-bridge.test.sh +555 -0
  36. package/runtime/plugins/dev/skills/engineering/afk/defaults/cargo-pre-worktree.sh +37 -0
  37. package/runtime/plugins/dev/skills/engineering/afk/defaults/envelope-post-attempt.sh +47 -0
  38. package/runtime/plugins/dev/skills/engineering/afk/defaults/gradle-pre-worktree.sh +45 -0
  39. package/runtime/plugins/dev/skills/engineering/afk/defaults/heartbeat-post-attempt.sh +43 -0
  40. package/runtime/plugins/dev/skills/engineering/afk/defaults/validation-post-merge.sh +77 -0
  41. package/runtime/plugins/dev/skills/engineering/afk/detectors/cargo.sh +27 -0
  42. package/runtime/plugins/dev/skills/engineering/afk/detectors/gradle.sh +38 -0
  43. package/runtime/plugins/dev/skills/engineering/afk/examples/only-mine.sh +32 -0
  44. package/runtime/plugins/dev/skills/engineering/afk/examples/red-afk-attempt-action.yml +53 -0
  45. package/runtime/plugins/dev/skills/engineering/afk/examples/rs-afk-attempt.yml +76 -0
  46. package/runtime/plugins/dev/skills/engineering/afk/hooks/red-cargo +38 -0
  47. package/runtime/plugins/dev/skills/engineering/afk/hooks/red-envelope +50 -0
  48. package/runtime/plugins/dev/skills/engineering/afk/hooks/red-gradle +42 -0
  49. package/runtime/plugins/dev/skills/engineering/afk/hooks/red-heartbeat +46 -0
  50. package/runtime/plugins/dev/skills/engineering/afk/hooks/red-validation +79 -0
  51. package/runtime/plugins/dev/skills/engineering/diagnose/scripts/hitl-loop.template.sh +41 -0
  52. package/runtime/plugins/dev/skills/engineering/red-setup/scripts/install-runtime-shim.sh +181 -0
  53. package/runtime/plugins/dev/skills/engineering/red-setup/workflows/red-issues-needs-triage.yml +36 -0
  54. package/runtime/plugins/dev/skills/engineering/wizard/template.sh +211 -0
  55. package/runtime/plugins/dev/skills/engineering/zoom-out/scripts/graph-mode-plumbing.sh +36 -0
  56. package/runtime/plugins/dev/skills/engineering/zoom-out/scripts/tests/contract.test.sh +168 -0
  57. package/runtime/plugins/dev/skills/misc/branch-lock/scripts/branch-lock-hook.sh +113 -0
  58. package/runtime/plugins/dev/skills/misc/branch-lock/scripts/branch-lock-session-start.sh +81 -0
  59. package/runtime/plugins/dev/skills/misc/branch-lock/scripts/branch-lock.sh +68 -0
  60. package/runtime/plugins/dev/skills/misc/branch-lock/scripts/lib/dev-config.sh +126 -0
  61. package/runtime/plugins/dev/skills/misc/branch-lock/scripts/lib/git-command-classifier.sh +198 -0
  62. package/runtime/plugins/dev/skills/misc/branch-lock/scripts/lib/lock-store.sh +67 -0
  63. package/runtime/plugins/dev/skills/misc/branch-lock/scripts/lib/scope-resolver.sh +43 -0
  64. package/runtime/plugins/dev/skills/misc/branch-lock/scripts/tests/branch-lock-cli.test.sh +114 -0
  65. package/runtime/plugins/dev/skills/misc/branch-lock/scripts/tests/claude-plugin-hook.test.sh +117 -0
  66. package/runtime/plugins/dev/skills/misc/branch-lock/scripts/tests/codex-hook.test.sh +230 -0
  67. package/runtime/plugins/dev/skills/misc/branch-lock/scripts/tests/dev-config.test.sh +99 -0
  68. package/runtime/plugins/dev/skills/misc/branch-lock/scripts/tests/git-command-classifier.test.sh +187 -0
  69. package/runtime/plugins/dev/skills/misc/branch-lock/scripts/tests/lock-store.test.sh +90 -0
  70. package/runtime/plugins/dev/skills/misc/branch-lock/scripts/tests/scope-resolver.test.sh +63 -0
  71. package/runtime/plugins/dev/skills/misc/branch-lock/scripts/tests/session-start.test.sh +123 -0
  72. package/runtime/plugins/dev/skills/misc/git-guardrails-claude-code/scripts/block-dangerous-git.sh +231 -0
  73. package/runtime/plugins/dev/skills/misc/git-guardrails-claude-code/scripts/tests/block-dangerous-git.test.sh +143 -0
  74. package/runtime/plugins/internal/scripts/structural-smoke.sh +7 -0
  75. package/runtime/plugins/internal/skills/maintainer/create-plugin/scripts/create-plugin.sh +308 -0
  76. package/runtime/plugins/memory/scripts/bootstrap.mjs +659 -0
  77. package/runtime/plugins/memory/scripts/structural-smoke.sh +7 -0
  78. package/scripts/check-skills-boundary.mjs +32 -0
@@ -0,0 +1,555 @@
1
+ #!/usr/bin/env bash
2
+ # Tests for plugins/dev/scripts/memory-bridge.sh (issue #57, PRD #49).
3
+ #
4
+ # The bridge is dev's SOFT, one-directional hook into the memory plugin. The
5
+ # acceptance criteria it must satisfy:
6
+ # - memory installed + initialized → recall runs and its output is returned;
7
+ # - graph memory with neighbors/path → focused graph reads are returned;
8
+ # - memory absent / uninitialized → behaves exactly as before: silent no-op,
9
+ # exit 0, no output, no error;
10
+ # - detection has two gates (config opt-in AND a resolvable CLI), and the CLI
11
+ # resolution cascade prefers $RED_MEMORY_CLI, then a `memory` bin on PATH,
12
+ # then a built sibling/in-repo dist.
13
+
14
+ set -uo pipefail
15
+
16
+ HERE="$(cd "$(dirname "$0")" && pwd)"
17
+ SCRIPTS="$(dirname "$HERE")"
18
+ LIB="$SCRIPTS/memory-bridge.sh"
19
+
20
+ # shellcheck source=../memory-bridge.sh
21
+ source "$LIB"
22
+
23
+ pass=0
24
+ fail=0
25
+
26
+ expect_eq() {
27
+ local label="$1" expected="$2" actual="$3"
28
+ if [[ "$expected" == "$actual" ]]; then
29
+ echo "PASS $label"
30
+ pass=$((pass + 1))
31
+ else
32
+ printf 'FAIL %s\n expected: %q\n actual: %q\n' "$label" "$expected" "$actual"
33
+ fail=$((fail + 1))
34
+ fi
35
+ }
36
+
37
+ expect_ok() {
38
+ local label="$1"; shift
39
+ if "$@"; then
40
+ echo "PASS $label"
41
+ pass=$((pass + 1))
42
+ else
43
+ echo "FAIL $label (command failed: $*)"
44
+ fail=$((fail + 1))
45
+ fi
46
+ }
47
+
48
+ expect_fail() {
49
+ local label="$1"; shift
50
+ if "$@"; then
51
+ echo "FAIL $label (expected non-zero, got success)"
52
+ fail=$((fail + 1))
53
+ else
54
+ echo "PASS $label"
55
+ pass=$((pass + 1))
56
+ fi
57
+ }
58
+
59
+ # ---------- fixtures ----------
60
+
61
+ TMP="$(mktemp -d)"
62
+ trap 'rm -rf "$TMP"' EXIT
63
+
64
+ # A repo that has opted into memory (config present).
65
+ INIT_ROOT="$TMP/init-repo"
66
+ mkdir -p "$INIT_ROOT/.red/memory"
67
+ echo '{"version":1,"mode":"markdown-only"}' > "$INIT_ROOT/.red/memory/config.json"
68
+
69
+ GRAPH_ROOT="$TMP/graph-repo"
70
+ mkdir -p "$GRAPH_ROOT/.red/memory"
71
+ echo '{"version":1,"mode":"graph","storePath":".red/memory/graph.rdb"}' > "$GRAPH_ROOT/.red/memory/config.json"
72
+
73
+ # A repo that never ran memory init (no config).
74
+ BARE_ROOT="$TMP/bare-repo"
75
+ mkdir -p "$BARE_ROOT"
76
+
77
+ # A fake `memory` CLI on PATH that echoes a recognisable block and the query it got.
78
+ FAKE_BIN="$TMP/bin"
79
+ mkdir -p "$FAKE_BIN"
80
+ cat > "$FAKE_BIN/memory" <<'EOF'
81
+ #!/usr/bin/env bash
82
+ case "$1" in
83
+ recall)
84
+ shift # drop the "recall" verb
85
+ echo "memory: 1 match for \"$*\""
86
+ echo " [1.0] note-1"
87
+ ;;
88
+ stats)
89
+ echo "memory: 17 node(s), 3 edge(s)"
90
+ ;;
91
+ neighbors)
92
+ label="$2"
93
+ echo "memory: 2 neighbor(s) of \"$label\""
94
+ echo " d1 node-auth (module) auth-service"
95
+ echo " d1 node-cache (concept) cache-ttl"
96
+ ;;
97
+ path)
98
+ from="$2"
99
+ to="$3"
100
+ echo "memory: path \"$from\" -> \"$to\": 2 hop(s), weight 2"
101
+ ;;
102
+ structural-impact)
103
+ file=""
104
+ while [ "$#" -gt 0 ]; do
105
+ if [ "$1" = "--file" ]; then file="$2"; fi
106
+ shift || true
107
+ done
108
+ echo "memory: structural impact for file $file"
109
+ echo " /repo/src/app.ts imports node:path"
110
+ echo " /repo/src/consumer.ts imports this target through ./app.js"
111
+ ;;
112
+ attempt)
113
+ if [[ "$2" != "record" ]]; then
114
+ echo "unexpected attempt command: $*" >&2
115
+ exit 1
116
+ fi
117
+ cat > "${FAKE_ATTEMPT_OUT:?}"
118
+ echo "memory: recorded attempt"
119
+ ;;
120
+ *)
121
+ echo "unexpected command: $*" >&2
122
+ exit 1
123
+ ;;
124
+ esac
125
+ EOF
126
+ chmod +x "$FAKE_BIN/memory"
127
+
128
+ # A clean environment helper: clear every override the bridge consults.
129
+ clear_env() { unset RED_MEMORY_CLI CLAUDE_PLUGIN_ROOT MEMORY_REPO_ROOT; }
130
+
131
+ # ---------- _memory_resolve_cli cascade ----------
132
+
133
+ # RED_MEMORY_CLI override (does not need node to *resolve*).
134
+ clear_env
135
+ override="$TMP/custom-cli.js"
136
+ echo '// fake' > "$override"
137
+ RED_MEMORY_CLI="$override" _memory_resolve_cli
138
+ expect_eq "resolve: \$RED_MEMORY_CLI -> node <path>" "node $override" "${MEMORY_CLI[*]}"
139
+
140
+ # RED_MEMORY_CLI set but file missing → no resolution.
141
+ clear_env
142
+ expect_fail "resolve: \$RED_MEMORY_CLI missing file fails" \
143
+ bash -c 'source "'"$LIB"'"; RED_MEMORY_CLI=/nope/cli.js _memory_resolve_cli'
144
+
145
+ # `memory` on PATH.
146
+ clear_env
147
+ PATH="$FAKE_BIN:$PATH" _memory_resolve_cli
148
+ expect_eq "resolve: memory on PATH -> (memory)" "memory" "${MEMORY_CLI[*]}"
149
+
150
+ # in-repo dist via MEMORY_REPO_ROOT.
151
+ clear_env
152
+ REPO="$TMP/monorepo"
153
+ mkdir -p "$REPO/plugins/memory/dist"
154
+ echo '// fake' > "$REPO/plugins/memory/dist/cli.js"
155
+ MEMORY_REPO_ROOT="$REPO" _memory_resolve_cli
156
+ expect_eq "resolve: MEMORY_REPO_ROOT in-repo dist -> node <path>" \
157
+ "node $REPO/plugins/memory/dist/cli.js" "${MEMORY_CLI[*]}"
158
+
159
+ # sibling dist via CLAUDE_PLUGIN_ROOT (dev plugin root; memory is its sibling).
160
+ clear_env
161
+ DEVROOT="$TMP/plugins/dev"
162
+ mkdir -p "$DEVROOT" "$TMP/plugins/memory/dist"
163
+ echo '// fake' > "$TMP/plugins/memory/dist/cli.js"
164
+ CLAUDE_PLUGIN_ROOT="$DEVROOT" _memory_resolve_cli
165
+ expect_eq "resolve: CLAUDE_PLUGIN_ROOT sibling dist -> node <path>" \
166
+ "node $DEVROOT/../memory/dist/cli.js" "${MEMORY_CLI[*]}"
167
+
168
+ # nothing resolvable (empty env, no PATH bin) → fails, MEMORY_CLI empty.
169
+ clear_env
170
+ expect_fail "resolve: nothing available fails" \
171
+ bash -c 'source "'"$LIB"'"; PATH=/usr/bin:/bin _memory_resolve_cli'
172
+
173
+ # ---------- memory_available (two gates) ----------
174
+
175
+ clear_env
176
+ expect_fail "available: no config -> unavailable (even with CLI on PATH)" \
177
+ bash -c 'source "'"$LIB"'"; PATH="'"$FAKE_BIN"':$PATH" memory_available "'"$BARE_ROOT"'"'
178
+
179
+ clear_env
180
+ expect_fail "available: config present but no CLI -> unavailable" \
181
+ bash -c 'source "'"$LIB"'"; PATH=/usr/bin:/bin memory_available "'"$INIT_ROOT"'"'
182
+
183
+ clear_env
184
+ expect_ok "available: config present AND CLI on PATH -> available" \
185
+ bash -c 'source "'"$LIB"'"; PATH="'"$FAKE_BIN"':$PATH" memory_available "'"$INIT_ROOT"'"'
186
+
187
+ # ---------- Memory mode + graph readiness ----------
188
+
189
+ clear_env
190
+ expect_eq "mode: absent config -> unavailable" "unavailable" \
191
+ "$(PATH=/usr/bin:/bin bash -c 'source "'"$LIB"'"; memory_mode "'"$BARE_ROOT"'"')"
192
+
193
+ clear_env
194
+ expect_eq "mode: markdown-only config -> markdown-only" "markdown-only" \
195
+ "$(PATH=/usr/bin:/bin bash -c 'source "'"$LIB"'"; memory_mode "'"$INIT_ROOT"'"')"
196
+
197
+ clear_env
198
+ expect_eq "mode: graph config -> graph" "graph" \
199
+ "$(PATH=/usr/bin:/bin bash -c 'source "'"$LIB"'"; memory_mode "'"$GRAPH_ROOT"'"')"
200
+
201
+ clear_env
202
+ expect_ok "graph-ready: graph config + stats with nodes -> ready" \
203
+ bash -c 'source "'"$LIB"'"; PATH="'"$FAKE_BIN"':$PATH" memory_graph_ready "'"$GRAPH_ROOT"'"'
204
+
205
+ clear_env
206
+ expect_fail "graph-ready: absent config -> not ready" \
207
+ bash -c 'source "'"$LIB"'"; PATH="'"$FAKE_BIN"':$PATH" memory_graph_ready "'"$BARE_ROOT"'"'
208
+
209
+ clear_env
210
+ expect_fail "graph-ready: markdown-only config -> not ready" \
211
+ bash -c 'source "'"$LIB"'"; PATH="'"$FAKE_BIN"':$PATH" memory_graph_ready "'"$INIT_ROOT"'"'
212
+
213
+ clear_env
214
+ EMPTYBIN="$TMP/emptybin"
215
+ mkdir -p "$EMPTYBIN"
216
+ cat > "$EMPTYBIN/memory" <<'EOF'
217
+ #!/usr/bin/env bash
218
+ if [[ "$1" == "stats" ]]; then
219
+ echo "memory: 0 node(s), 0 edge(s)"
220
+ else
221
+ exit 1
222
+ fi
223
+ EOF
224
+ chmod +x "$EMPTYBIN/memory"
225
+ expect_fail "graph-ready: graph stats with zero nodes -> not ready" \
226
+ bash -c 'source "'"$LIB"'"; PATH="'"$EMPTYBIN"':/usr/bin:/bin" memory_graph_ready "'"$GRAPH_ROOT"'"'
227
+
228
+ clear_env
229
+ STATERRBIN="$TMP/staterrbin"
230
+ mkdir -p "$STATERRBIN"
231
+ cat > "$STATERRBIN/memory" <<'EOF'
232
+ #!/usr/bin/env bash
233
+ if [[ "$1" == "stats" ]]; then
234
+ echo "stats boom" >&2
235
+ exit 1
236
+ fi
237
+ exit 1
238
+ EOF
239
+ chmod +x "$STATERRBIN/memory"
240
+ out="$(PATH="$STATERRBIN:/usr/bin:/bin" bash -c 'source "'"$LIB"'"; memory_graph_ready "'"$GRAPH_ROOT"'"' 2>/dev/null)"
241
+ expect_eq "graph-ready: failing stats command -> no stdout" "" "$out"
242
+ expect_fail "graph-ready: failing stats command -> not ready" \
243
+ bash -c 'source "'"$LIB"'"; PATH="'"$STATERRBIN"':/usr/bin:/bin" memory_graph_ready "'"$GRAPH_ROOT"'"'
244
+
245
+ # ---------- memory_neighbors (graph-only, graceful) ----------
246
+
247
+ clear_env
248
+ out="$(PATH="$FAKE_BIN:$PATH" bash -c 'source "'"$LIB"'"; memory_neighbors "'"$GRAPH_ROOT"'" zoom-out 1 both')"
249
+ expect_eq "neighbors: graph ready -> returns CLI output" \
250
+ $'memory: 2 neighbor(s) of "zoom-out"\n d1 node-auth (module) auth-service\n d1 node-cache (concept) cache-ttl' "$out"
251
+ clear_env
252
+ expect_ok "neighbors: graph ready -> exit 0" \
253
+ bash -c 'source "'"$LIB"'"; PATH="'"$FAKE_BIN"':$PATH" memory_neighbors "'"$GRAPH_ROOT"'" zoom-out 1 both'
254
+
255
+ clear_env
256
+ out="$(PATH=/usr/bin:/bin bash -c 'source "'"$LIB"'"; memory_neighbors "'"$GRAPH_ROOT"'" zoom-out 1 both')"
257
+ expect_eq "neighbors: graph config but no CLI -> no output" "" "$out"
258
+ clear_env
259
+ expect_ok "neighbors: graph config but no CLI -> exit 0" \
260
+ bash -c 'source "'"$LIB"'"; PATH=/usr/bin:/bin memory_neighbors "'"$GRAPH_ROOT"'" zoom-out 1 both'
261
+
262
+ clear_env
263
+ out="$(PATH="$FAKE_BIN:$PATH" bash -c 'source "'"$LIB"'"; memory_neighbors "'"$INIT_ROOT"'" zoom-out 1 both')"
264
+ expect_eq "neighbors: markdown-only config -> no output" "" "$out"
265
+
266
+ clear_env
267
+ ZERONEIGHBORBIN="$TMP/zeroneighborbin"
268
+ mkdir -p "$ZERONEIGHBORBIN"
269
+ cat > "$ZERONEIGHBORBIN/memory" <<'EOF'
270
+ #!/usr/bin/env bash
271
+ case "$1" in
272
+ stats)
273
+ echo "memory: 17 node(s), 3 edge(s)"
274
+ ;;
275
+ neighbors)
276
+ echo "memory: 0 neighbor(s) of \"$2\""
277
+ ;;
278
+ *)
279
+ exit 1
280
+ ;;
281
+ esac
282
+ EOF
283
+ chmod +x "$ZERONEIGHBORBIN/memory"
284
+ out="$(PATH="$ZERONEIGHBORBIN:/usr/bin:/bin" bash -c 'source "'"$LIB"'"; memory_neighbors "'"$GRAPH_ROOT"'" zoom-out 1 both')"
285
+ expect_eq "neighbors: zero neighbors -> no output" "" "$out"
286
+
287
+ clear_env
288
+ NEIGHBORERRBIN="$TMP/neighborerrbin"
289
+ mkdir -p "$NEIGHBORERRBIN"
290
+ cat > "$NEIGHBORERRBIN/memory" <<'EOF'
291
+ #!/usr/bin/env bash
292
+ case "$1" in
293
+ stats)
294
+ echo "memory: 17 node(s), 3 edge(s)"
295
+ ;;
296
+ neighbors)
297
+ echo "neighbors boom" >&2
298
+ exit 1
299
+ ;;
300
+ *)
301
+ exit 1
302
+ ;;
303
+ esac
304
+ EOF
305
+ chmod +x "$NEIGHBORERRBIN/memory"
306
+ out="$(PATH="$NEIGHBORERRBIN:/usr/bin:/bin" bash -c 'source "'"$LIB"'"; memory_neighbors "'"$GRAPH_ROOT"'" zoom-out 1 both' 2>/dev/null)"
307
+ expect_eq "neighbors: failing command -> swallowed, no stdout" "" "$out"
308
+ clear_env
309
+ expect_ok "neighbors: failing command -> still exit 0" \
310
+ bash -c 'source "'"$LIB"'"; PATH="'"$NEIGHBORERRBIN"':/usr/bin:/bin" memory_neighbors "'"$GRAPH_ROOT"'" zoom-out 1 both'
311
+
312
+ # ---------- memory_structural_impact (graph-only, graceful) ----------
313
+
314
+ clear_env
315
+ out="$(PATH="$FAKE_BIN:$PATH" bash -c 'source "'"$LIB"'"; memory_structural_impact "'"$GRAPH_ROOT"'" /repo/src/app.ts render')"
316
+ expect_eq "structural-impact: graph ready -> returns CLI output" \
317
+ $'memory: structural impact for file /repo/src/app.ts\n /repo/src/app.ts imports node:path\n /repo/src/consumer.ts imports this target through ./app.js' "$out"
318
+
319
+ clear_env
320
+ out="$(PATH="$FAKE_BIN:$PATH" bash -c 'source "'"$LIB"'"; memory_structural_impact "'"$INIT_ROOT"'" /repo/src/app.ts render')"
321
+ expect_eq "structural-impact: markdown-only config -> no output" "" "$out"
322
+
323
+ clear_env
324
+ NOIMPACTBIN="$TMP/noimpactbin"
325
+ mkdir -p "$NOIMPACTBIN"
326
+ cat > "$NOIMPACTBIN/memory" <<'EOF'
327
+ #!/usr/bin/env bash
328
+ case "$1" in
329
+ stats)
330
+ echo "memory: 17 node(s), 3 edge(s)"
331
+ ;;
332
+ structural-impact)
333
+ file=""
334
+ while [ "$#" -gt 0 ]; do
335
+ if [ "$1" = "--file" ]; then file="$2"; fi
336
+ shift || true
337
+ done
338
+ echo "memory: no structural impact for file $file"
339
+ ;;
340
+ *)
341
+ exit 1
342
+ ;;
343
+ esac
344
+ EOF
345
+ chmod +x "$NOIMPACTBIN/memory"
346
+ out="$(PATH="$NOIMPACTBIN:/usr/bin:/bin" bash -c 'source "'"$LIB"'"; memory_structural_impact "'"$GRAPH_ROOT"'" /repo/src/missing.ts missing')"
347
+ expect_eq "structural-impact: no graph data -> no output" "" "$out"
348
+
349
+ clear_env
350
+ IMPACTERRBIN="$TMP/impacterrbin"
351
+ mkdir -p "$IMPACTERRBIN"
352
+ cat > "$IMPACTERRBIN/memory" <<'EOF'
353
+ #!/usr/bin/env bash
354
+ case "$1" in
355
+ stats)
356
+ echo "memory: 17 node(s), 3 edge(s)"
357
+ ;;
358
+ structural-impact)
359
+ echo "impact boom" >&2
360
+ exit 1
361
+ ;;
362
+ *)
363
+ exit 1
364
+ ;;
365
+ esac
366
+ EOF
367
+ chmod +x "$IMPACTERRBIN/memory"
368
+ out="$(PATH="$IMPACTERRBIN:/usr/bin:/bin" bash -c 'source "'"$LIB"'"; memory_structural_impact "'"$GRAPH_ROOT"'" /repo/src/app.ts render' 2>/dev/null)"
369
+ expect_eq "structural-impact: failing command -> swallowed, no stdout" "" "$out"
370
+ clear_env
371
+ expect_ok "structural-impact: failing command -> still exit 0" \
372
+ bash -c 'source "'"$LIB"'"; PATH="'"$IMPACTERRBIN"':/usr/bin:/bin" memory_structural_impact "'"$GRAPH_ROOT"'" /repo/src/app.ts render'
373
+
374
+ # ---------- memory_path (graph-only, graceful) ----------
375
+
376
+ clear_env
377
+ out="$(PATH="$FAKE_BIN:$PATH" bash -c 'source "'"$LIB"'"; memory_path "'"$GRAPH_ROOT"'" zoom-out memory-bridge bfs')"
378
+ expect_eq "path: graph ready -> returns CLI output" \
379
+ 'memory: path "zoom-out" -> "memory-bridge": 2 hop(s), weight 2' "$out"
380
+ clear_env
381
+ expect_ok "path: graph ready -> exit 0" \
382
+ bash -c 'source "'"$LIB"'"; PATH="'"$FAKE_BIN"':$PATH" memory_path "'"$GRAPH_ROOT"'" zoom-out memory-bridge bfs'
383
+
384
+ clear_env
385
+ out="$(PATH=/usr/bin:/bin bash -c 'source "'"$LIB"'"; memory_path "'"$GRAPH_ROOT"'" zoom-out memory-bridge bfs')"
386
+ expect_eq "path: graph config but no CLI -> no output" "" "$out"
387
+ clear_env
388
+ expect_ok "path: graph config but no CLI -> exit 0" \
389
+ bash -c 'source "'"$LIB"'"; PATH=/usr/bin:/bin memory_path "'"$GRAPH_ROOT"'" zoom-out memory-bridge bfs'
390
+
391
+ clear_env
392
+ out="$(PATH="$FAKE_BIN:$PATH" bash -c 'source "'"$LIB"'"; memory_path "'"$INIT_ROOT"'" zoom-out memory-bridge bfs')"
393
+ expect_eq "path: markdown-only config -> no output" "" "$out"
394
+
395
+ clear_env
396
+ out="$(PATH="$FAKE_BIN:$PATH" bash -c 'source "'"$LIB"'"; memory_path "'"$GRAPH_ROOT"'" zoom-out')"
397
+ expect_eq "path: missing destination label -> no output" "" "$out"
398
+
399
+ clear_env
400
+ NOPATHBIN="$TMP/nopathbin"
401
+ mkdir -p "$NOPATHBIN"
402
+ cat > "$NOPATHBIN/memory" <<'EOF'
403
+ #!/usr/bin/env bash
404
+ case "$1" in
405
+ stats)
406
+ echo "memory: 17 node(s), 3 edge(s)"
407
+ ;;
408
+ path)
409
+ echo "memory: no path from \"$2\" to \"$3\""
410
+ ;;
411
+ *)
412
+ exit 1
413
+ ;;
414
+ esac
415
+ EOF
416
+ chmod +x "$NOPATHBIN/memory"
417
+ out="$(PATH="$NOPATHBIN:/usr/bin:/bin" bash -c 'source "'"$LIB"'"; memory_path "'"$GRAPH_ROOT"'" zoom-out memory-bridge bfs')"
418
+ expect_eq "path: no path result -> no output" "" "$out"
419
+
420
+ clear_env
421
+ EMPTYPATHBIN="$TMP/emptypathbin"
422
+ mkdir -p "$EMPTYPATHBIN"
423
+ cat > "$EMPTYPATHBIN/memory" <<'EOF'
424
+ #!/usr/bin/env bash
425
+ case "$1" in
426
+ stats)
427
+ echo "memory: 17 node(s), 3 edge(s)"
428
+ ;;
429
+ path)
430
+ exit 0
431
+ ;;
432
+ *)
433
+ exit 1
434
+ ;;
435
+ esac
436
+ EOF
437
+ chmod +x "$EMPTYPATHBIN/memory"
438
+ out="$(PATH="$EMPTYPATHBIN:/usr/bin:/bin" bash -c 'source "'"$LIB"'"; memory_path "'"$GRAPH_ROOT"'" zoom-out memory-bridge bfs')"
439
+ expect_eq "path: empty command output -> no output" "" "$out"
440
+
441
+ clear_env
442
+ PATHERRBIN="$TMP/patherrbin"
443
+ mkdir -p "$PATHERRBIN"
444
+ cat > "$PATHERRBIN/memory" <<'EOF'
445
+ #!/usr/bin/env bash
446
+ case "$1" in
447
+ stats)
448
+ echo "memory: 17 node(s), 3 edge(s)"
449
+ ;;
450
+ path)
451
+ echo "path boom" >&2
452
+ exit 1
453
+ ;;
454
+ *)
455
+ exit 1
456
+ ;;
457
+ esac
458
+ EOF
459
+ chmod +x "$PATHERRBIN/memory"
460
+ out="$(PATH="$PATHERRBIN:/usr/bin:/bin" bash -c 'source "'"$LIB"'"; memory_path "'"$GRAPH_ROOT"'" zoom-out memory-bridge bfs' 2>/dev/null)"
461
+ expect_eq "path: failing command -> swallowed, no stdout" "" "$out"
462
+ clear_env
463
+ expect_ok "path: failing command -> still exit 0" \
464
+ bash -c 'source "'"$LIB"'"; PATH="'"$PATHERRBIN"':/usr/bin:/bin" memory_path "'"$GRAPH_ROOT"'" zoom-out memory-bridge bfs'
465
+
466
+ # ---------- memory_recall (graceful, query passthrough) ----------
467
+
468
+ # Absent memory: silent no-op, exit 0, no output.
469
+ clear_env
470
+ out="$(PATH=/usr/bin:/bin bash -c 'source "'"$LIB"'"; memory_recall "'"$BARE_ROOT"'" cache ttl')"
471
+ expect_eq "recall: absent memory -> no output" "" "$out"
472
+ clear_env
473
+ expect_ok "recall: absent memory -> exit 0" \
474
+ bash -c 'source "'"$LIB"'"; PATH=/usr/bin:/bin memory_recall "'"$BARE_ROOT"'" cache ttl'
475
+
476
+ # Config present but no CLI: still silent no-op, exit 0.
477
+ clear_env
478
+ out="$(PATH=/usr/bin:/bin bash -c 'source "'"$LIB"'"; memory_recall "'"$INIT_ROOT"'" cache ttl')"
479
+ expect_eq "recall: opted-in but no CLI -> no output" "" "$out"
480
+
481
+ # No query terms: no-op even when memory is fully available.
482
+ clear_env
483
+ out="$(PATH="$FAKE_BIN:$PATH" bash -c 'source "'"$LIB"'"; memory_recall "'"$INIT_ROOT"'"')"
484
+ expect_eq "recall: no query terms -> no output" "" "$out"
485
+
486
+ # Available memory: recall runs, output returned, query passed through verbatim.
487
+ clear_env
488
+ out="$(PATH="$FAKE_BIN:$PATH" bash -c 'source "'"$LIB"'"; memory_recall "'"$INIT_ROOT"'" cache ttl regression')"
489
+ expect_eq "recall: available -> returns CLI output" \
490
+ $'memory: 1 match for "cache ttl regression"\n [1.0] note-1' "$out"
491
+ clear_env
492
+ expect_ok "recall: available -> exit 0" \
493
+ bash -c 'source "'"$LIB"'"; PATH="'"$FAKE_BIN"':$PATH" memory_recall "'"$INIT_ROOT"'" cache ttl'
494
+
495
+ # A CLI that errors (exit 1) must not propagate failure to the dev process.
496
+ clear_env
497
+ ERRBIN="$TMP/errbin"
498
+ mkdir -p "$ERRBIN"
499
+ cat > "$ERRBIN/memory" <<'EOF'
500
+ #!/usr/bin/env bash
501
+ echo "boom" >&2
502
+ exit 1
503
+ EOF
504
+ chmod +x "$ERRBIN/memory"
505
+ out="$(PATH="$ERRBIN:/usr/bin:/bin" bash -c 'source "'"$LIB"'"; memory_recall "'"$INIT_ROOT"'" q' 2>/dev/null)"
506
+ expect_eq "recall: erroring CLI -> swallowed, no stdout" "" "$out"
507
+ clear_env
508
+ expect_ok "recall: erroring CLI -> still exit 0" \
509
+ bash -c 'source "'"$LIB"'"; PATH="'"$ERRBIN"':/usr/bin:/bin" memory_recall "'"$INIT_ROOT"'" q'
510
+
511
+ # ---------- memory_record_attempt (graph-only write, graceful) ----------
512
+
513
+ payload="$TMP/attempt-payload.json"
514
+ cat > "$payload" <<'EOF'
515
+ {"repository":"reddb-io/red-skills","issueNumber":99,"attemptNumber":1,"status":"done"}
516
+ EOF
517
+
518
+ clear_env
519
+ FAKE_ATTEMPT_OUT="$TMP/attempt-out.json"
520
+ out="$(FAKE_ATTEMPT_OUT="$FAKE_ATTEMPT_OUT" PATH="$FAKE_BIN:$PATH" bash -c 'source "'"$LIB"'"; memory_record_attempt "'"$GRAPH_ROOT"'" "'"$payload"'"')"
521
+ expect_eq "record-attempt: graph ready -> no stdout" "" "$out"
522
+ expect_eq "record-attempt: graph ready -> forwards JSON payload" \
523
+ "$(cat "$payload")" "$(cat "$FAKE_ATTEMPT_OUT")"
524
+ clear_env
525
+ expect_ok "record-attempt: graph ready -> exit 0" \
526
+ bash -c 'source "'"$LIB"'"; FAKE_ATTEMPT_OUT="'"$TMP/attempt-ok.json"'" PATH="'"$FAKE_BIN"':$PATH" memory_record_attempt "'"$GRAPH_ROOT"'" "'"$payload"'"'
527
+
528
+ clear_env
529
+ rm -f "$TMP/attempt-markdown.json"
530
+ out="$(FAKE_ATTEMPT_OUT="$TMP/attempt-markdown.json" PATH="$FAKE_BIN:$PATH" bash -c 'source "'"$LIB"'"; memory_record_attempt "'"$INIT_ROOT"'" "'"$payload"'"')"
531
+ expect_eq "record-attempt: markdown-only -> no output" "" "$out"
532
+ if [[ ! -e "$TMP/attempt-markdown.json" ]]; then
533
+ echo "PASS record-attempt: markdown-only -> does not call CLI"; pass=$((pass + 1))
534
+ else
535
+ echo "FAIL record-attempt: markdown-only -> unexpectedly called CLI"; fail=$((fail + 1))
536
+ fi
537
+
538
+ clear_env
539
+ out="$(PATH=/usr/bin:/bin bash -c 'source "'"$LIB"'"; memory_record_attempt "'"$GRAPH_ROOT"'" "'"$payload"'"')"
540
+ expect_eq "record-attempt: graph config but no CLI -> no output" "" "$out"
541
+ clear_env
542
+ expect_ok "record-attempt: graph config but no CLI -> exit 0" \
543
+ bash -c 'source "'"$LIB"'"; PATH=/usr/bin:/bin memory_record_attempt "'"$GRAPH_ROOT"'" "'"$payload"'"'
544
+
545
+ clear_env
546
+ out="$(PATH="$ERRBIN:/usr/bin:/bin" bash -c 'source "'"$LIB"'"; memory_record_attempt "'"$GRAPH_ROOT"'" "'"$payload"'"' 2>/dev/null)"
547
+ expect_eq "record-attempt: erroring CLI -> swallowed, no stdout" "" "$out"
548
+ clear_env
549
+ expect_ok "record-attempt: erroring CLI -> still exit 0" \
550
+ bash -c 'source "'"$LIB"'"; PATH="'"$ERRBIN"':/usr/bin:/bin" memory_record_attempt "'"$GRAPH_ROOT"'" "'"$payload"'"'
551
+
552
+ # ---------- summary ----------
553
+ echo
554
+ echo "memory-bridge: $pass passed, $fail failed"
555
+ [[ "$fail" -eq 0 ]]
@@ -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})}'
@@ -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