@lifeaitools/rdc-skills 0.35.23 → 0.35.25

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 (39) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.github/workflows/self-test.yml +34 -34
  3. package/MANIFEST.md +224 -224
  4. package/RELEASE.md +53 -11
  5. package/deploy/install-systemd.sh +29 -6
  6. package/deploy/systemd/rdc-skills-mcp-update.service +15 -0
  7. package/deploy/systemd/rdc-skills-mcp-update.timer +9 -0
  8. package/deploy/update-from-tag.sh +382 -0
  9. package/guides/agents/backend.md +102 -102
  10. package/guides/agents/content.md +94 -94
  11. package/guides/agents/cs2.md +56 -56
  12. package/guides/agents/data.md +86 -86
  13. package/guides/agents/design.md +77 -77
  14. package/guides/agents/frontend.md +91 -91
  15. package/guides/agents/infrastructure.md +81 -81
  16. package/guides/agents/setup.md +272 -272
  17. package/guides/agents/verify.md +119 -119
  18. package/guides/agents/viz.md +106 -106
  19. package/guides/orchestration-epic.md +17 -17
  20. package/guides/output-contract.md +8 -0
  21. package/guides/work-contract.md +194 -0
  22. package/package.json +3 -2
  23. package/scripts/lib/guide-content-rules.mjs +49 -0
  24. package/scripts/self-test.mjs +1439 -1459
  25. package/scripts/test-guide-validator.mjs +25 -24
  26. package/skills/behavior-audit/agents/openai.yaml +4 -4
  27. package/skills/build/SKILL.md +81 -29
  28. package/skills/fixit/SKILL.md +16 -0
  29. package/skills/open/SKILL.md +35 -5
  30. package/skills/overnight/SKILL.md +3 -1
  31. package/skills/plan/SKILL.md +21 -23
  32. package/skills/tests/onramp.test.json +101 -101
  33. package/skills/tests/rdc-env.test.json +12 -12
  34. package/skills/tests/rdc-new-model.test.json +12 -12
  35. package/skills/tests/rdc-refactor.test.json +29 -29
  36. package/skills/tests/rdc-regen-media.test.json +29 -29
  37. package/tests/deploy-updater.test.mjs +608 -0
  38. package/.rdc/evidence/orchestrator-rework-strikes/a9fad8b4716ee35e5f5d0047.json +0 -1
  39. package/.rdc/last_seen.json +0 -8
