@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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rdc",
3
- "version": "0.9.36",
3
+ "version": "0.9.38",
4
4
  "description": "RDC typed-agent dispatch skill suite for Claude Code — plan, build, review, overnight unattended builds with work-item tracking and TDD enforcement.",
5
5
  "author": {
6
6
  "name": "LIFEAI",
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
@@ -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: *.dev.place.fund B: apex C: other zone)
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
- [ ] CF proxy misconfigs on *.dev.place.fund
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" https://deploy.regendevcorp.com/api/v1/applications
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: https://deploy.regendevcorp.com
146
+ Dashboard: <deployment-dashboard-url>
147
147
  GitHub App UUID: xdmcy60putp5h9j7k4kwg9c3
148
148
  ```
149
149
 
@@ -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 `regen-root`
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
@@ -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 C:/Dev/regen-root/.rdc && touch C:/Dev/regen-root/.rdc/overnight.lock
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 C:/Dev/regen-root/.rdc/overnight.lock
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.
@@ -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` or `rdc:release <repo> --dry-run` — bump, commit, tag, push, wait CI, install, verify, restart. Known repos: clauth, rdc-skills, regen-media, gws. No user handoff.
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 tool-call narration. No raw git/npm/CI dumps.
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
- > **Sandbox contract:** This skill honors `RDC_TEST=1` per `guides/agent-bootstrap.md` § RDC_TEST Sandbox Contract. Destructive external calls (git tag push, CI poll, npm publish verification, global install, daemon restart) short-circuit under the flag — the checklist still runs, but each destructive step echoes `[RDC_TEST] skipping <step>` instead of mutating external state.
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 vA.B.C
15
+ rdc:release: <repo> vX.Y.Z -> vA.B.C
48
16
  [ ] Source path resolved
49
- [ ] Working tree clean (git status)
50
- [ ] Current version detected (package.json)
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 (if --dry-run, stop here)
53
- [ ] package.json bumped (all version fields)
21
+ [ ] Dry-run gate handled
22
+ [ ] Version files updated
23
+ [ ] Tests/self-test passed
54
24
  [ ] Commit created
55
- [ ] Tag vA.B.C created
56
- [ ] Push to origin
57
- [ ] Push --tags to origin
58
- [ ] CI run located (gh run list, if applicable)
59
- [ ] CI completed successfully (poll every 20s, 10min timeout)
60
- [ ] npm registry shows vA.B.C (if npm package, poll every 15s, 3min timeout)
61
- [ ] Local install executed
62
- [ ] Installed version verified matches vA.B.C
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
- ## Execution details (silent to user — don't narrate)
35
+ ## Rules
69
36
 
70
- ### 1. Version bump
71
- - Read `package.json`, parse version
72
- - Apply bump: patch (default), minor, major, or explicit
73
- - Rewrite all `version` fields (some packages have version in `claude.version` too)
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
- ### 2. Commit + tag + push
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
- *Under `$RDC_TEST=1`, sections 3 (CI poll), 4 (npm registry poll), 5 (install), 6 (verify install), 7 (post-install), and 8 (smoke test) are also skipped — echo `[RDC_TEST] skipping <section>` for each and mark the checklist line as `[~]`.*
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
- # Locate the run triggered by the tag push
94
- gh run list --repo LIFEAI/<repo> --limit 5 --json status,conclusion,headBranch,databaseId
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
@@ -29,7 +29,7 @@ description: >-
29
29
 
30
30
  1. **Run the linter:**
31
31
  ```bash
32
- node C:/Dev/rdc-skills/scripts/self-test.mjs
32
+ node {RDC_SKILLS_ROOT}/scripts/self-test.mjs
33
33
  ```
34
34
 
35
35
  2. **Interpret exit codes:**
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. On Windows it's typically `C:/Dev/rdc-skills`.)
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 "https://deploy.regendevcorp.com/api/v1/applications/$UUID/deploy" ...
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:** The `check-cwd.js` SessionStart hook hard-blocks Claude sessions launched from `C:/Dev/rdc-skills`. The hook must check `process.env.RDC_TEST === '1'` and call `process.exit(0)` early to allow Tier 2 sandbox runs. Without this bypass, all Tier 2 headless invocations fail with `exit_code: -1`. File: `~/.claude/hooks/check-cwd.js`.
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
 
@@ -1,18 +1,18 @@
1
1
  #!/usr/bin/env node
2
- // Hard blocks session if CWD is not the regen-root project directory.
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('regen-root')) {
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 regen-root project (scope guard on event.cwd).
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 in package.json or plugin config:
19
- * "hookScope": "regen-root" folder name that must appear in event.cwd
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'; // only block in sessions inside this folder name
28
- const OVERNIGHT_SENTINEL = 'C:/Dev/regen-root/.rdc/overnight.lock'; // only fire when this exists
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
- 'C:/Dev/regen-root/apps/rdc-marketing-engine/.env.local',
31
- 'C:/Dev/regen-root/.env.local',
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(/^\/(?:rdc[:-])?([a-z][a-z0-9-]*)\b/i);
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(markerPath(raw.session_id), JSON.stringify(marker, null, 2));
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 || 'C:/Users/DaveLadouceur',
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.36",
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.36",
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
- fail(`CLAUDE_HOME not found: ${claudeHome}`);
770
- process.exit(1);
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
@@ -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"),