@lifeaitools/rdc-skills 0.9.36 → 0.9.38
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/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +27 -0
- package/commands/deploy.md +4 -4
- package/commands/design.md +1 -1
- package/commands/overnight.md +2 -2
- package/commands/release.md +29 -139
- package/commands/self-test.md +1 -1
- package/commands/watch.md +1 -1
- package/guides/agent-bootstrap.md +2 -2
- package/hooks/check-cwd.js +3 -3
- package/hooks/no-stop-open-epics.js +9 -7
- package/hooks/rdc-invocation-marker.js +17 -3
- package/hooks/rdc-output-contract-gate.js +9 -0
- package/hooks/require-work-item-on-commit.js +2 -1
- package/package.json +8 -3
- package/scripts/install-rdc-skills.js +6 -2
- package/scripts/self-test.mjs +36 -0
- package/scripts/test-rdc-hooks.mjs +145 -0
- package/skills/build/SKILL.md +2 -2
- package/skills/co-develop/SKILL.md +1 -1
- package/skills/deploy/SKILL.md +12 -12
- package/skills/design/SKILL.md +6 -6
- package/skills/fs-mcp/SKILL.md +2 -2
- package/skills/plan/SKILL.md +2 -2
- package/skills/release/SKILL.md +48 -302
- package/skills/self-test/SKILL.md +3 -3
- package/skills/terminal-config/SKILL.md +34 -189
- package/skills/watch/SKILL.md +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## v0.9.38 — Clean-box npm install hardening
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **Fresh Claude home install.** The installer now creates `CLAUDE_HOME` and `settings.json` when they do not exist, so a first-time machine is not blocked before Claude has written config files.
|
|
15
|
+
- **Public package portability.** Removed the most brittle user-machine paths from public skill docs and hooks, replacing them with `{PROJECT_ROOT}`, `{RDC_SKILLS_ROOT}`, or environment-derived paths.
|
|
16
|
+
- **Hook path fallback.** Commit hygiene hooks now use the current OS home directory instead of a user-specific fallback path.
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- **npm binaries.** `rdc-skills-install` and `rdc-skills-self-test` are exposed from the npm package for simpler clean-box installs.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## v0.9.37 — RDC hook loop guard and behavior tests
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
|
|
28
|
+
- **Stop hook reentry.** `rdc-output-contract-gate.js` now exits cleanly when Claude is already inside an active Stop-hook retry, preventing noncompliant-but-legitimate turns from hard-looping the session.
|
|
29
|
+
- **Marker deduplication.** `rdc-invocation-marker.js` now preserves the original marker when `UserPromptExpansion` and `UserPromptSubmit` both fire for the same RDC command within the same second.
|
|
30
|
+
|
|
31
|
+
### Added
|
|
32
|
+
|
|
33
|
+
- **Hook behavioral tests.** New `scripts/test-rdc-hooks.mjs` executes the marker and Stop hooks against real JSON payloads, and `self-test --strict` now fails if those behavioral checks fail.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
10
37
|
## v0.9.36 — Portable installer profiles and startup onboarding
|
|
11
38
|
|
|
12
39
|
### Added
|
package/commands/deploy.md
CHANGED
|
@@ -55,7 +55,7 @@ rdc:deploy: <slug> → <domain>
|
|
|
55
55
|
```
|
|
56
56
|
rdc:deploy new: <slug>
|
|
57
57
|
[ ] Registry entry loaded (or interactive create)
|
|
58
|
-
[ ] DNS path chosen (A:
|
|
58
|
+
[ ] DNS path chosen (A: staging wildcard B: apex C: other zone)
|
|
59
59
|
[ ] DNS record verified or wildcard confirmed
|
|
60
60
|
[ ] Cloudflare proxy setting correct for DNS path
|
|
61
61
|
[ ] server_uuid, project_uuid, environment_uuid, github_app_uuid resolved
|
|
@@ -103,7 +103,7 @@ rdc:deploy audit: fleet scan
|
|
|
103
103
|
[ ] Env var drift (registry.env_vars_needed vs Coolify env)
|
|
104
104
|
[ ] Branch mismatches (Coolify git_branch ≠ expected)
|
|
105
105
|
[ ] Disk space on 64.237.54.189
|
|
106
|
-
[ ]
|
|
106
|
+
[ ] DNS/proxy misconfigs on configured staging wildcard
|
|
107
107
|
[ ] Duplicate apps (same repo, multiple UUIDs)
|
|
108
108
|
|
|
109
109
|
Findings:
|
|
@@ -127,7 +127,7 @@ There is no Coolify MCP server — do not reference `@masonator/coolify-mcp`.
|
|
|
127
127
|
|
|
128
128
|
```bash
|
|
129
129
|
_COOLIFY=$(curl -s http://127.0.0.1:52437/v/coolify-api)
|
|
130
|
-
curl -s -H "Authorization: Bearer $_COOLIFY"
|
|
130
|
+
curl -s -H "Authorization: Bearer $_COOLIFY" "$DEPLOY_API_BASE/api/v1/applications"
|
|
131
131
|
```
|
|
132
132
|
|
|
133
133
|
If clauth daemon is not responding:
|
|
@@ -143,7 +143,7 @@ BLOCKED: clauth daemon not responding. Run scripts\restart-clauth.bat, unlock at
|
|
|
143
143
|
```
|
|
144
144
|
Server UUID: ih386anenvvvn6fy1umtyow0
|
|
145
145
|
Server IP: 64.237.54.189
|
|
146
|
-
Dashboard:
|
|
146
|
+
Dashboard: <deployment-dashboard-url>
|
|
147
147
|
GitHub App UUID: xdmcy60putp5h9j7k4kwg9c3
|
|
148
148
|
```
|
|
149
149
|
|
package/commands/design.md
CHANGED
|
@@ -15,7 +15,7 @@ Use `skills/design/SKILL.md` as the source of truth.
|
|
|
15
15
|
|
|
16
16
|
- Studio, Palette Library, brand-token, theme, component, or live-editor work
|
|
17
17
|
- Any design task that must understand RDC's real token tables and Studio routes
|
|
18
|
-
- UI critique, audit, polish, colorize, type, layout, or craft work in `
|
|
18
|
+
- UI critique, audit, polish, colorize, type, layout, or craft work in `{PROJECT_ROOT}`
|
|
19
19
|
- Agent-side color-system exploration using Rampa CLI
|
|
20
20
|
|
|
21
21
|
## Arguments
|
package/commands/overnight.md
CHANGED
|
@@ -30,13 +30,13 @@ description: >-
|
|
|
30
30
|
**First action — set the overnight sentinel** so the `no-stop-open-epics` Stop hook engages (interactive sessions are not gated by it):
|
|
31
31
|
|
|
32
32
|
```bash
|
|
33
|
-
mkdir -p
|
|
33
|
+
mkdir -p {PROJECT_ROOT}/.rdc && touch {PROJECT_ROOT}/.rdc/overnight.lock
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
**Last action at end of run (success OR failure) — remove the sentinel:**
|
|
37
37
|
|
|
38
38
|
```bash
|
|
39
|
-
rm -f
|
|
39
|
+
rm -f {PROJECT_ROOT}/.rdc/overnight.lock
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
If the sentinel file does not exist, the Stop hook will NOT block — which means interactive sessions stop freely and only `rdc:overnight` is held to the "drain the queue" contract.
|
package/commands/release.md
CHANGED
|
@@ -1,159 +1,49 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: rdc:release
|
|
3
2
|
description: >-
|
|
4
|
-
Atomic release. Usage `rdc:release <repo> [version]` or `rdc:release <repo> --patch|--minor|--major
|
|
3
|
+
Atomic release. Usage `rdc:release <repo> [version]` or `rdc:release <repo> --patch|--minor|--major|--dry-run` — bump, commit, tag, push, wait CI/publish, install, and verify. Uses repo-local release metadata.
|
|
5
4
|
---
|
|
6
5
|
|
|
6
|
+
# rdc:release — Generic Release
|
|
7
|
+
|
|
7
8
|
> **⚠️ OUTPUT CONTRACT (READ FIRST):** `guides/output-contract.md`
|
|
8
|
-
> Checklist-only output. No
|
|
9
|
+
> Checklist-only output. No raw git/npm/CI dumps.
|
|
9
10
|
> One checklist upfront, updated in place, shown again at end with 1-line verdict.
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
# rdc:release — Atomic LIFEAI Package Release
|
|
14
|
-
|
|
15
|
-
## Purpose
|
|
16
|
-
|
|
17
|
-
Dave has Bash access. Therefore Dave should never be asked to run install
|
|
18
|
-
commands. This skill handles the complete release loop for any LIFEAI-published
|
|
19
|
-
package so the user sees one checklist and one verdict, not a series of "now run
|
|
20
|
-
this" handoffs.
|
|
21
|
-
|
|
22
|
-
## Arguments
|
|
23
|
-
|
|
24
|
-
- `rdc:release <repo>` — patch bump (default), full loop
|
|
25
|
-
- `rdc:release <repo> <version>` — explicit version (e.g. `1.6.0`)
|
|
26
|
-
- `rdc:release <repo> --minor` | `--major` | `--patch` — semver bump
|
|
27
|
-
- `rdc:release <repo> --dry-run` — show checklist and planned version, do nothing
|
|
28
|
-
- `rdc:release` (no args) — list known repos, ask which
|
|
29
|
-
|
|
30
|
-
## Known repos
|
|
31
|
-
|
|
32
|
-
Resolve `<repo>` to source path + install mechanism:
|
|
33
|
-
|
|
34
|
-
| Repo | Source path | Publish | Install | Post-install |
|
|
35
|
-
|------|-------------|---------|---------|--------------|
|
|
36
|
-
| `clauth` | `C:/Dev/clauth` | npm via tag → GitHub Actions | `npm install -g @lifeaitools/clauth@latest` | `curl -s -X POST http://127.0.0.1:52437/restart` |
|
|
37
|
-
| `rdc-skills` | `C:/Dev/rdc-skills` | local (no npm) | `bash C:/Dev/rdc-skills/scripts/install.sh` + cp fallback | none |
|
|
38
|
-
| `regen-media` | `C:/Dev/regen-root/mcp-servers/regen-media` | local | restart MCP server | none |
|
|
39
|
-
| `gws` | `C:/Dev/regen-root/mcp-servers/gws` | local | restart MCP server | none |
|
|
40
|
-
|
|
41
|
-
Add new repos to this table as they emerge. If user specifies an unknown repo,
|
|
42
|
-
ask for the source path and install command.
|
|
43
|
-
|
|
44
|
-
## Checklist (run every mode)
|
|
12
|
+
## Checklist
|
|
45
13
|
|
|
46
14
|
```
|
|
47
|
-
rdc:release: <repo> vX.Y.Z
|
|
15
|
+
rdc:release: <repo> vX.Y.Z -> vA.B.C
|
|
48
16
|
[ ] Source path resolved
|
|
49
|
-
[ ]
|
|
50
|
-
[ ]
|
|
17
|
+
[ ] Release metadata read
|
|
18
|
+
[ ] Working tree clean or user-approved dirty scope identified
|
|
19
|
+
[ ] Current version detected
|
|
51
20
|
[ ] New version computed
|
|
52
|
-
[ ] Dry-run gate
|
|
53
|
-
[ ]
|
|
21
|
+
[ ] Dry-run gate handled
|
|
22
|
+
[ ] Version files updated
|
|
23
|
+
[ ] Tests/self-test passed
|
|
54
24
|
[ ] Commit created
|
|
55
|
-
[ ] Tag
|
|
56
|
-
[ ]
|
|
57
|
-
[ ]
|
|
58
|
-
[ ]
|
|
59
|
-
[ ]
|
|
60
|
-
[ ]
|
|
61
|
-
[ ]
|
|
62
|
-
|
|
63
|
-
[ ] Post-install action (daemon restart, etc., if applicable)
|
|
64
|
-
[ ] Smoke test (ping health endpoint or binary --version)
|
|
65
|
-
✅ rdc:release <repo>: vA.B.C live and installed
|
|
25
|
+
[ ] Tag created
|
|
26
|
+
[ ] Branch and tag pushed
|
|
27
|
+
[ ] CI/publish status verified
|
|
28
|
+
[ ] Registry/package/deploy target shows vA.B.C, if applicable
|
|
29
|
+
[ ] Local install/update executed, if applicable
|
|
30
|
+
[ ] Installed/runtime version verified
|
|
31
|
+
[ ] Smoke test passed
|
|
32
|
+
✅ rdc:release <repo>: vA.B.C live and verified
|
|
66
33
|
```
|
|
67
34
|
|
|
68
|
-
##
|
|
35
|
+
## Rules
|
|
69
36
|
|
|
70
|
-
|
|
71
|
-
-
|
|
72
|
-
-
|
|
73
|
-
-
|
|
37
|
+
- Do not release without explicit user authorization.
|
|
38
|
+
- Prefer repo-local release instructions in `.rdc/release.json`, README, package scripts, or CI workflows.
|
|
39
|
+
- Never force push or bypass hooks.
|
|
40
|
+
- Never declare success without verifying the installed or deployed version.
|
|
74
41
|
|
|
75
|
-
|
|
76
|
-
```bash
|
|
77
|
-
cd <source_path>
|
|
78
|
-
git add package.json
|
|
79
|
-
git commit -m "chore(release): vA.B.C"
|
|
80
|
-
if [ "$RDC_TEST" != "1" ]; then
|
|
81
|
-
git tag vA.B.C
|
|
82
|
-
git push && git push --tags
|
|
83
|
-
else
|
|
84
|
-
echo "[RDC_TEST] skipping git tag + git push --tags"
|
|
85
|
-
fi
|
|
86
|
-
```
|
|
87
|
-
Never `--no-verify`. Never `--force`. If pre-commit hook fails, fix root cause, don't skip.
|
|
42
|
+
## RDC Skills Package
|
|
88
43
|
|
|
89
|
-
|
|
44
|
+
After publishing this package to npm, a clean-box install should use:
|
|
90
45
|
|
|
91
|
-
### 3. CI poll (for repos with GH Actions publish)
|
|
92
46
|
```bash
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
# Poll its status field every 20s until conclusion ∈ {success, failure, cancelled}
|
|
96
|
-
# Max 10 minutes — if timeout, fail with [!] and report run URL
|
|
47
|
+
npm install -g @lifeaitools/rdc-skills@latest
|
|
48
|
+
rdc-skills-install --profile core
|
|
97
49
|
```
|
|
98
|
-
|
|
99
|
-
### 4. npm registry poll (only if published to npm)
|
|
100
|
-
```bash
|
|
101
|
-
npm view @lifeaitools/<repo> version
|
|
102
|
-
# Poll every 15s until matches new version
|
|
103
|
-
# Max 3 minutes — usually lands within 30-60s after CI success
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
### 5. Install
|
|
107
|
-
- **clauth:** `npm install -g @lifeaitools/clauth@latest`
|
|
108
|
-
- **rdc-skills:** `bash C:/Dev/rdc-skills/scripts/install.sh` — if output shows only one file copied (known bug), fall back to `cp C:/Dev/rdc-skills/skills/*.md ~/.claude/skills/user/ && cp C:/Dev/rdc-skills/skills/*.md C:/Dev/regen-root/.claude/skills/user/`
|
|
109
|
-
- **MCP servers:** restart the server process (find its PID, kill, respawn — or document how)
|
|
110
|
-
|
|
111
|
-
### 6. Verify install
|
|
112
|
-
- **clauth:** `curl -s http://127.0.0.1:52437/ping | jq -r .app_version` — must equal new version
|
|
113
|
-
- **rdc-skills:** `ls ~/.claude/skills/user/rdc-release.md` (or whichever file is new in this release) — must exist
|
|
114
|
-
- **Global npm:** `npm list -g --depth=0 @lifeaitools/<repo>` — version matches
|
|
115
|
-
|
|
116
|
-
### 7. Post-install
|
|
117
|
-
- **clauth:** `curl -s -X POST http://127.0.0.1:52437/restart` — wait 3s — ping again
|
|
118
|
-
- Others: N/A
|
|
119
|
-
|
|
120
|
-
### 8. Smoke test
|
|
121
|
-
- **clauth:** new tool roundtrip (e.g., `curl -s http://127.0.0.1:52437/get/openai | head -c 10` succeeds without error)
|
|
122
|
-
- **rdc-skills:** load one new skill file and check frontmatter parses
|
|
123
|
-
- **MCP:** server responds to `tools/list`
|
|
124
|
-
|
|
125
|
-
## Failure modes → checklist outcome
|
|
126
|
-
|
|
127
|
-
| Failure | Marker | Next |
|
|
128
|
-
|---------|--------|------|
|
|
129
|
-
| Dirty working tree | `[!]` line 2 | Abort with one-sentence diff summary; ask user to commit or stash |
|
|
130
|
-
| Pre-commit hook fails | `[!]` line 7 | Fix root cause, re-stage, retry (NEVER --no-verify) |
|
|
131
|
-
| CI fails | `[!]` line 11 | Print run URL, tail 20 lines of failing job, ask user |
|
|
132
|
-
| npm doesn't register | `[!]` line 12 | Print npm error, check if CI actually published (not a silent skip) |
|
|
133
|
-
| Install script broken | `[!]` line 13 | Fall back to manual cp; note the installer bug as a task |
|
|
134
|
-
| Version mismatch after install | `[!]` line 14 | Uninstall + clean install; if still wrong, likely cache issue |
|
|
135
|
-
| Post-install restart fails | `[!]` line 15 | Print ping error; instruct user to run `scripts\restart-clauth.bat` manually |
|
|
136
|
-
|
|
137
|
-
Even on failure, show the full checklist with `[!]` markers so user sees exactly
|
|
138
|
-
where it stopped.
|
|
139
|
-
|
|
140
|
-
## Dry-run mode
|
|
141
|
-
|
|
142
|
-
Prints the checklist + resolved version bump + every command that WOULD run, but
|
|
143
|
-
executes nothing beyond reads. Use when user says "what would you do" or before
|
|
144
|
-
risky major bumps.
|
|
145
|
-
|
|
146
|
-
## Never
|
|
147
|
-
|
|
148
|
-
- Never release on behalf of the user without an explicit `rdc:release` invocation
|
|
149
|
-
- Never skip CI verification ("it'll probably work" is how stale installs happen)
|
|
150
|
-
- Never `--force` push, never `--no-verify`, never `--no-gpg-sign`
|
|
151
|
-
- Never touch `main` — release tags go on the default branch the repo uses (`master` for clauth, rdc-skills)
|
|
152
|
-
- Never declare success without verifying the installed version matches
|
|
153
|
-
|
|
154
|
-
## Related
|
|
155
|
-
|
|
156
|
-
- `.claude/rules/clauth.md` — clauth release workflow source of truth
|
|
157
|
-
- `feedback_version_bump_must_tag.md` (memory) — never bump without tagging
|
|
158
|
-
- `feedback_clauth_ci.md` (memory) — CI publish pattern
|
|
159
|
-
- `feedback_just_do_it.md` (memory) — don't hand commands to Dave; run them
|
package/commands/self-test.md
CHANGED
package/commands/watch.md
CHANGED
|
@@ -24,7 +24,7 @@ description: >-
|
|
|
24
24
|
```bash
|
|
25
25
|
node ${CLAUDE_PLUGIN_ROOT}/scripts/watch-init.mjs
|
|
26
26
|
```
|
|
27
|
-
(If `CLAUDE_PLUGIN_ROOT` is not set, resolve the plugin path from your invocation context
|
|
27
|
+
(If `CLAUDE_PLUGIN_ROOT` is not set, resolve the plugin path from your invocation context or the installed plugin cache.)
|
|
28
28
|
|
|
29
29
|
2. **Parse the output.** The script prints `run_id`, `log_path`, `current`, `viewer`, and `open_hint`. Capture `log_path` and `viewer` for the rest of the session.
|
|
30
30
|
|
|
@@ -137,7 +137,7 @@ When `RDC_TEST=1` is set, the skill is running inside the Tier 2 sandbox harness
|
|
|
137
137
|
**Bash pattern:**
|
|
138
138
|
```bash
|
|
139
139
|
if [ "$RDC_TEST" != "1" ]; then
|
|
140
|
-
curl -X POST "
|
|
140
|
+
curl -X POST "$DEPLOY_API_BASE/api/v1/applications/$UUID/deploy" ...
|
|
141
141
|
else
|
|
142
142
|
echo "[RDC_TEST] skipping Coolify deploy"
|
|
143
143
|
fi
|
|
@@ -156,7 +156,7 @@ if (process.env.RDC_TEST !== '1') {
|
|
|
156
156
|
|
|
157
157
|
**New-skill contract:** every new `rdc:*` skill MUST honor `RDC_TEST` before shipping. Tier 2 manifests will fail any skill that writes to external state under the flag.
|
|
158
158
|
|
|
159
|
-
**Known blocker:**
|
|
159
|
+
**Known blocker:** Project-specific cwd hooks must check `process.env.RDC_TEST === '1'` and call `process.exit(0)` early to allow Tier 2 sandbox runs. Without this bypass, headless self-test invocations can fail before the skill loads. File: `~/.claude/hooks/check-cwd.js`.
|
|
160
160
|
|
|
161
161
|
---
|
|
162
162
|
|
package/hooks/check-cwd.js
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// Hard blocks session if CWD is not the
|
|
2
|
+
// Hard blocks session if CWD is not the expected project directory.
|
|
3
3
|
// Exits with code 1 to fail the hook + outputs a blocking systemMessage.
|
|
4
4
|
|
|
5
5
|
const { execSync } = require('child_process');
|
|
6
6
|
const hookLog = require('./hook-logger');
|
|
7
7
|
const cwd = process.cwd().replace(/\\/g, '/');
|
|
8
8
|
|
|
9
|
-
let expected = 'regen-root';
|
|
9
|
+
let expected = process.env.RDC_PROJECT_SCOPE || 'regen-root';
|
|
10
10
|
try {
|
|
11
11
|
expected = execSync('git rev-parse --show-toplevel', { encoding: 'utf8', stdio: 'pipe' })
|
|
12
12
|
.trim().replace(/\\/g, '/');
|
|
13
13
|
} catch (_) {}
|
|
14
14
|
|
|
15
|
-
if (!cwd.endsWith(
|
|
15
|
+
if (!cwd.endsWith(expected)) {
|
|
16
16
|
hookLog('check-cwd', 'SessionStart', 'block', { cwd, expected });
|
|
17
17
|
process.stdout.write(JSON.stringify({
|
|
18
18
|
systemMessage:
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Fires on every Stop event where Claude decided to end_turn.
|
|
6
6
|
* Queries get_open_epics() via Supabase REST.
|
|
7
7
|
* Only blocks if epics with status=todo exist — in_progress means another session owns them.
|
|
8
|
-
* Only fires in the
|
|
8
|
+
* Only fires in the configured project scope (scope guard on event.cwd).
|
|
9
9
|
*
|
|
10
10
|
* Exit codes:
|
|
11
11
|
* 0 = allow stop
|
|
@@ -15,8 +15,9 @@
|
|
|
15
15
|
* NEXT_PUBLIC_SUPABASE_URL=https://<ref>.supabase.co
|
|
16
16
|
* NEXT_PUBLIC_SUPABASE_ANON_KEY=<key>
|
|
17
17
|
*
|
|
18
|
-
* PROJECT SCOPE
|
|
19
|
-
*
|
|
18
|
+
* PROJECT SCOPE:
|
|
19
|
+
* RDC_PROJECT_SCOPE=<folder-name> folder name that must appear in event.cwd
|
|
20
|
+
* RDC_PROJECT_ROOT=<path> root used to find .rdc/overnight.lock and .env.local
|
|
20
21
|
*/
|
|
21
22
|
|
|
22
23
|
const fs = require('fs');
|
|
@@ -24,11 +25,12 @@ const path = require('path');
|
|
|
24
25
|
|
|
25
26
|
// ── Config — override these per project ──────────────────────────────────────
|
|
26
27
|
|
|
27
|
-
const PROJECT_SCOPE = 'regen-root'; //
|
|
28
|
-
const
|
|
28
|
+
const PROJECT_SCOPE = process.env.RDC_PROJECT_SCOPE || 'regen-root'; // LIFEAI default; override per project
|
|
29
|
+
const PROJECT_ROOT = process.env.RDC_PROJECT_ROOT || process.cwd();
|
|
30
|
+
const OVERNIGHT_SENTINEL = process.env.RDC_OVERNIGHT_LOCK || path.join(PROJECT_ROOT, '.rdc', 'overnight.lock');
|
|
29
31
|
const ENV_PATHS = [
|
|
30
|
-
|
|
31
|
-
'
|
|
32
|
+
...(process.env.RDC_ENV_PATHS ? process.env.RDC_ENV_PATHS.split(path.delimiter) : []),
|
|
33
|
+
path.join(PROJECT_ROOT, '.env.local'),
|
|
32
34
|
];
|
|
33
35
|
const SUPABASE_URL = process.env.SUPABASE_URL || readEnvVar('NEXT_PUBLIC_SUPABASE_URL');
|
|
34
36
|
|
|
@@ -76,7 +76,7 @@ function detectRdc(raw) {
|
|
|
76
76
|
|
|
77
77
|
if (event === 'UserPromptSubmit') {
|
|
78
78
|
const prompt = String(raw.prompt || '').trim();
|
|
79
|
-
const m = prompt.match(/^\/
|
|
79
|
+
const m = prompt.match(/^\/rdc[:-]([a-z][a-z0-9-]*)\b/i);
|
|
80
80
|
if (!m) return null;
|
|
81
81
|
const command = normalizeCommandName(m[1]);
|
|
82
82
|
if (RDC_COMMANDS.has(command)) return command;
|
|
@@ -87,6 +87,20 @@ function detectRdc(raw) {
|
|
|
87
87
|
|
|
88
88
|
function writeMarker(raw, command) {
|
|
89
89
|
fs.mkdirSync(markerDir(), { recursive: true });
|
|
90
|
+
const p = markerPath(raw.session_id);
|
|
91
|
+
if (fs.existsSync(p)) {
|
|
92
|
+
try {
|
|
93
|
+
const existing = JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
94
|
+
const started = Date.parse(existing.started_at || '');
|
|
95
|
+
const isRecentDuplicate = Number.isFinite(started) &&
|
|
96
|
+
Date.now() - started <= 1000 &&
|
|
97
|
+
existing.session_id === (raw.session_id || null) &&
|
|
98
|
+
existing.command === command;
|
|
99
|
+
if (isRecentDuplicate) {
|
|
100
|
+
return { ...existing, deduped: true };
|
|
101
|
+
}
|
|
102
|
+
} catch {}
|
|
103
|
+
}
|
|
90
104
|
const marker = {
|
|
91
105
|
session_id: raw.session_id || null,
|
|
92
106
|
command,
|
|
@@ -98,7 +112,7 @@ function writeMarker(raw, command) {
|
|
|
98
112
|
started_at: new Date().toISOString(),
|
|
99
113
|
hook_event_name: raw.hook_event_name || null,
|
|
100
114
|
};
|
|
101
|
-
fs.writeFileSync(
|
|
115
|
+
fs.writeFileSync(p, JSON.stringify(marker, null, 2));
|
|
102
116
|
return marker;
|
|
103
117
|
}
|
|
104
118
|
|
|
@@ -127,7 +141,7 @@ async function main() {
|
|
|
127
141
|
|
|
128
142
|
try {
|
|
129
143
|
const marker = writeMarker(raw, command);
|
|
130
|
-
hookLog('rdc-invocation-marker', raw.hook_event_name || 'unknown', 'marked', {
|
|
144
|
+
hookLog('rdc-invocation-marker', raw.hook_event_name || 'unknown', marker.deduped ? 'deduped' : 'marked', {
|
|
131
145
|
command,
|
|
132
146
|
session_id: marker.session_id,
|
|
133
147
|
});
|
|
@@ -60,6 +60,14 @@ function pass(marker) {
|
|
|
60
60
|
process.exit(0);
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
function passReentry(marker) {
|
|
64
|
+
try { fs.unlinkSync(marker.path); } catch {}
|
|
65
|
+
hookLog('rdc-output-contract-gate', 'Stop', 'pass-reentry', {
|
|
66
|
+
command: marker.data.command || null,
|
|
67
|
+
});
|
|
68
|
+
process.exit(0);
|
|
69
|
+
}
|
|
70
|
+
|
|
63
71
|
async function main() {
|
|
64
72
|
let raw;
|
|
65
73
|
try { raw = JSON.parse(await readStdin()); } catch { process.exit(0); }
|
|
@@ -70,6 +78,7 @@ async function main() {
|
|
|
70
78
|
const checklist = hasChecklist(message);
|
|
71
79
|
const verdict = hasVerdict(message);
|
|
72
80
|
if (checklist && verdict) pass(marker);
|
|
81
|
+
if (raw.stop_hook_active === true) passReentry(marker);
|
|
73
82
|
|
|
74
83
|
const command = marker.data.command || 'rdc';
|
|
75
84
|
const missing = [];
|
|
@@ -18,10 +18,11 @@
|
|
|
18
18
|
|
|
19
19
|
const fs = require('fs');
|
|
20
20
|
const path = require('path');
|
|
21
|
+
const os = require('os');
|
|
21
22
|
const hookLog = require('./hook-logger');
|
|
22
23
|
|
|
23
24
|
const MARKER_FILE = path.join(
|
|
24
|
-
process.env.USERPROFILE || process.env.HOME ||
|
|
25
|
+
process.env.USERPROFILE || process.env.HOME || os.homedir(),
|
|
25
26
|
'.claude',
|
|
26
27
|
'fixit.marker'
|
|
27
28
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lifeaitools/rdc-skills",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.38",
|
|
4
4
|
"description": "RDC typed-agent dispatch skill suite for Claude Code - plan, build, review, overnight builds",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude-code",
|
|
@@ -19,14 +19,19 @@
|
|
|
19
19
|
"type": "plugin",
|
|
20
20
|
"skills": "skills/",
|
|
21
21
|
"guides": "guides/",
|
|
22
|
-
"version": "0.9.
|
|
22
|
+
"version": "0.9.38",
|
|
23
23
|
"commands": "commands/"
|
|
24
24
|
},
|
|
25
|
+
"bin": {
|
|
26
|
+
"rdc-skills-install": "scripts/install-rdc-skills.js",
|
|
27
|
+
"rdc-skills-self-test": "scripts/self-test.mjs"
|
|
28
|
+
},
|
|
25
29
|
"scripts": {
|
|
26
30
|
"install-rdc-skills": "node scripts/install-rdc-skills.js",
|
|
27
31
|
"uninstall:win": "powershell -ExecutionPolicy Bypass -File scripts/uninstall.ps1",
|
|
28
32
|
"uninstall:unix": "bash scripts/uninstall.sh",
|
|
29
33
|
"validate": "node tests/validate-skills.js",
|
|
30
|
-
"rdc-design": "node scripts/rdc-design-cli.mjs"
|
|
34
|
+
"rdc-design": "node scripts/rdc-design-cli.mjs",
|
|
35
|
+
"test:hooks": "node scripts/test-rdc-hooks.mjs"
|
|
31
36
|
}
|
|
32
37
|
}
|
|
@@ -766,8 +766,12 @@ async function main() {
|
|
|
766
766
|
console.log('');
|
|
767
767
|
|
|
768
768
|
if (!fs.existsSync(claudeHome)) {
|
|
769
|
-
|
|
770
|
-
|
|
769
|
+
fs.mkdirSync(claudeHome, { recursive: true });
|
|
770
|
+
warn(`CLAUDE_HOME did not exist — created ${claudeHome}`);
|
|
771
|
+
}
|
|
772
|
+
if (!fs.existsSync(settingsPath)) {
|
|
773
|
+
writeJson(settingsPath, {});
|
|
774
|
+
info(` created settings.json`);
|
|
771
775
|
}
|
|
772
776
|
|
|
773
777
|
// Read version + git SHA once
|
package/scripts/self-test.mjs
CHANGED
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
import { readFileSync, readdirSync, existsSync, writeFileSync, appendFileSync, renameSync, mkdirSync, statSync } from "node:fs";
|
|
32
32
|
import { join, basename, dirname, resolve } from "node:path";
|
|
33
33
|
import { fileURLToPath } from "node:url";
|
|
34
|
+
import { spawnSync } from "node:child_process";
|
|
34
35
|
import { loadAllManifests } from "./lib/manifest-schema.mjs";
|
|
35
36
|
import { runManifest } from "./lib/runner.mjs";
|
|
36
37
|
import {
|
|
@@ -802,6 +803,19 @@ function auditRequiredHookWiring() {
|
|
|
802
803
|
return findings;
|
|
803
804
|
}
|
|
804
805
|
|
|
806
|
+
function runHookBehaviorTests() {
|
|
807
|
+
const result = spawnSync(process.execPath, [join(REPO_ROOT, "scripts", "test-rdc-hooks.mjs")], {
|
|
808
|
+
cwd: REPO_ROOT,
|
|
809
|
+
encoding: "utf8",
|
|
810
|
+
});
|
|
811
|
+
return {
|
|
812
|
+
ok: result.status === 0,
|
|
813
|
+
status: result.status,
|
|
814
|
+
stdout: (result.stdout || "").trim(),
|
|
815
|
+
stderr: (result.stderr || "").trim(),
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
|
|
805
819
|
// ─── Tier 2 behavioral runner ──────────────────────────────────────────────
|
|
806
820
|
|
|
807
821
|
async function runPool(items, concurrency, worker) {
|
|
@@ -1038,6 +1052,7 @@ function main() {
|
|
|
1038
1052
|
}
|
|
1039
1053
|
|
|
1040
1054
|
const guideValidatorResultJson = !ONLY_SKILL ? runGuideContentValidator() : null;
|
|
1055
|
+
const hookBehaviorResultJson = !ONLY_SKILL ? runHookBehaviorTests() : null;
|
|
1041
1056
|
|
|
1042
1057
|
const failed = results.filter((r) => r.errors.length > 0);
|
|
1043
1058
|
const warned = results.filter((r) => r.warnings.length > 0 && r.errors.length === 0);
|
|
@@ -1050,6 +1065,11 @@ function main() {
|
|
|
1050
1065
|
...duplicateFindings.filter((f) => f.level === "error"),
|
|
1051
1066
|
...orphanHookFindings.filter((f) => f.level === "error"),
|
|
1052
1067
|
...(guideValidatorResultJson ? guideValidatorResultJson.errors : []),
|
|
1068
|
+
...(hookBehaviorResultJson && !hookBehaviorResultJson.ok ? [{
|
|
1069
|
+
level: "error",
|
|
1070
|
+
code: "hook-behavior-test-failed",
|
|
1071
|
+
message: hookBehaviorResultJson.stderr || hookBehaviorResultJson.stdout || `exit ${hookBehaviorResultJson.status}`,
|
|
1072
|
+
}] : []),
|
|
1053
1073
|
];
|
|
1054
1074
|
const globalWarnings = [
|
|
1055
1075
|
...manifestAudit.findings.filter((f) => f.level === "warn"),
|
|
@@ -1092,6 +1112,7 @@ function main() {
|
|
|
1092
1112
|
findings: guideValidatorResultJson.findings,
|
|
1093
1113
|
}
|
|
1094
1114
|
: null,
|
|
1115
|
+
hook_behavior_tests: hookBehaviorResultJson,
|
|
1095
1116
|
results: results.map((r) => ({
|
|
1096
1117
|
skill: r.skill,
|
|
1097
1118
|
file: r.file,
|
|
@@ -1217,10 +1238,12 @@ function main() {
|
|
|
1217
1238
|
let duplicateFindings = [];
|
|
1218
1239
|
let orphanHookFindings = [];
|
|
1219
1240
|
let requiredHookFindings = [];
|
|
1241
|
+
let hookBehaviorResult = null;
|
|
1220
1242
|
if (!ONLY_SKILL) {
|
|
1221
1243
|
duplicateFindings = auditDuplicates(results);
|
|
1222
1244
|
orphanHookFindings = auditOrphanHooks(results);
|
|
1223
1245
|
requiredHookFindings = auditRequiredHookWiring();
|
|
1246
|
+
hookBehaviorResult = runHookBehaviorTests();
|
|
1224
1247
|
}
|
|
1225
1248
|
|
|
1226
1249
|
if (duplicateFindings.length + orphanHookFindings.length + requiredHookFindings.length > 0) {
|
|
@@ -1230,6 +1253,14 @@ function main() {
|
|
|
1230
1253
|
}
|
|
1231
1254
|
}
|
|
1232
1255
|
|
|
1256
|
+
if (hookBehaviorResult) {
|
|
1257
|
+
console.log("\nhook behavior tests\n");
|
|
1258
|
+
console.log("─".repeat(80));
|
|
1259
|
+
console.log(hookBehaviorResult.ok ? " pass rdc marker/gate behavioral smoke tests" : " FAIL rdc marker/gate behavioral smoke tests");
|
|
1260
|
+
if (hookBehaviorResult.stdout) console.log(` ${hookBehaviorResult.stdout}`);
|
|
1261
|
+
if (hookBehaviorResult.stderr) console.log(` ${hookBehaviorResult.stderr}`);
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1233
1264
|
if (FIXED_FILES.length > 0) {
|
|
1234
1265
|
console.log("\nfixed files (review with git diff):");
|
|
1235
1266
|
for (const p of FIXED_FILES) console.log(` ${p}`);
|
|
@@ -1243,6 +1274,11 @@ function main() {
|
|
|
1243
1274
|
...orphanHookFindings.filter((f) => f.level === "error"),
|
|
1244
1275
|
...requiredHookFindings.filter((f) => f.level === "error"),
|
|
1245
1276
|
...(guideValidatorResult ? guideValidatorResult.errors : []),
|
|
1277
|
+
...(hookBehaviorResult && !hookBehaviorResult.ok ? [{
|
|
1278
|
+
level: "error",
|
|
1279
|
+
code: "hook-behavior-test-failed",
|
|
1280
|
+
message: hookBehaviorResult.stderr || hookBehaviorResult.stdout || `exit ${hookBehaviorResult.status}`,
|
|
1281
|
+
}] : []),
|
|
1246
1282
|
];
|
|
1247
1283
|
const globalWarnings = [
|
|
1248
1284
|
...manifestAudit.findings.filter((f) => f.level === "warn"),
|