@lifeaitools/rdc-skills 0.9.34 → 0.9.36
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 +25 -0
- package/README.md +22 -1
- package/guides/rdc-skills-startup.md +30 -0
- package/hooks/rdc-invocation-marker.js +143 -0
- package/hooks/rdc-output-contract-gate.js +85 -0
- package/package.json +2 -2
- package/scripts/install-rdc-skills.js +122 -16
- package/scripts/install.ps1 +45 -14
- package/scripts/self-test.mjs +79 -2
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## v0.9.36 — Portable installer profiles and startup onboarding
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Installer profiles.** `--profile core` installs clean-box-safe hooks only; `--profile lifeai` keeps the LIFEAI/regen-root cwd, clauth, Supabase, and overnight hooks. `auto` chooses `lifeai` only when a sibling `regen-root` project is detected.
|
|
15
|
+
- **Managed startup guide.** New `guides/rdc-skills-startup.md` explains what RDC skills add, the active profiles, and where to find skills, commands, guides, hooks, and installer behavior.
|
|
16
|
+
- **Optional startup block writer.** `--project-root <path> --write-startup-blocks` writes `.rdc/guides/rdc-skills-startup.md` plus managed RDC sections in project `CLAUDE.md` and `AGENTS.md`.
|
|
17
|
+
- **Install welcome.** The installer now prints the selected profile, what it enables, and where to look next.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- The default clean-box profile no longer wires regen-root-specific hooks such as cwd lock, Supabase work-item exit gate, rate-limit retry, or overnight open-epic guard.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## v0.9.35 — RDC output-contract hook enforcement
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
|
|
29
|
+
- **RDC slash-command marker hook.** `rdc-invocation-marker.js` marks direct `/rdc:*` skill invocations through `UserPromptExpansion`, with a `UserPromptSubmit` fallback for older runtimes, and injects the output/engineering contract reminder.
|
|
30
|
+
- **RDC Stop gate.** `rdc-output-contract-gate.js` blocks completion for active RDC invocations until the final assistant message includes both a checklist row and a verdict line.
|
|
31
|
+
- **Installer wiring and self-test coverage.** The Node and PowerShell installers now wire the RDC marker/gate hooks, and `self-test` fails when required hook files or installer references are missing.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
10
35
|
## 0.6.1 — Tier 2 stability + rdc:watch session watcher
|
|
11
36
|
|
|
12
37
|
### Added
|
package/README.md
CHANGED
|
@@ -45,6 +45,27 @@ rm -rf ~/.claude/plugins/cache/rdc-skills/rdc-skills/0.7.*
|
|
|
45
45
|
|
|
46
46
|
Use **one method only**: plugin (Option A or B) OR manual install — never both.
|
|
47
47
|
|
|
48
|
+
### Installer profiles
|
|
49
|
+
|
|
50
|
+
`scripts/install-rdc-skills.js` supports profiles:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
node scripts/install-rdc-skills.js --profile core
|
|
54
|
+
node scripts/install-rdc-skills.js --profile lifeai
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
- `core` is safe for a clean machine with only rdc-skills installed. It wires portable hooks only: RDC output-contract enforcement, foreground-process policy, commit-message hygiene, and non-blocking work logging.
|
|
58
|
+
- `lifeai` is for the LIFEAI/regen-root workstation. It additionally wires the regen-root cwd lock, clauth/Supabase work-item gates, overnight queue guard, and LIFEAI session hooks.
|
|
59
|
+
- `auto` is the default. It chooses `lifeai` only when a sibling `regen-root` project is detected; otherwise it chooses `core`.
|
|
60
|
+
|
|
61
|
+
To add managed startup instructions to a project:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
node scripts/install-rdc-skills.js --project-root /path/to/project --write-startup-blocks
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
This writes `.rdc/guides/rdc-skills-startup.md` and managed RDC sections in `CLAUDE.md` and `AGENTS.md`.
|
|
68
|
+
|
|
48
69
|
### Legacy install scripts (deprecated since v0.6.0)
|
|
49
70
|
|
|
50
71
|
`scripts/install.sh` and `scripts/install.ps1` copy files into `~/.claude/skills/user/`. These exist only for environments that cannot use the plugin system. Running them alongside the plugin creates duplicates. Do not use them if you have the plugin installed.
|
|
@@ -176,7 +197,7 @@ python3 -c "import json; print(json.load(open('package.json'))['version'])"
|
|
|
176
197
|
/rdc:help
|
|
177
198
|
```
|
|
178
199
|
|
|
179
|
-
Current version: **v0.9.
|
|
200
|
+
Current version: **v0.9.36**
|
|
180
201
|
|
|
181
202
|
## Quick Start
|
|
182
203
|
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# RDC Skills Startup Contract
|
|
2
|
+
> Managed by `rdc-skills`. Keep local project-specific details in adjacent project guides.
|
|
3
|
+
|
|
4
|
+
## What RDC Skills Adds
|
|
5
|
+
|
|
6
|
+
- Slash commands for the RDC workflow: plan, build, review, report, design, deploy, release, status, and work item operations.
|
|
7
|
+
- Output-contract enforcement for active `/rdc:*` turns: visible checklist rows plus a final verdict line.
|
|
8
|
+
- Engineering behavior guidance: small scoped changes, explicit assumptions, evidence for completed work, and honest blockers.
|
|
9
|
+
- Optional project integrations for work items, credentials, deployments, and release automation.
|
|
10
|
+
|
|
11
|
+
## Agent Startup Rules
|
|
12
|
+
|
|
13
|
+
1. Read the active project instructions first (`CLAUDE.md` for Claude Code, `AGENTS.md` for Codex).
|
|
14
|
+
2. For any `/rdc:*` invocation, follow `.rdc/guides/output-contract.md` and `.rdc/guides/engineering-behavior.md`.
|
|
15
|
+
3. Do not treat skill prose as proof. Completed work needs evidence: command output, test result, route probe, screenshot, SQL result, or source citation.
|
|
16
|
+
4. If a project has its own approval gates, architecture rules, or credential model, those project rules override generic RDC defaults.
|
|
17
|
+
5. When an RDC skill cannot access the required project services, stop with a specific blocker instead of inventing a fallback.
|
|
18
|
+
|
|
19
|
+
## Profiles
|
|
20
|
+
|
|
21
|
+
- `core`: portable defaults for a clean machine. No regen-root cwd lock, clauth requirement, Supabase exit gate, or LIFEAI deployment assumption.
|
|
22
|
+
- `lifeai`: LIFEAI/regen-root defaults. Enables project-specific hooks and workflows for clauth, Supabase work items, deployment, and overnight queue behavior.
|
|
23
|
+
|
|
24
|
+
## Where To Look
|
|
25
|
+
|
|
26
|
+
- Skills: `skills/<name>/SKILL.md`
|
|
27
|
+
- Commands: `commands/<name>.md`
|
|
28
|
+
- Guides: `guides/*.md` and project copies under `.rdc/guides/`
|
|
29
|
+
- Hooks: `hooks/*.js`
|
|
30
|
+
- Installer: `scripts/install-rdc-skills.js`
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* UserPromptExpansion/UserPromptSubmit hook — mark active rdc:* invocations.
|
|
4
|
+
*
|
|
5
|
+
* This does not enforce compliance. It primes the turn with the RDC contract
|
|
6
|
+
* and leaves a session marker for rdc-output-contract-gate.js to enforce at
|
|
7
|
+
* Stop time.
|
|
8
|
+
*/
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const os = require('os');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
const hookLog = require('./hook-logger');
|
|
15
|
+
|
|
16
|
+
const RDC_COMMANDS = new Set([
|
|
17
|
+
'build',
|
|
18
|
+
'co-develop',
|
|
19
|
+
'collab',
|
|
20
|
+
'deploy',
|
|
21
|
+
'design',
|
|
22
|
+
'fixit',
|
|
23
|
+
'fs-mcp',
|
|
24
|
+
'handoff',
|
|
25
|
+
'help',
|
|
26
|
+
'overnight',
|
|
27
|
+
'plan',
|
|
28
|
+
'preplan',
|
|
29
|
+
'prototype',
|
|
30
|
+
'release',
|
|
31
|
+
'report',
|
|
32
|
+
'review',
|
|
33
|
+
'self-test',
|
|
34
|
+
'status',
|
|
35
|
+
'terminal-config',
|
|
36
|
+
'watch',
|
|
37
|
+
'workitems',
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
function readStdin() {
|
|
41
|
+
return new Promise((resolve) => {
|
|
42
|
+
let input = '';
|
|
43
|
+
process.stdin.setEncoding('utf8');
|
|
44
|
+
process.stdin.on('data', (chunk) => { input += chunk; });
|
|
45
|
+
process.stdin.on('end', () => resolve(input));
|
|
46
|
+
process.stdin.resume();
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function markerDir() {
|
|
51
|
+
return path.join(os.homedir(), '.claude', 'rdc-active');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function markerPath(sessionId) {
|
|
55
|
+
const safe = String(sessionId || 'unknown').replace(/[^a-zA-Z0-9_.-]/g, '_');
|
|
56
|
+
return path.join(markerDir(), `${safe}.json`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function normalizeCommandName(value) {
|
|
60
|
+
return String(value || '')
|
|
61
|
+
.trim()
|
|
62
|
+
.replace(/^\/+/, '')
|
|
63
|
+
.replace(/^rdc[:-]/i, '')
|
|
64
|
+
.toLowerCase();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function detectRdc(raw) {
|
|
68
|
+
const event = raw.hook_event_name || '';
|
|
69
|
+
if (event === 'UserPromptExpansion') {
|
|
70
|
+
if (raw.command_source && raw.command_source !== 'plugin') return null;
|
|
71
|
+
const command = normalizeCommandName(raw.command_name);
|
|
72
|
+
if (command === 'help' && !/^\/rdc[:-]help\b/i.test(String(raw.prompt || ''))) return null;
|
|
73
|
+
if (RDC_COMMANDS.has(command)) return command;
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (event === 'UserPromptSubmit') {
|
|
78
|
+
const prompt = String(raw.prompt || '').trim();
|
|
79
|
+
const m = prompt.match(/^\/(?:rdc[:-])?([a-z][a-z0-9-]*)\b/i);
|
|
80
|
+
if (!m) return null;
|
|
81
|
+
const command = normalizeCommandName(m[1]);
|
|
82
|
+
if (RDC_COMMANDS.has(command)) return command;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function writeMarker(raw, command) {
|
|
89
|
+
fs.mkdirSync(markerDir(), { recursive: true });
|
|
90
|
+
const marker = {
|
|
91
|
+
session_id: raw.session_id || null,
|
|
92
|
+
command,
|
|
93
|
+
command_name: raw.command_name || null,
|
|
94
|
+
command_args: raw.command_args || null,
|
|
95
|
+
prompt: raw.prompt || null,
|
|
96
|
+
cwd: raw.cwd || null,
|
|
97
|
+
transcript_path: raw.transcript_path || null,
|
|
98
|
+
started_at: new Date().toISOString(),
|
|
99
|
+
hook_event_name: raw.hook_event_name || null,
|
|
100
|
+
};
|
|
101
|
+
fs.writeFileSync(markerPath(raw.session_id), JSON.stringify(marker, null, 2));
|
|
102
|
+
return marker;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function outputContext(eventName, command) {
|
|
106
|
+
const additionalContext = [
|
|
107
|
+
`RDC CONTRACT ACTIVE for /${command}.`,
|
|
108
|
+
'Before responding, follow the project-local guide contracts:',
|
|
109
|
+
'- .rdc/guides/output-contract.md: show one checklist for this invocation, update it as work progresses, and end with the required verdict line.',
|
|
110
|
+
'- .rdc/guides/engineering-behavior.md: assumptions, scope, evidence, deviations, blockers, and verification must be explicit.',
|
|
111
|
+
'Runtime enforcement is on emitted artifacts: the final assistant message must contain at least one checklist row and a verdict line.',
|
|
112
|
+
].join('\n');
|
|
113
|
+
|
|
114
|
+
process.stdout.write(JSON.stringify({
|
|
115
|
+
hookSpecificOutput: {
|
|
116
|
+
hookEventName: eventName,
|
|
117
|
+
additionalContext,
|
|
118
|
+
},
|
|
119
|
+
}));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function main() {
|
|
123
|
+
let raw;
|
|
124
|
+
try { raw = JSON.parse(await readStdin()); } catch { process.exit(0); }
|
|
125
|
+
const command = detectRdc(raw);
|
|
126
|
+
if (!command) process.exit(0);
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
const marker = writeMarker(raw, command);
|
|
130
|
+
hookLog('rdc-invocation-marker', raw.hook_event_name || 'unknown', 'marked', {
|
|
131
|
+
command,
|
|
132
|
+
session_id: marker.session_id,
|
|
133
|
+
});
|
|
134
|
+
outputContext(raw.hook_event_name || 'UserPromptExpansion', command);
|
|
135
|
+
} catch (e) {
|
|
136
|
+
hookLog('rdc-invocation-marker', raw.hook_event_name || 'unknown', 'error', {
|
|
137
|
+
command,
|
|
138
|
+
error: e.message,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
main().catch(() => process.exit(0));
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Stop hook — enforce visible RDC output contract artifacts.
|
|
4
|
+
*
|
|
5
|
+
* Triggered only when rdc-invocation-marker.js has marked the session. It checks
|
|
6
|
+
* positive output patterns only: at least one checklist row and one verdict
|
|
7
|
+
* line. It intentionally does not police forbidden phrases.
|
|
8
|
+
*/
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const os = require('os');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
const hookLog = require('./hook-logger');
|
|
15
|
+
|
|
16
|
+
function readStdin() {
|
|
17
|
+
return new Promise((resolve) => {
|
|
18
|
+
let input = '';
|
|
19
|
+
process.stdin.setEncoding('utf8');
|
|
20
|
+
process.stdin.on('data', (chunk) => { input += chunk; });
|
|
21
|
+
process.stdin.on('end', () => resolve(input));
|
|
22
|
+
process.stdin.resume();
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function markerPath(sessionId) {
|
|
27
|
+
const safe = String(sessionId || 'unknown').replace(/[^a-zA-Z0-9_.-]/g, '_');
|
|
28
|
+
return path.join(os.homedir(), '.claude', 'rdc-active', `${safe}.json`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function readMarker(sessionId) {
|
|
32
|
+
const p = markerPath(sessionId);
|
|
33
|
+
if (!fs.existsSync(p)) return null;
|
|
34
|
+
try {
|
|
35
|
+
return { path: p, data: JSON.parse(fs.readFileSync(p, 'utf8')) };
|
|
36
|
+
} catch {
|
|
37
|
+
return { path: p, data: { command: 'unknown' } };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function hasChecklist(text) {
|
|
42
|
+
return /(?:^|\n)\s*(?:[-*]\s*)?\[(?: |x|X|~|!|-)\]\s+\S/m.test(text || '');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function hasVerdict(text) {
|
|
46
|
+
return /(?:^|\n)\s*(?:✅|⚠️|❌)\s+\S/m.test(text || '');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function block(reason, details = {}) {
|
|
50
|
+
hookLog('rdc-output-contract-gate', 'Stop', 'block', details);
|
|
51
|
+
process.stdout.write(JSON.stringify({ decision: 'block', reason }));
|
|
52
|
+
process.exit(0);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function pass(marker) {
|
|
56
|
+
try { fs.unlinkSync(marker.path); } catch {}
|
|
57
|
+
hookLog('rdc-output-contract-gate', 'Stop', 'pass', {
|
|
58
|
+
command: marker.data.command || null,
|
|
59
|
+
});
|
|
60
|
+
process.exit(0);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function main() {
|
|
64
|
+
let raw;
|
|
65
|
+
try { raw = JSON.parse(await readStdin()); } catch { process.exit(0); }
|
|
66
|
+
const marker = readMarker(raw.session_id);
|
|
67
|
+
if (!marker) process.exit(0);
|
|
68
|
+
|
|
69
|
+
const message = String(raw.last_assistant_message || '');
|
|
70
|
+
const checklist = hasChecklist(message);
|
|
71
|
+
const verdict = hasVerdict(message);
|
|
72
|
+
if (checklist && verdict) pass(marker);
|
|
73
|
+
|
|
74
|
+
const command = marker.data.command || 'rdc';
|
|
75
|
+
const missing = [];
|
|
76
|
+
if (!checklist) missing.push('a visible checklist row like `[ ] Step` or `[x] Step`');
|
|
77
|
+
if (!verdict) missing.push('a final verdict line beginning with ✅, ⚠️, or ❌');
|
|
78
|
+
|
|
79
|
+
block(
|
|
80
|
+
`RDC output contract incomplete for /${command}: missing ${missing.join(' and ')}. Continue the response by rendering the RDC checklist and verdict required by .rdc/guides/output-contract.md. Do not restart the task; correct the visible output contract.`,
|
|
81
|
+
{ command, checklist, verdict, stop_hook_active: raw.stop_hook_active === true },
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
main().catch((e) => block(`RDC output contract gate crashed: ${e.message}`));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lifeaitools/rdc-skills",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.36",
|
|
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,7 +19,7 @@
|
|
|
19
19
|
"type": "plugin",
|
|
20
20
|
"skills": "skills/",
|
|
21
21
|
"guides": "guides/",
|
|
22
|
-
"version": "0.9.
|
|
22
|
+
"version": "0.9.36",
|
|
23
23
|
"commands": "commands/"
|
|
24
24
|
},
|
|
25
25
|
"scripts": {
|
|
@@ -5,9 +5,12 @@
|
|
|
5
5
|
* Usage:
|
|
6
6
|
* node scripts/install-rdc-skills.js ← standard
|
|
7
7
|
* node scripts/install-rdc-skills.js --skip-hooks ← skip hook wiring
|
|
8
|
+
* node scripts/install-rdc-skills.js --profile core ← clean-box portable hooks
|
|
9
|
+
* node scripts/install-rdc-skills.js --profile lifeai ← LIFEAI/regen-root hooks
|
|
8
10
|
* node scripts/install-rdc-skills.js --claude-home <path> ← custom CLI home
|
|
9
11
|
* node scripts/install-rdc-skills.js --codex-root <path> ← also install to .agents/skills/user/
|
|
10
12
|
* node scripts/install-rdc-skills.js --codex-skill-dir <path> ← also install to a Codex skill dir
|
|
13
|
+
* node scripts/install-rdc-skills.js --project-root <path> --write-startup-blocks
|
|
11
14
|
* node scripts/install-rdc-skills.js --migrate <path> ← migrate docs/ → .rdc/
|
|
12
15
|
*
|
|
13
16
|
* What it does:
|
|
@@ -32,11 +35,16 @@ const { execSync } = require('child_process');
|
|
|
32
35
|
// ── Args ──────────────────────────────────────────────────────────────────────
|
|
33
36
|
const args = process.argv.slice(2);
|
|
34
37
|
const skipHooks = args.includes('--skip-hooks');
|
|
38
|
+
const profileIdx = args.indexOf('--profile');
|
|
39
|
+
const profileArg = profileIdx >= 0 ? String(args[profileIdx + 1] || '').toLowerCase() : 'auto';
|
|
35
40
|
const homeIdx = args.indexOf('--claude-home');
|
|
36
41
|
const claudeHome = homeIdx >= 0 ? args[homeIdx + 1] : path.join(os.homedir(), '.claude');
|
|
37
42
|
const migrateIdx = args.indexOf('--migrate');
|
|
38
43
|
const doMigrate = migrateIdx >= 0;
|
|
39
44
|
const migratePath = doMigrate ? (args[migrateIdx + 1] || process.cwd()) : null;
|
|
45
|
+
const projectIdx = args.indexOf('--project-root');
|
|
46
|
+
const projectRootArg = projectIdx >= 0 ? path.resolve(args[projectIdx + 1]) : null;
|
|
47
|
+
const shouldWriteStartupBlocks = args.includes('--write-startup-blocks');
|
|
40
48
|
|
|
41
49
|
const repoRoot = path.resolve(__dirname, '..');
|
|
42
50
|
|
|
@@ -44,6 +52,7 @@ const codexIdx = args.indexOf('--codex-root');
|
|
|
44
52
|
const codexRoot = codexIdx >= 0
|
|
45
53
|
? path.resolve(args[codexIdx + 1])
|
|
46
54
|
: (() => {
|
|
55
|
+
if (projectRootArg) return null;
|
|
47
56
|
const sibling = path.resolve(repoRoot, '..', 'regen-root');
|
|
48
57
|
return fs.existsSync(path.join(sibling, '.agents')) ? sibling : null;
|
|
49
58
|
})();
|
|
@@ -54,6 +63,18 @@ const explicitCodexSkillDir = codexSkillDirIdx >= 0
|
|
|
54
63
|
const hooksSrc = path.join(repoRoot, 'hooks');
|
|
55
64
|
const hooksDst = path.join(claudeHome, 'hooks');
|
|
56
65
|
const settingsPath = path.join(claudeHome, 'settings.json');
|
|
66
|
+
const detectedLifeaiRoot = (() => {
|
|
67
|
+
const sibling = path.resolve(repoRoot, '..', 'regen-root');
|
|
68
|
+
return fs.existsSync(path.join(sibling, 'CLAUDE.md')) && fs.existsSync(path.join(sibling, '.rdc')) ? sibling : null;
|
|
69
|
+
})();
|
|
70
|
+
const installProfile = (() => {
|
|
71
|
+
if (profileArg === 'core' || profileArg === 'lifeai') return profileArg;
|
|
72
|
+
if (profileArg !== 'auto') {
|
|
73
|
+
console.log(` \x1b[33m⚠\x1b[0m Unknown --profile "${profileArg}" — using auto`);
|
|
74
|
+
}
|
|
75
|
+
return detectedLifeaiRoot ? 'lifeai' : 'core';
|
|
76
|
+
})();
|
|
77
|
+
const projectRoot = projectRootArg || codexRoot || detectedLifeaiRoot;
|
|
57
78
|
|
|
58
79
|
const PLUGIN_KEY = 'rdc-skills@rdc-skills';
|
|
59
80
|
const MARKETPLACE = 'rdc-skills';
|
|
@@ -108,6 +129,7 @@ function copyMissingProjectGuides(projectRoot) {
|
|
|
108
129
|
let copied = 0;
|
|
109
130
|
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
110
131
|
if (!entry.isFile() || !entry.name.endsWith('.md')) continue;
|
|
132
|
+
if (entry.name === 'rdc-skills-startup.md') continue; // installed only by --write-startup-blocks
|
|
111
133
|
const target = path.join(dst, entry.name);
|
|
112
134
|
if (fs.existsSync(target)) continue;
|
|
113
135
|
fs.copyFileSync(path.join(src, entry.name), target);
|
|
@@ -126,6 +148,53 @@ function writeJson(p, data, indent = 2) {
|
|
|
126
148
|
fs.writeFileSync(p, JSON.stringify(data, null, indent));
|
|
127
149
|
}
|
|
128
150
|
|
|
151
|
+
function upsertManagedBlock(filePath, title, body) {
|
|
152
|
+
const begin = `<!-- BEGIN RDC-SKILLS:${title} -->`;
|
|
153
|
+
const end = `<!-- END RDC-SKILLS:${title} -->`;
|
|
154
|
+
const block = `${begin}\n${body.trim()}\n${end}\n`;
|
|
155
|
+
const current = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf8') : '';
|
|
156
|
+
const re = new RegExp(`${begin.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}[\\s\\S]*?${end.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\n?`, 'm');
|
|
157
|
+
const next = re.test(current)
|
|
158
|
+
? current.replace(re, block)
|
|
159
|
+
: `${current.replace(/\s*$/, '')}${current.trim() ? '\n\n' : ''}${block}`;
|
|
160
|
+
if (next !== current) {
|
|
161
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
162
|
+
fs.writeFileSync(filePath, next);
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function writeStartupBlocks(targetRoot, profile) {
|
|
169
|
+
if (!targetRoot) return { wrote: 0, skipped: 'no-project-root' };
|
|
170
|
+
const startupSrc = path.join(repoRoot, 'guides', 'rdc-skills-startup.md');
|
|
171
|
+
const guidesDir = path.join(targetRoot, '.rdc', 'guides');
|
|
172
|
+
const startupDst = path.join(guidesDir, 'rdc-skills-startup.md');
|
|
173
|
+
fs.mkdirSync(guidesDir, { recursive: true });
|
|
174
|
+
fs.copyFileSync(startupSrc, startupDst);
|
|
175
|
+
|
|
176
|
+
let wrote = 1;
|
|
177
|
+
const claudeBody = [
|
|
178
|
+
'## RDC Skills',
|
|
179
|
+
'',
|
|
180
|
+
'@.rdc/guides/rdc-skills-startup.md',
|
|
181
|
+
'',
|
|
182
|
+
`Installed profile: \`${profile}\`.`,
|
|
183
|
+
'For `/rdc:*` work, follow `.rdc/guides/output-contract.md` and `.rdc/guides/engineering-behavior.md`.',
|
|
184
|
+
].join('\n');
|
|
185
|
+
const agentsBody = [
|
|
186
|
+
'## RDC Skills',
|
|
187
|
+
'',
|
|
188
|
+
'Read `.rdc/guides/rdc-skills-startup.md` before using any `rdc:*` workflow.',
|
|
189
|
+
`Installed profile: \`${profile}\`.`,
|
|
190
|
+
'For `/rdc:*` work, follow `.rdc/guides/output-contract.md` and `.rdc/guides/engineering-behavior.md`.',
|
|
191
|
+
].join('\n');
|
|
192
|
+
|
|
193
|
+
if (upsertManagedBlock(path.join(targetRoot, 'CLAUDE.md'), 'STARTUP', claudeBody)) wrote++;
|
|
194
|
+
if (upsertManagedBlock(path.join(targetRoot, 'AGENTS.md'), 'STARTUP', agentsBody)) wrote++;
|
|
195
|
+
return { wrote, skipped: null };
|
|
196
|
+
}
|
|
197
|
+
|
|
129
198
|
// ── Frontmatter parser ────────────────────────────────────────────────────────
|
|
130
199
|
function readFrontmatter(filePath) {
|
|
131
200
|
try {
|
|
@@ -546,7 +615,7 @@ function buildZip(version) {
|
|
|
546
615
|
}
|
|
547
616
|
|
|
548
617
|
// ── Hook config ───────────────────────────────────────────────────────────────
|
|
549
|
-
function buildHooksConfig(hooksDir) {
|
|
618
|
+
function buildHooksConfig(hooksDir, profile = 'core') {
|
|
550
619
|
const base = hooksDir.replace(/\\/g, '/');
|
|
551
620
|
const cmd = (file, msg) => {
|
|
552
621
|
const command = process.platform === 'win32'
|
|
@@ -556,15 +625,16 @@ function buildHooksConfig(hooksDir) {
|
|
|
556
625
|
if (msg) entry.statusMessage = msg;
|
|
557
626
|
return entry;
|
|
558
627
|
};
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
cmd('
|
|
562
|
-
|
|
628
|
+
const config = {
|
|
629
|
+
UserPromptExpansion: [{ hooks: [
|
|
630
|
+
cmd('rdc-invocation-marker.js', 'Marking RDC slash command...'),
|
|
631
|
+
]}],
|
|
632
|
+
UserPromptSubmit: [{ hooks: [
|
|
633
|
+
cmd('rdc-invocation-marker.js', 'Marking RDC prompt...'),
|
|
563
634
|
]}],
|
|
564
635
|
PreToolUse: [
|
|
565
636
|
{ hooks: [
|
|
566
637
|
cmd('foreground-process-gate.js', 'Checking foreground process policy...'),
|
|
567
|
-
cmd('work-item-exit-gate.js', 'Checking work item exit gates...'),
|
|
568
638
|
]},
|
|
569
639
|
{ matcher: 'Bash', hooks: [
|
|
570
640
|
cmd('require-work-item-on-commit.js'),
|
|
@@ -573,19 +643,36 @@ function buildHooksConfig(hooksDir) {
|
|
|
573
643
|
PostToolUse: [{ hooks: [
|
|
574
644
|
cmd('check-services.js'),
|
|
575
645
|
]}],
|
|
576
|
-
PreCompact: [{ hooks: [
|
|
577
|
-
cmd('precompact-log.js'),
|
|
578
|
-
]}],
|
|
579
|
-
PostCompact: [{ hooks: [
|
|
580
|
-
cmd('postcompact-log.js'),
|
|
581
|
-
cmd('restart-brief.js', 'Writing restart brief...'),
|
|
582
|
-
]}],
|
|
583
646
|
Stop: [{ hooks: [
|
|
584
|
-
cmd('
|
|
647
|
+
cmd('rdc-output-contract-gate.js', 'Checking RDC output contract...'),
|
|
585
648
|
cmd('post-work-check.js', 'Checking for undocumented work...'),
|
|
586
|
-
cmd('no-stop-open-epics.js', 'Checking for open epics...'),
|
|
587
649
|
]}],
|
|
588
650
|
};
|
|
651
|
+
|
|
652
|
+
if (profile === 'lifeai') {
|
|
653
|
+
config.SessionStart = [{ hooks: [
|
|
654
|
+
cmd('check-cwd.js'),
|
|
655
|
+
cmd('check-stale-work-items.js', 'Checking for stale work items...'),
|
|
656
|
+
]}];
|
|
657
|
+
config.PreToolUse[0].hooks.push(
|
|
658
|
+
cmd('work-item-exit-gate.js', 'Checking work item exit gates...'),
|
|
659
|
+
);
|
|
660
|
+
config.PreCompact = [{ hooks: [
|
|
661
|
+
cmd('precompact-log.js'),
|
|
662
|
+
]}];
|
|
663
|
+
config.PostCompact = [{ hooks: [
|
|
664
|
+
cmd('postcompact-log.js'),
|
|
665
|
+
cmd('restart-brief.js', 'Writing restart brief...'),
|
|
666
|
+
]}];
|
|
667
|
+
config.Stop[0].hooks.unshift(
|
|
668
|
+
cmd('rate-limit-retry.js', 'Checking for rate limits...'),
|
|
669
|
+
);
|
|
670
|
+
config.Stop[0].hooks.push(
|
|
671
|
+
cmd('no-stop-open-epics.js', 'Checking for open epics...'),
|
|
672
|
+
);
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
return config;
|
|
589
676
|
}
|
|
590
677
|
|
|
591
678
|
// ── Preflight ─────────────────────────────────────────────────────────────────
|
|
@@ -675,6 +762,7 @@ async function main() {
|
|
|
675
762
|
console.log('');
|
|
676
763
|
console.log(` CLAUDE_HOME : ${claudeHome}`);
|
|
677
764
|
console.log(` Plugin root : ${repoRoot}`);
|
|
765
|
+
console.log(` Profile : ${installProfile}${profileArg === 'auto' ? ' (auto)' : ''}`);
|
|
678
766
|
console.log('');
|
|
679
767
|
|
|
680
768
|
if (!fs.existsSync(claudeHome)) {
|
|
@@ -815,11 +903,20 @@ async function main() {
|
|
|
815
903
|
info('[4/6] Hook wiring — skipped (--skip-hooks)');
|
|
816
904
|
} else {
|
|
817
905
|
const settings = readJson(settingsPath);
|
|
818
|
-
settings.hooks = buildHooksConfig(hooksDst);
|
|
906
|
+
settings.hooks = buildHooksConfig(hooksDst, installProfile);
|
|
819
907
|
writeJson(settingsPath, settings);
|
|
820
908
|
ok(`[4/6] Hook wiring — ${settingsPath}`);
|
|
821
909
|
}
|
|
822
910
|
|
|
911
|
+
// 4.5 Optional startup blocks
|
|
912
|
+
if (shouldWriteStartupBlocks) {
|
|
913
|
+
const startup = writeStartupBlocks(projectRoot, installProfile);
|
|
914
|
+
if (startup.skipped) warn(`[4.5] Startup — skipped (${startup.skipped})`);
|
|
915
|
+
else ok(`[4.5] Startup — wrote managed startup guide/block(s) under ${projectRoot}`);
|
|
916
|
+
} else {
|
|
917
|
+
info('[4.5] Startup — skipped (use --project-root <path> --write-startup-blocks)');
|
|
918
|
+
}
|
|
919
|
+
|
|
823
920
|
// 5. Zip for claude.ai / distribution
|
|
824
921
|
const zipPath = buildZip(version);
|
|
825
922
|
if (zipPath) {
|
|
@@ -890,6 +987,15 @@ async function main() {
|
|
|
890
987
|
console.log(' Cowork : restart Claude Desktop — /rdc:status in a new Cowork session');
|
|
891
988
|
console.log(' claude.ai : no plugin install needed — use FS MCP to read commands on demand');
|
|
892
989
|
console.log(' dist/rdc-skills-plugin-v' + version + '.zip available if needed');
|
|
990
|
+
console.log('');
|
|
991
|
+
console.log(` Profile: ${installProfile}`);
|
|
992
|
+
if (installProfile === 'core') {
|
|
993
|
+
console.log(' Core hooks are portable: RDC output contract + foreground process + commit-message hygiene.');
|
|
994
|
+
console.log(' Project services are not provisioned. Configure work items, credentials, deploys, and project guides before using infrastructure-heavy skills.');
|
|
995
|
+
} else {
|
|
996
|
+
console.log(' LIFEAI hooks are active: regen-root cwd lock, Supabase work-item gates, clauth-aware checks, and overnight queue guard.');
|
|
997
|
+
}
|
|
998
|
+
console.log(' Startup blocks: run with --project-root <path> --write-startup-blocks to add managed CLAUDE.md/AGENTS.md sections.');
|
|
893
999
|
|
|
894
1000
|
listCommands();
|
|
895
1001
|
|
package/scripts/install.ps1
CHANGED
|
@@ -7,10 +7,14 @@
|
|
|
7
7
|
# Usage:
|
|
8
8
|
# ./install.ps1 # standard install
|
|
9
9
|
# ./install.ps1 -SkipHooks # skip hooks registration (e.g. if you manage hooks manually)
|
|
10
|
+
# ./install.ps1 -Profile core # portable hooks only
|
|
11
|
+
# ./install.ps1 -Profile lifeai # LIFEAI/regen-root hooks
|
|
10
12
|
# ./install.ps1 -ClaudeHome <path> # custom CLAUDE_HOME
|
|
11
13
|
|
|
12
14
|
param(
|
|
13
15
|
[string]$ClaudeHome = "",
|
|
16
|
+
[ValidateSet("auto", "core", "lifeai")]
|
|
17
|
+
[string]$Profile = "auto",
|
|
14
18
|
[switch]$SkipHooks = $false,
|
|
15
19
|
[switch]$Force = $false
|
|
16
20
|
)
|
|
@@ -22,6 +26,12 @@ if (-not $ClaudeHome) {
|
|
|
22
26
|
|
|
23
27
|
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
24
28
|
$repoRoot = Split-Path -Parent $scriptDir
|
|
29
|
+
$lifeaiRoot = Join-Path (Split-Path -Parent $repoRoot) "regen-root"
|
|
30
|
+
$installProfile = if ($Profile -eq "auto") {
|
|
31
|
+
if ((Test-Path (Join-Path $lifeaiRoot "CLAUDE.md")) -and (Test-Path (Join-Path $lifeaiRoot ".rdc"))) { "lifeai" } else { "core" }
|
|
32
|
+
} else {
|
|
33
|
+
$Profile
|
|
34
|
+
}
|
|
25
35
|
|
|
26
36
|
Write-Host ""
|
|
27
37
|
Write-Host " rdc-skills Installer" -ForegroundColor Green
|
|
@@ -29,6 +39,7 @@ Write-Host " ====================" -ForegroundColor Green
|
|
|
29
39
|
Write-Host ""
|
|
30
40
|
Write-Host " CLAUDE_HOME : $ClaudeHome" -ForegroundColor Cyan
|
|
31
41
|
Write-Host " Plugin root : $repoRoot" -ForegroundColor Cyan
|
|
42
|
+
Write-Host " Profile : $installProfile$(if ($Profile -eq 'auto') { ' (auto)' } else { '' })" -ForegroundColor Cyan
|
|
32
43
|
Write-Host ""
|
|
33
44
|
|
|
34
45
|
if (-not (Test-Path $ClaudeHome)) {
|
|
@@ -82,19 +93,24 @@ if ($SkipHooks) {
|
|
|
82
93
|
$hooksBase = $hooksDir.Replace("\", "/")
|
|
83
94
|
|
|
84
95
|
$hooksConfig = [PSCustomObject]@{
|
|
85
|
-
|
|
96
|
+
UserPromptExpansion = @(
|
|
86
97
|
[PSCustomObject]@{
|
|
87
98
|
hooks = @(
|
|
88
|
-
[PSCustomObject]@{ type = "command"; command = "powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$hooksBase/run-hidden-hook.ps1`" `"$hooksBase/
|
|
89
|
-
|
|
99
|
+
[PSCustomObject]@{ type = "command"; command = "powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$hooksBase/run-hidden-hook.ps1`" `"$hooksBase/rdc-invocation-marker.js`""; statusMessage = "Marking RDC slash command..." }
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
)
|
|
103
|
+
UserPromptSubmit = @(
|
|
104
|
+
[PSCustomObject]@{
|
|
105
|
+
hooks = @(
|
|
106
|
+
[PSCustomObject]@{ type = "command"; command = "powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$hooksBase/run-hidden-hook.ps1`" `"$hooksBase/rdc-invocation-marker.js`""; statusMessage = "Marking RDC prompt..." }
|
|
90
107
|
)
|
|
91
108
|
}
|
|
92
109
|
)
|
|
93
110
|
PreToolUse = @(
|
|
94
111
|
[PSCustomObject]@{
|
|
95
112
|
hooks = @(
|
|
96
|
-
[PSCustomObject]@{ type = "command"; command = "powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$hooksBase/run-hidden-hook.ps1`" `"$hooksBase/foreground-process-gate.js`""; statusMessage = "Checking foreground process policy..." }
|
|
97
|
-
[PSCustomObject]@{ type = "command"; command = "powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$hooksBase/run-hidden-hook.ps1`" `"$hooksBase/work-item-exit-gate.js`""; statusMessage = "Checking work item exit gates..." }
|
|
113
|
+
[PSCustomObject]@{ type = "command"; command = "powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$hooksBase/run-hidden-hook.ps1`" `"$hooksBase/foreground-process-gate.js`""; statusMessage = "Checking foreground process policy..." }
|
|
98
114
|
)
|
|
99
115
|
},
|
|
100
116
|
[PSCustomObject]@{
|
|
@@ -111,30 +127,42 @@ if ($SkipHooks) {
|
|
|
111
127
|
)
|
|
112
128
|
}
|
|
113
129
|
)
|
|
114
|
-
|
|
130
|
+
Stop = @(
|
|
115
131
|
[PSCustomObject]@{
|
|
116
132
|
hooks = @(
|
|
117
|
-
[PSCustomObject]@{ type = "command"; command = "powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$hooksBase/run-hidden-hook.ps1`" `"$hooksBase/
|
|
133
|
+
[PSCustomObject]@{ type = "command"; command = "powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$hooksBase/run-hidden-hook.ps1`" `"$hooksBase/rdc-output-contract-gate.js`""; statusMessage = "Checking RDC output contract..." },
|
|
134
|
+
[PSCustomObject]@{ type = "command"; command = "powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$hooksBase/run-hidden-hook.ps1`" `"$hooksBase/post-work-check.js`""; statusMessage = "Checking for undocumented work..." }
|
|
118
135
|
)
|
|
119
136
|
}
|
|
120
137
|
)
|
|
121
|
-
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if ($installProfile -eq "lifeai") {
|
|
141
|
+
$hooksConfig | Add-Member -NotePropertyName "SessionStart" -NotePropertyValue @(
|
|
122
142
|
[PSCustomObject]@{
|
|
123
143
|
hooks = @(
|
|
124
|
-
[PSCustomObject]@{ type = "command"; command = "powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$hooksBase/run-hidden-hook.ps1`" `"$hooksBase/
|
|
125
|
-
[PSCustomObject]@{ type = "command"; command = "powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$hooksBase/run-hidden-hook.ps1`" `"$hooksBase/
|
|
144
|
+
[PSCustomObject]@{ type = "command"; command = "powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$hooksBase/run-hidden-hook.ps1`" `"$hooksBase/check-cwd.js`"" },
|
|
145
|
+
[PSCustomObject]@{ type = "command"; command = "powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$hooksBase/run-hidden-hook.ps1`" `"$hooksBase/check-stale-work-items.js`""; statusMessage = "Checking for stale work items..." }
|
|
126
146
|
)
|
|
127
147
|
}
|
|
128
148
|
)
|
|
129
|
-
|
|
149
|
+
$hooksConfig.PreToolUse[0].hooks += [PSCustomObject]@{ type = "command"; command = "powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$hooksBase/run-hidden-hook.ps1`" `"$hooksBase/work-item-exit-gate.js`""; statusMessage = "Checking work item exit gates..." }
|
|
150
|
+
$hooksConfig | Add-Member -NotePropertyName "PreCompact" -NotePropertyValue @(
|
|
151
|
+
[PSCustomObject]@{ hooks = @([PSCustomObject]@{ type = "command"; command = "powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$hooksBase/run-hidden-hook.ps1`" `"$hooksBase/precompact-log.js`"" }) }
|
|
152
|
+
)
|
|
153
|
+
$hooksConfig | Add-Member -NotePropertyName "PostCompact" -NotePropertyValue @(
|
|
130
154
|
[PSCustomObject]@{
|
|
131
155
|
hooks = @(
|
|
132
|
-
[PSCustomObject]@{ type = "command"; command = "powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$hooksBase/run-hidden-hook.ps1`" `"$hooksBase/
|
|
133
|
-
[PSCustomObject]@{ type = "command"; command = "powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$hooksBase/run-hidden-hook.ps1`" `"$hooksBase/
|
|
134
|
-
[PSCustomObject]@{ type = "command"; command = "powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$hooksBase/run-hidden-hook.ps1`" `"$hooksBase/no-stop-open-epics.js`""; statusMessage = "Checking for open epics..." }
|
|
156
|
+
[PSCustomObject]@{ type = "command"; command = "powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$hooksBase/run-hidden-hook.ps1`" `"$hooksBase/postcompact-log.js`"" },
|
|
157
|
+
[PSCustomObject]@{ type = "command"; command = "powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$hooksBase/run-hidden-hook.ps1`" `"$hooksBase/restart-brief.js`""; statusMessage = "Writing restart brief..." }
|
|
135
158
|
)
|
|
136
159
|
}
|
|
137
160
|
)
|
|
161
|
+
$hooksConfig.Stop[0].hooks = @(
|
|
162
|
+
[PSCustomObject]@{ type = "command"; command = "powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$hooksBase/run-hidden-hook.ps1`" `"$hooksBase/rate-limit-retry.js`""; statusMessage = "Checking for rate limits..." }
|
|
163
|
+
) + $hooksConfig.Stop[0].hooks + @(
|
|
164
|
+
[PSCustomObject]@{ type = "command"; command = "powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$hooksBase/run-hidden-hook.ps1`" `"$hooksBase/no-stop-open-epics.js`""; statusMessage = "Checking for open epics..." }
|
|
165
|
+
)
|
|
138
166
|
}
|
|
139
167
|
|
|
140
168
|
# Add/replace hooks key
|
|
@@ -148,6 +176,8 @@ if ($SkipHooks) {
|
|
|
148
176
|
$settings | ConvertTo-Json -Depth 20 | Set-Content $settingsPath -Encoding UTF8
|
|
149
177
|
Write-Host " [3/3] Hook wiring ✓ registered in $settingsPath" -ForegroundColor Green
|
|
150
178
|
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
151
181
|
|
|
152
182
|
# ── Summary ───────────────────────────────────────────────────────────────────
|
|
153
183
|
|
|
@@ -169,3 +199,4 @@ Write-Host " 4. Run /rdc:status in Claude Code to verify"
|
|
|
169
199
|
Write-Host ""
|
|
170
200
|
Write-Host " Docs: https://github.com/LIFEAI/rdc-skills#readme" -ForegroundColor Cyan
|
|
171
201
|
Write-Host ""
|
|
202
|
+
}
|
package/scripts/self-test.mjs
CHANGED
|
@@ -699,6 +699,7 @@ function auditOrphanHooks(results) {
|
|
|
699
699
|
})
|
|
700
700
|
.map((f) => join(SKILLS_DIR, f, "SKILL.md")),
|
|
701
701
|
join(REPO_ROOT, ".claude", "settings.json"),
|
|
702
|
+
join(REPO_ROOT, "scripts", "install-rdc-skills.js"),
|
|
702
703
|
PLUGIN_MANIFEST,
|
|
703
704
|
];
|
|
704
705
|
for (const src of sources) {
|
|
@@ -729,6 +730,78 @@ function auditOrphanHooks(results) {
|
|
|
729
730
|
return findings;
|
|
730
731
|
}
|
|
731
732
|
|
|
733
|
+
function auditRequiredHookWiring() {
|
|
734
|
+
const findings = [];
|
|
735
|
+
const required = [
|
|
736
|
+
{
|
|
737
|
+
file: join(REPO_ROOT, "scripts", "install-rdc-skills.js"),
|
|
738
|
+
label: "scripts/install-rdc-skills.js",
|
|
739
|
+
tokens: [
|
|
740
|
+
"UserPromptExpansion",
|
|
741
|
+
"UserPromptSubmit",
|
|
742
|
+
"Stop",
|
|
743
|
+
"rdc-invocation-marker.js",
|
|
744
|
+
"rdc-output-contract-gate.js",
|
|
745
|
+
],
|
|
746
|
+
},
|
|
747
|
+
{
|
|
748
|
+
file: join(REPO_ROOT, "scripts", "install.ps1"),
|
|
749
|
+
label: "scripts/install.ps1",
|
|
750
|
+
tokens: [
|
|
751
|
+
"UserPromptExpansion",
|
|
752
|
+
"UserPromptSubmit",
|
|
753
|
+
"Stop",
|
|
754
|
+
"rdc-invocation-marker.js",
|
|
755
|
+
"rdc-output-contract-gate.js",
|
|
756
|
+
],
|
|
757
|
+
},
|
|
758
|
+
];
|
|
759
|
+
|
|
760
|
+
for (const hookFile of [
|
|
761
|
+
"rdc-invocation-marker.js",
|
|
762
|
+
"rdc-output-contract-gate.js",
|
|
763
|
+
]) {
|
|
764
|
+
if (!existsSync(join(HOOKS_DIR, hookFile))) {
|
|
765
|
+
findings.push({
|
|
766
|
+
level: "error",
|
|
767
|
+
code: "required-hook-missing",
|
|
768
|
+
message: `hooks/${hookFile} is required for RDC output-contract enforcement`,
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
for (const target of required) {
|
|
774
|
+
if (!existsSync(target.file)) {
|
|
775
|
+
findings.push({
|
|
776
|
+
level: "error",
|
|
777
|
+
code: "hook-installer-missing",
|
|
778
|
+
message: `${target.label} not found for RDC hook wiring audit`,
|
|
779
|
+
});
|
|
780
|
+
continue;
|
|
781
|
+
}
|
|
782
|
+
let text = "";
|
|
783
|
+
try { text = readFileSync(target.file, "utf8"); } catch (e) {
|
|
784
|
+
findings.push({
|
|
785
|
+
level: "error",
|
|
786
|
+
code: "hook-installer-unreadable",
|
|
787
|
+
message: `${target.label} unreadable: ${e.message}`,
|
|
788
|
+
});
|
|
789
|
+
continue;
|
|
790
|
+
}
|
|
791
|
+
for (const token of target.tokens) {
|
|
792
|
+
if (!text.includes(token)) {
|
|
793
|
+
findings.push({
|
|
794
|
+
level: "error",
|
|
795
|
+
code: "required-hook-wiring-missing",
|
|
796
|
+
message: `${target.label} must reference ${token}`,
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
return findings;
|
|
803
|
+
}
|
|
804
|
+
|
|
732
805
|
// ─── Tier 2 behavioral runner ──────────────────────────────────────────────
|
|
733
806
|
|
|
734
807
|
async function runPool(items, concurrency, worker) {
|
|
@@ -1143,14 +1216,16 @@ function main() {
|
|
|
1143
1216
|
// Cross-skill checks — deferred until all audits complete
|
|
1144
1217
|
let duplicateFindings = [];
|
|
1145
1218
|
let orphanHookFindings = [];
|
|
1219
|
+
let requiredHookFindings = [];
|
|
1146
1220
|
if (!ONLY_SKILL) {
|
|
1147
1221
|
duplicateFindings = auditDuplicates(results);
|
|
1148
1222
|
orphanHookFindings = auditOrphanHooks(results);
|
|
1223
|
+
requiredHookFindings = auditRequiredHookWiring();
|
|
1149
1224
|
}
|
|
1150
1225
|
|
|
1151
|
-
if (duplicateFindings.length + orphanHookFindings.length > 0) {
|
|
1226
|
+
if (duplicateFindings.length + orphanHookFindings.length + requiredHookFindings.length > 0) {
|
|
1152
1227
|
console.log("\nglobal findings\n");
|
|
1153
|
-
for (const f of [...duplicateFindings, ...orphanHookFindings]) {
|
|
1228
|
+
for (const f of [...duplicateFindings, ...orphanHookFindings, ...requiredHookFindings]) {
|
|
1154
1229
|
console.log(` [${f.level}] ${f.code}: ${f.message}`);
|
|
1155
1230
|
}
|
|
1156
1231
|
}
|
|
@@ -1166,12 +1241,14 @@ function main() {
|
|
|
1166
1241
|
...manifestAudit.findings.filter((f) => f.level === "error"),
|
|
1167
1242
|
...duplicateFindings.filter((f) => f.level === "error"),
|
|
1168
1243
|
...orphanHookFindings.filter((f) => f.level === "error"),
|
|
1244
|
+
...requiredHookFindings.filter((f) => f.level === "error"),
|
|
1169
1245
|
...(guideValidatorResult ? guideValidatorResult.errors : []),
|
|
1170
1246
|
];
|
|
1171
1247
|
const globalWarnings = [
|
|
1172
1248
|
...manifestAudit.findings.filter((f) => f.level === "warn"),
|
|
1173
1249
|
...duplicateFindings.filter((f) => f.level === "warn"),
|
|
1174
1250
|
...orphanHookFindings.filter((f) => f.level === "warn"),
|
|
1251
|
+
...requiredHookFindings.filter((f) => f.level === "warn"),
|
|
1175
1252
|
...(guideValidatorResult ? guideValidatorResult.warnings : []),
|
|
1176
1253
|
];
|
|
1177
1254
|
|