@@ -0,0 +1,15 @@
1
+ [Unit]
2
+ Description=Keep the public RDC Skills MCP on the newest release tag
3
+ After=network-online.target
4
+ Wants=network-online.target
5
+
6
+ [Service]
7
+ Type=oneshot
8
+ User=root
9
+ WorkingDirectory=/srv/regen/rdc-skills
10
+ # A copy outside the checkout: a rollback to a release that predates the
11
+ # updater must not be able to delete the program doing the rollback.
12
+ ExecStart=/usr/local/libexec/rdc-skills/update-from-tag.sh
13
+ # Proved and failed commits, and install hashes — outside the checkout it judges.
14
+ StateDirectory=rdc-skills-update
15
+ TimeoutStartSec=20min
@@ -0,0 +1,9 @@
1
+ [Unit]
2
+ Description=Check for a new RDC Skills release every five minutes
3
+
4
+ [Timer]
5
+ OnBootSec=2min
6
+ OnUnitActiveSec=5min
7
+
8
+ [Install]
9
+ WantedBy=timers.target
@@ -0,0 +1,382 @@
1
+ #!/usr/bin/env bash
2
+ # Keep the public MCP on the newest release tag, and prove it is serving it.
3
+ #
4
+ # The tag is the promotion for rdc-skills (RELEASE.md). npm consumers get a tag
5
+ # when the registry does; this is the same act for the one consumer that runs
6
+ # from a git checkout — the public MCP at https://rdc-skills.regendevcorp.com.
7
+ # Before this existed the checkout moved only when someone logged in and pulled,
8
+ # which the fleet's hand-rolled-deploy guard refuses, so the public catalog sat
9
+ # on 0.35.18 through six releases while npm moved on.
10
+ #
11
+ # THE MODEL (classified per .claude/rules/stateful-inspection.md)
12
+ # identity a RELEASE: a plain vX.Y.Z tag merged into origin/master, or the
13
+ # PROVED commit — the last one this updater restarted onto and saw
14
+ # /health report. On a host with no history, the commit the running
15
+ # process reports is adopted as proved: it is demonstrably serving.
16
+ # state HEAD; the recorded dependency and unit installs; the FAILED set;
17
+ # the install-error ATTEMPTS against one release.
18
+ # liveness /health's git_sha (fixed at process start) and version (re-read
19
+ # from package.json on every request — so only git_sha proves a
20
+ # restart took).
21
+ #
22
+ # THE INVARIANT: the service is restarted only onto a release. HEAD is state; it
23
+ # is moved to a release, never served for being checked out. A HEAD that is not
24
+ # a release and not on origin/master (a commit made on the host) is refused and
25
+ # the service is left alone.
26
+ #
27
+ # EACH RUN: desired = the newest tag unless it has FAILED or is not ahead of the
28
+ # proved release, else the proved release. Move HEAD there if needed; if /health
29
+ # already reports it and the installs match, done. Otherwise converge (install
30
+ # what changed, restart, prove):
31
+ # proved record it, refresh the updater copy
32
+ # never answers the release is bad: record FAILED (never the proved one),
33
+ # return to the proved release
34
+ # install/restart says nothing about the release, the first two times: return
35
+ # error to the proved release and retry next run. The third time
36
+ # against the same release it is treated as bad. A registry
37
+ # that cannot be reached is not counted — unless it has been
38
+ # unreachable for that release for a whole day.
39
+ #
40
+ # Run by rdc-skills-mcp-update.timer from a copy OUTSIDE the checkout
41
+ # (/usr/local/libexec/rdc-skills), refreshed only after a proved move — a
42
+ # rollback to a release that predates this file can never delete the updater.
43
+ # State lives in /var/lib/rdc-skills-update, not in the checkout it judges.
44
+ #
45
+ # Exit codes: 0 serving the newest release · 2 wrong checkout · 3 tracked changes
46
+ # (preserved, never touched) · 4 HEAD is a commit made on the host (refused, service
47
+ # untouched) · 5 the newest release failed and the proved one is serving again ·
48
+ # 6 nothing proved serving this run (retried next run) · 7 the newest release
49
+ # failed before; the proved one is held · 8 serving and proved, but recording it
50
+ # or refreshing the updater copy failed (retried next run) · 9 the newest tag is
51
+ # not ahead of the proved release (a downgrade); the proved one is held.
52
+ #
53
+ # Everything runs from functions called on the last line. Bash reads a script as
54
+ # it executes; by the time main runs, every line it will execute has been read.
55
+ set -euo pipefail
56
+
57
+ ROOT="${RDC_SKILLS_DEPLOY_ROOT:-/srv/regen/rdc-skills}"
58
+ EXPECTED_ROOT="${RDC_SKILLS_EXPECTED_ROOT:-/srv/regen/rdc-skills}"
59
+ UNIT="rdc-skills-mcp.service"
60
+ UNIT_DIR="${RDC_SKILLS_UNIT_DIR:-/etc/systemd/system}"
61
+ LIBEXEC_DIR="${RDC_SKILLS_LIBEXEC_DIR:-/usr/local/libexec/rdc-skills}"
62
+ STATE_DIR="${RDC_SKILLS_STATE_DIR:-/var/lib/rdc-skills-update}"
63
+ LOCK_FILE="${RDC_SKILLS_LOCK_FILE:-/run/lock/rdc-skills-update.lock}"
64
+ HEALTH_URL="${RDC_SKILLS_HEALTH_URL:-http://127.0.0.1:3110/health}"
65
+ HEALTH_DEADLINE="${RDC_SKILLS_HEALTH_DEADLINE:-120}"
66
+ HEALTH_INTERVAL="${RDC_SKILLS_HEALTH_INTERVAL:-2}"
67
+ HEALTH_SETTLE_ATTEMPTS="${RDC_SKILLS_HEALTH_SETTLE_ATTEMPTS:-3}"
68
+ INSTALL_ERROR_LIMIT="${RDC_SKILLS_INSTALL_ERROR_LIMIT:-3}"
69
+ # How long one release may keep failing to reach the registry before it is judged
70
+ # bad after all (a lockfile `resolved` URL on a host that no longer exists).
71
+ NETWORK_ERROR_WINDOW="${RDC_SKILLS_NETWORK_ERROR_WINDOW:-86400}"
72
+ SYSTEMCTL="${SYSTEMCTL:-systemctl}"
73
+ # Release tags are fetched with force into a namespace this updater owns, so a
74
+ # tag moved or re-created on origin updates here instead of failing the fetch.
75
+ RELEASE_REFS="refs/rdc-release-tags"
76
+
77
+ log() { printf 'rdc-skills-update: %s\n' "$*"; }
78
+
79
+ state_get() { cat "$STATE_DIR/$1" 2>/dev/null || true; }
80
+ state_put() { printf '%s\n' "$2" > "$STATE_DIR/$1.tmp" && mv -f "$STATE_DIR/$1.tmp" "$STATE_DIR/$1"; }
81
+
82
+ # Never fails, and never reads stdin: no arguments hash as empty input.
83
+ hash_files() { { if (($#)); then cat -- "$@" 2>/dev/null || true; fi; } | sha256sum | cut -d' ' -f1; }
84
+ unit_files() { (shopt -s nullglob; printf '%s\n' deploy/systemd/*.service deploy/systemd/*.timer); }
85
+
86
+ version_at() {
87
+ git show "$1:package.json" | node -e 'process.stdout.write(JSON.parse(require("fs").readFileSync(0, "utf8")).version)'
88
+ }
89
+
90
+ health_field() {
91
+ HEALTH="$1" FIELD="$2" node -e '
92
+ try { process.stdout.write(String(JSON.parse(process.env.HEALTH)[process.env.FIELD] ?? "")); } catch {}
93
+ ' 2>/dev/null || true
94
+ }
95
+ health_body() { curl -fsS -m 5 "$HEALTH_URL" 2>/dev/null || true; }
96
+ answers_as() { # <body> <sha> <version>
97
+ [[ -n "$1" && "$(health_field "$1" git_sha)" == "$2" && "$(health_field "$1" version)" == "$3" ]]
98
+ }
99
+
100
+ # The first ANSWER decides; only silence is retried — one slow response never
101
+ # restarts a healthy service. Prints the answer (empty when there was none).
102
+ settled_body() {
103
+ local i body=""
104
+ for ((i = 1; i <= HEALTH_SETTLE_ATTEMPTS; i++)); do
105
+ body="$(health_body)"
106
+ [[ -n "$body" ]] && break
107
+ ((i < HEALTH_SETTLE_ATTEMPTS)) && sleep "$HEALTH_INTERVAL"
108
+ done
109
+ printf '%s' "$body"
110
+ }
111
+
112
+ wait_serving() { # <sha> <version>: after a restart, until the deadline
113
+ local end=$((SECONDS + HEALTH_DEADLINE)) body=""
114
+ while :; do
115
+ body="$(health_body)"
116
+ answers_as "$body" "$1" "$2" && return 0
117
+ ((SECONDS >= end)) && break
118
+ sleep "$HEALTH_INTERVAL"
119
+ done
120
+ log "health at $HEALTH_URL did not report $2 @ ${1:0:7} within ${HEALTH_DEADLINE}s; last: ${body:-<no answer>}"
121
+ return 1
122
+ }
123
+
124
+ installs_match() {
125
+ [[ -d node_modules ]] \
126
+ && [[ "$(hash_files package-lock.json)" == "$(state_get lock-sha256)" ]] \
127
+ && [[ "$(hash_files $(unit_files))" == "$(state_get units-sha256)" ]]
128
+ }
129
+
130
+ held() { # <sha> <version>: /health reports the release and the installs match (serve moved HEAD to it)
131
+ answers_as "$(settled_body)" "$1" "$2" && installs_match
132
+ }
133
+
134
+ # converge <sha> <version>: with HEAD at <sha>, install what changed since the
135
+ # last install, restart, and prove it.
136
+ # 0 proved · 1 install or restart error · 2 restarted, but /health never reported it
137
+ # 3 the dependency install could not reach the registry (never counted against a release)
138
+ # Each recorded hash is cleared BEFORE its install and written after, so a run
139
+ # killed halfway is redone rather than trusted. errexit is suspended inside a
140
+ # function called from a condition, so every step checks its own status.
141
+ converge() {
142
+ local sha="$1" version="$2" lock_hash units_hash units npm_out npm_rc
143
+ if [[ -e git-sha.json ]]; then
144
+ # A pack-time stamp outranks `git rev-parse` in the MCP and would pin
145
+ # /health to whatever commit was packed here, failing every proof.
146
+ log "removing a stale git-sha.json stamp from the checkout"
147
+ rm -f git-sha.json || return 1
148
+ fi
149
+ lock_hash="$(hash_files package-lock.json)"
150
+ if [[ ! -d node_modules || "$lock_hash" != "$(state_get lock-sha256)" ]]; then
151
+ log "installing the locked production dependency graph"
152
+ state_put lock-sha256 "" || return 1
153
+ # --ignore-scripts: nothing the server imports needs an install script, and
154
+ # this runs as root, unattended, within minutes of a tag.
155
+ npm_rc=0
156
+ npm_out="$(npm ci --omit=dev --no-audit --no-fund --ignore-scripts 2>&1)" || npm_rc=$?
157
+ if ((npm_rc != 0)); then
158
+ printf '%s\n' "$npm_out" | tail -n 5 | sed 's/^/rdc-skills-update: /'
159
+ # A registry that cannot be reached says nothing about the release; a
160
+ # lockfile that cannot install does.
161
+ if grep -qE '^npm (ERR!|error) (code (E5[0-9]{2}|ETIMEDOUT|ECONNRESET|ECONNREFUSED|EAI_AGAIN|ENOTFOUND|ENETUNREACH|EHOSTUNREACH)|network )' <<<"$npm_out"; then
162
+ log "npm ci could not reach the registry"
163
+ return 3
164
+ fi
165
+ log "npm ci failed"
166
+ return 1
167
+ fi
168
+ state_put lock-sha256 "$lock_hash" || return 1
169
+ fi
170
+ units="$(unit_files)"
171
+ # shellcheck disable=SC2086 # unit file names carry no spaces
172
+ units_hash="$(hash_files $units)"
173
+ if [[ "$units_hash" != "$(state_get units-sha256)" ]]; then
174
+ log "installing unit files"
175
+ state_put units-sha256 "" || return 1
176
+ if [[ -n "$units" ]]; then
177
+ # shellcheck disable=SC2086
178
+ install -m 0644 $units "$UNIT_DIR/" || return 1
179
+ fi
180
+ "$SYSTEMCTL" daemon-reload || return 1
181
+ state_put units-sha256 "$units_hash" || return 1
182
+ fi
183
+ "$SYSTEMCTL" restart "$UNIT" || { log "restart failed"; return 1; }
184
+ wait_serving "$sha" "$version" || return 2
185
+ }
186
+
187
+ is_failed() { [[ -f "$STATE_DIR/failed" ]] && grep -qx "$1" "$STATE_DIR/failed"; }
188
+ mark_failed() { is_failed "$1" || printf '%s\n' "$1" >> "$STATE_DIR/failed"; }
189
+
190
+ # Install/restart errors counted against one release; any other release resets it.
191
+ bump_attempts() {
192
+ local sha="$1" recorded count=0
193
+ recorded="$(state_get attempts)"
194
+ [[ "${recorded%% *}" == "$sha" ]] && count="${recorded##* }"
195
+ count=$((count + 1))
196
+ state_put attempts "$sha $count"
197
+ printf '%s' "$count"
198
+ }
199
+
200
+ # Seconds this release has been failing to reach the registry (0 on the first failure).
201
+ network_failing_for() {
202
+ local sha="$1" recorded now since
203
+ now="$(date +%s)"
204
+ recorded="$(state_get network-since)"
205
+ if [[ "${recorded%% *}" == "$sha" ]]; then
206
+ since="${recorded##* }"
207
+ else
208
+ since="$now"
209
+ state_put network-since "$sha $now"
210
+ fi
211
+ printf '%s' "$((now - since))"
212
+ }
213
+
214
+ # serve <sha> <version>: move HEAD to a release and make /health prove it.
215
+ # Returns converge's code (0 also when it was already held).
216
+ serve() {
217
+ local sha="$1" version="$2" rc=0
218
+ if [[ "$(git rev-parse HEAD)" != "$sha" ]]; then
219
+ git reset --quiet --hard "$sha" || return 1
220
+ fi
221
+ held "$sha" "$version" && return 0
222
+ log "converging ${sha:0:7} ($version)"
223
+ converge "$sha" "$version" || rc=$?
224
+ return "$rc"
225
+ }
226
+
227
+ main() {
228
+ if [[ "$ROOT" != "$EXPECTED_ROOT" ]]; then
229
+ log "refused: checkout is $ROOT, expected $EXPECTED_ROOT"
230
+ return 2
231
+ fi
232
+ mkdir -p "$STATE_DIR"
233
+ # systemd never overlaps two timer runs; this also covers a run started by hand.
234
+ if command -v flock >/dev/null 2>&1; then
235
+ exec 9>"$LOCK_FILE"
236
+ if ! flock -n 9; then
237
+ log "another update run holds $LOCK_FILE"
238
+ return 0
239
+ fi
240
+ fi
241
+ cd "$ROOT"
242
+
243
+ local tracked
244
+ tracked="$(git status --porcelain --untracked-files=no)"
245
+ if [[ -n "$tracked" ]]; then
246
+ log "refused: tracked changes in $ROOT — preserved, not updated"
247
+ printf '%s\n' "$tracked" | sed 's/^/rdc-skills-update: /'
248
+ return 3
249
+ fi
250
+
251
+ # Releases are read only from the forced namespace below, so a tag moved on
252
+ # origin updates there instead of being refused as a clobber (which, with
253
+ # `--tags` into refs/tags, fails the whole fetch every run). --no-tags keeps
254
+ # refs/tags out of it entirely.
255
+ git fetch --quiet --no-tags --prune origin \
256
+ '+refs/heads/master:refs/remotes/origin/master' \
257
+ "+refs/tags/v*:$RELEASE_REFS/v*"
258
+ # Plain vX.Y.Z only: a pre-release or a stray `v*` name is never a release.
259
+ local tag target=""
260
+ tag="$(git for-each-ref --merged refs/remotes/origin/master --sort=-v:refname \
261
+ --format='%(refname:lstrip=2)' "$RELEASE_REFS" \
262
+ | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -n 1 || true)"
263
+ [[ -n "$tag" ]] && target="$(git rev-parse "$RELEASE_REFS/$tag^{commit}")"
264
+
265
+ local proved
266
+ proved="$(state_get proved)"
267
+ if [[ -z "$proved" ]]; then
268
+ # No history: adopt what is demonstrably serving, so a bad first release has
269
+ # somewhere to go back to — recorded BEFORE anything is restarted.
270
+ local running
271
+ running="$(health_field "$(settled_body)" git_sha)"
272
+ # Only a commit on origin/master: adopting a host-made commit would let the
273
+ # move below reset it off every branch.
274
+ if [[ -n "$running" ]] && git cat-file -e "$running^{commit}" 2>/dev/null \
275
+ && git merge-base --is-ancestor "$running" refs/remotes/origin/master; then
276
+ state_put proved "$running"
277
+ proved="$running"
278
+ log "adopted the serving commit ${running:0:7} as the proved release"
279
+ fi
280
+ fi
281
+
282
+ # A release only moves forward: a tag that is not the proved commit or a
283
+ # descendant of it (a higher number put on an older commit) would downgrade the
284
+ # public MCP. Rolling back is a new, higher tag — the tag is the promotion.
285
+ local desired="" holding=""
286
+ if [[ -n "$target" && -n "$proved" && "$target" != "$proved" ]] \
287
+ && ! git merge-base --is-ancestor "$proved" "$target"; then
288
+ desired="$proved"
289
+ holding="behind"
290
+ elif [[ -n "$target" ]] && ! is_failed "$target"; then
291
+ desired="$target"
292
+ elif [[ -n "$proved" ]]; then
293
+ desired="$proved"
294
+ [[ -n "$target" ]] && holding="failed"
295
+ fi
296
+ if [[ -z "$desired" ]]; then
297
+ if [[ -n "$target" ]]; then
298
+ log "$tag failed its health proof and there is no proved release to serve"
299
+ else
300
+ log "no release tag reachable from origin/master, and no proved release"
301
+ fi
302
+ return 6
303
+ fi
304
+
305
+ # HEAD is state, but a commit made ON the host is work that exists nowhere else:
306
+ # never discard it, and never serve it.
307
+ local head
308
+ head="$(git rev-parse HEAD)"
309
+ if [[ "$head" != "$desired" && "$head" != "$proved" && "$head" != "$target" ]] \
310
+ && ! git merge-base --is-ancestor "$head" refs/remotes/origin/master; then
311
+ log "refused: HEAD ${head:0:7} is a commit made on this host (not on origin/master) — preserved; the service is left as it is"
312
+ return 4
313
+ fi
314
+
315
+ local version rc=0
316
+ version="$(version_at "$desired")"
317
+ serve "$desired" "$version" || rc=$?
318
+ local name="${tag:-release}"
319
+ [[ "$desired" != "$target" ]] && name="${desired:0:7}"
320
+
321
+ if ((rc == 0)); then
322
+ state_put attempts "" || true
323
+ state_put network-since "" || true
324
+ if [[ "$(state_get proved)" != "$desired" ]] && ! state_put proved "$desired"; then
325
+ log "serving $name (${desired:0:7}), but recording it as proved failed"
326
+ return 8
327
+ fi
328
+ # The updater copy follows the newest release only — a held older release may
329
+ # predate this file. Compared by content, so a failed refresh is retried.
330
+ if [[ "$desired" == "$target" ]] && ! cmp -s deploy/update-from-tag.sh "$LIBEXEC_DIR/update-from-tag.sh"; then
331
+ if ! install -D -m 0755 deploy/update-from-tag.sh "$LIBEXEC_DIR/update-from-tag.sh"; then
332
+ log "serving $name (${desired:0:7}), but refreshing the updater copy in $LIBEXEC_DIR failed"
333
+ return 8
334
+ fi
335
+ fi
336
+ if [[ "$holding" == failed ]]; then
337
+ log "holding ${desired:0:7}: $tag (${target:0:7}) failed its health proof before; waiting for a newer tag"
338
+ return 7
339
+ fi
340
+ if [[ "$holding" == behind ]]; then
341
+ log "holding ${desired:0:7}: the newest tag $tag (${target:0:7}) is not ahead of the proved release; waiting for a newer tag"
342
+ return 9
343
+ fi
344
+ log "current at $name (${desired:0:7})"
345
+ return 0
346
+ fi
347
+
348
+ # Not proved. The proved release itself is never marked failed: it served.
349
+ local bad=0
350
+ if [[ "$desired" != "$proved" ]]; then
351
+ if ((rc == 2)); then
352
+ bad=1
353
+ elif ((rc == 3)); then
354
+ if (( $(network_failing_for "$desired") >= NETWORK_ERROR_WINDOW )); then
355
+ log "installing $name has failed to reach the registry for over ${NETWORK_ERROR_WINDOW}s; treating it as a bad release"
356
+ bad=1
357
+ else
358
+ log "the registry could not be reached installing $name; retrying next run (not counted)"
359
+ fi
360
+ elif (( $(bump_attempts "$desired") >= INSTALL_ERROR_LIMIT )); then
361
+ log "install or restart error on $name for the ${INSTALL_ERROR_LIMIT}th run; treating it as a bad release"
362
+ bad=1
363
+ else
364
+ log "install or restart error on $name; retrying next run"
365
+ fi
366
+ ((bad)) && mark_failed "$desired"
367
+ fi
368
+
369
+ if [[ -n "$proved" && "$desired" != "$proved" ]]; then
370
+ log "returning to the proved release ${proved:0:7}"
371
+ local back_rc=0
372
+ serve "$proved" "$(version_at "$proved")" || back_rc=$?
373
+ if ((back_rc == 0)); then
374
+ ((bad)) && { log "$name failed; the proved release is serving again"; return 5; }
375
+ return 6
376
+ fi
377
+ log "the proved release ${proved:0:7} could not be proved serving either"
378
+ fi
379
+ return 6
380
+ }
381
+
382
+ main "$@"; exit $?
@@ -1,104 +1,104 @@
1
- > **⚠️ OUTPUT CONTRACT (READ FIRST):** `guides/output-contract.md`
2
- > Checklist-only output. No tool-call narration. No raw MCP/JSON/log dumps.
3
- > One checklist upfront, updated in place, shown again at end with a 1-line verdict.
4
-
5
- > If dispatching subagents or running as a subagent: read `{PROJECT_ROOT}/.rdc/guides/agent-bootstrap.md` first (fallback: `{PROJECT_ROOT}/.rdc/guides/agent-bootstrap.md`).
6
-
7
- > **Sandbox contract:** This guide honors `RDC_TEST=1` per `guides/agent-bootstrap.md` § RDC_TEST Sandbox Contract. Destructive external calls short-circuit under the flag.
8
-
9
-
10
- # rdc:backend — Backend Agent
11
-
12
- ## Mandatory First Step
13
-
14
- Read the guide before ANY code:
15
- ```
16
- {PROJECT_ROOT}/.rdc/guides/backend.md
17
- (fallback: {PROJECT_ROOT}/.rdc/guides/backend.md)
18
- ```
19
-
20
- ## Database Client — One Pattern Per Context
21
-
22
- ```ts
23
- // Server component / API route
24
- import { createServerClient } from "@regen/supabase";
25
- const supabase = await createServerClient();
26
-
27
- // Client component
28
- import { createBrowserClient } from "@regen/supabase";
29
- const supabase = createBrowserClient();
30
- ```
31
-
32
- Non-public schemas:
33
- ```ts
34
- const { data } = await supabase.schema("custom").from("table_name").select("*");
35
- ```
36
-
37
- ## Credentials — Daemon First
38
-
39
- ```bash
40
- curl -s http://127.0.0.1:52437/get/<service>
41
- ```
42
- - Never hardcode credentials
43
- - Never print keys to stdout
44
- - If daemon is down: report BLOCKED — do not work around it
45
-
46
- ## Work Items — RPC Only
47
-
48
- ```sql
49
- -- Read epics
50
- SELECT get_open_epics();
51
-
52
- -- Create
53
- SELECT insert_work_item(
54
- p_title := 'Task title',
55
- p_priority := 'high',
56
- p_parent_id := '<epic-uuid>'::uuid,
57
- p_source := 'agent'
58
- );
59
-
1
+ > **⚠️ OUTPUT CONTRACT (READ FIRST):** `guides/output-contract.md`
2
+ > Checklist-only output. No tool-call narration. No raw MCP/JSON/log dumps.
3
+ > One checklist upfront, updated in place, shown again at end with a 1-line verdict.
4
+
5
+ > If dispatching subagents or running as a subagent: read `{PROJECT_ROOT}/.rdc/guides/agent-bootstrap.md` first (fallback: `{PROJECT_ROOT}/.rdc/guides/agent-bootstrap.md`).
6
+
7
+ > **Sandbox contract:** This guide honors `RDC_TEST=1` per `guides/agent-bootstrap.md` § RDC_TEST Sandbox Contract. Destructive external calls short-circuit under the flag.
8
+
9
+
10
+ # rdc:backend — Backend Agent
11
+
12
+ ## Mandatory First Step
13
+
14
+ Read the guide before ANY code:
15
+ ```
16
+ {PROJECT_ROOT}/.rdc/guides/backend.md
17
+ (fallback: {PROJECT_ROOT}/.rdc/guides/backend.md)
18
+ ```
19
+
20
+ ## Database Client — One Pattern Per Context
21
+
22
+ ```ts
23
+ // Server component / API route
24
+ import { createServerClient } from "@regen/supabase";
25
+ const supabase = await createServerClient();
26
+
27
+ // Client component
28
+ import { createBrowserClient } from "@regen/supabase";
29
+ const supabase = createBrowserClient();
30
+ ```
31
+
32
+ Non-public schemas:
33
+ ```ts
34
+ const { data } = await supabase.schema("custom").from("table_name").select("*");
35
+ ```
36
+
37
+ ## Credentials — Daemon First
38
+
39
+ ```bash
40
+ curl -s http://127.0.0.1:52437/get/<service>
41
+ ```
42
+ - Never hardcode credentials
43
+ - Never print keys to stdout
44
+ - If daemon is down: report BLOCKED — do not work around it
45
+
46
+ ## Work Items — RPC Only
47
+
48
+ ```sql
49
+ -- Read epics
50
+ SELECT get_open_epics();
51
+
52
+ -- Create
53
+ SELECT insert_work_item(
54
+ p_title := 'Task title',
55
+ p_priority := 'high',
56
+ p_parent_id := '<epic-uuid>'::uuid,
57
+ p_source := 'agent'
58
+ );
59
+
60
60
  -- Implementation agents submit report + CodeFlow post, then move to review
61
61
  SELECT update_work_item_status('<uuid>'::uuid, 'review', '["Implementation complete; ready for validator"]'::jsonb, '<agent-session-id>', 'agent');
62
- ```
63
-
64
- **NEVER write raw INSERT/UPDATE against work items.**
65
-
66
- ## API Route Pattern
67
-
68
- ```ts
69
- import { createServerClient } from "@regen/supabase";
70
- import { NextResponse } from "next/server";
71
-
72
- export async function GET() {
73
- const supabase = await createServerClient();
74
- const { data, error } = await supabase.from("table").select("*");
75
- if (error) return NextResponse.json({ error: error.message }, { status: 500 });
76
- return NextResponse.json(data);
77
- }
78
-
79
- export async function POST(request: Request) {
80
- const body = await request.json();
81
- const supabase = await createServerClient();
82
- const { data, error } = await supabase.from("table").insert(body).select().single();
83
- if (error) return NextResponse.json({ error: error.message }, { status: 400 });
84
- return NextResponse.json(data, { status: 201 });
85
- }
86
- ```
87
-
88
- ## Auth
89
-
90
- Use the auth helpers from your project's auth package for protected apps.
91
-
92
- ## Schema-Driven Forms
93
-
94
- When working with schema-driven forms (common in dynamic CRUD), never hardcode columns.
95
- Use the schema table to drive form rendering instead.
96
-
97
- ## Safety Rules
98
-
99
- - Branch: development branch — auto-commit after logical blocks
100
- - NEVER run `pnpm build`
101
- - NEVER overlap with other agents on the same files
102
- - Update work items in real time — not batch at end
103
- - Push after each logical block *(skip if `$RDC_TEST=1` — echo `[RDC_TEST] skipping git push` instead)*
104
- - Write tests FIRST — red → implement → green
62
+ ```
63
+
64
+ **NEVER write raw INSERT/UPDATE against work items.**
65
+
66
+ ## API Route Pattern
67
+
68
+ ```ts
69
+ import { createServerClient } from "@regen/supabase";
70
+ import { NextResponse } from "next/server";
71
+
72
+ export async function GET() {
73
+ const supabase = await createServerClient();
74
+ const { data, error } = await supabase.from("table").select("*");
75
+ if (error) return NextResponse.json({ error: error.message }, { status: 500 });
76
+ return NextResponse.json(data);
77
+ }
78
+
79
+ export async function POST(request: Request) {
80
+ const body = await request.json();
81
+ const supabase = await createServerClient();
82
+ const { data, error } = await supabase.from("table").insert(body).select().single();
83
+ if (error) return NextResponse.json({ error: error.message }, { status: 400 });
84
+ return NextResponse.json(data, { status: 201 });
85
+ }
86
+ ```
87
+
88
+ ## Auth
89
+
90
+ Use the auth helpers from your project's auth package for protected apps.
91
+
92
+ ## Schema-Driven Forms
93
+
94
+ When working with schema-driven forms (common in dynamic CRUD), never hardcode columns.
95
+ Use the schema table to drive form rendering instead.
96
+
97
+ ## Safety Rules
98
+
99
+ - Branch: development branch — auto-commit after logical blocks
100
+ - NEVER run `pnpm build`
101
+ - NEVER overlap with other agents on the same files
102
+ - Update work items in real time — not batch at end
103
+ - Push after each logical block *(skip if `$RDC_TEST=1` — echo `[RDC_TEST] skipping git push` instead)*
104
+ - Write tests FIRST — red → implement → green