@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
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Behavioral smoke tests for the RDC marker and Stop output-contract hooks.
|
|
3
|
+
|
|
4
|
+
import { mkdtempSync, rmSync, existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { join, resolve, dirname } from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { spawnSync } from "node:child_process";
|
|
9
|
+
|
|
10
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
const REPO_ROOT = resolve(__dirname, "..");
|
|
12
|
+
const MARKER_HOOK = join(REPO_ROOT, "hooks", "rdc-invocation-marker.js");
|
|
13
|
+
const STOP_HOOK = join(REPO_ROOT, "hooks", "rdc-output-contract-gate.js");
|
|
14
|
+
|
|
15
|
+
const tmpHome = mkdtempSync(join(tmpdir(), "rdc-hooks-"));
|
|
16
|
+
const env = { ...process.env, HOME: tmpHome, USERPROFILE: tmpHome };
|
|
17
|
+
const failures = [];
|
|
18
|
+
|
|
19
|
+
function markerPath(sessionId) {
|
|
20
|
+
return join(tmpHome, ".claude", "rdc-active", `${sessionId}.json`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function runHook(script, payload) {
|
|
24
|
+
return spawnSync(process.execPath, [script], {
|
|
25
|
+
input: JSON.stringify(payload),
|
|
26
|
+
encoding: "utf8",
|
|
27
|
+
env,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function assert(name, condition, detail = "") {
|
|
32
|
+
if (!condition) failures.push(`${name}${detail ? `: ${detail}` : ""}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function readMarker(sessionId) {
|
|
36
|
+
return JSON.parse(readFileSync(markerPath(sessionId), "utf8"));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const expansion = runHook(MARKER_HOOK, {
|
|
41
|
+
hook_event_name: "UserPromptExpansion",
|
|
42
|
+
session_id: "s-expansion",
|
|
43
|
+
command_source: "plugin",
|
|
44
|
+
command_name: "rdc:design",
|
|
45
|
+
prompt: "/rdc:design palette audit",
|
|
46
|
+
});
|
|
47
|
+
assert("expansion exits zero", expansion.status === 0, expansion.stderr);
|
|
48
|
+
assert("expansion emits context", /RDC CONTRACT ACTIVE/.test(expansion.stdout), expansion.stdout);
|
|
49
|
+
assert("expansion writes marker", existsSync(markerPath("s-expansion")));
|
|
50
|
+
assert("expansion marker event preserved", readMarker("s-expansion").hook_event_name === "UserPromptExpansion");
|
|
51
|
+
|
|
52
|
+
const builtin = runHook(MARKER_HOOK, {
|
|
53
|
+
hook_event_name: "UserPromptExpansion",
|
|
54
|
+
session_id: "s-builtin",
|
|
55
|
+
command_source: "builtin",
|
|
56
|
+
command_name: "help",
|
|
57
|
+
prompt: "/help",
|
|
58
|
+
});
|
|
59
|
+
assert("builtin exits zero", builtin.status === 0, builtin.stderr);
|
|
60
|
+
assert("builtin emits no context", builtin.stdout.trim() === "", builtin.stdout);
|
|
61
|
+
assert("builtin writes no marker", !existsSync(markerPath("s-builtin")));
|
|
62
|
+
|
|
63
|
+
const submit1 = runHook(MARKER_HOOK, {
|
|
64
|
+
hook_event_name: "UserPromptExpansion",
|
|
65
|
+
session_id: "s-dedup",
|
|
66
|
+
command_source: "plugin",
|
|
67
|
+
command_name: "rdc:design",
|
|
68
|
+
prompt: "/rdc:design button audit",
|
|
69
|
+
});
|
|
70
|
+
assert("dedup first mark exits zero", submit1.status === 0, submit1.stderr);
|
|
71
|
+
const first = readMarker("s-dedup");
|
|
72
|
+
const submit2 = runHook(MARKER_HOOK, {
|
|
73
|
+
hook_event_name: "UserPromptSubmit",
|
|
74
|
+
session_id: "s-dedup",
|
|
75
|
+
prompt: "/rdc:design button audit",
|
|
76
|
+
});
|
|
77
|
+
assert("dedup second mark exits zero", submit2.status === 0, submit2.stderr);
|
|
78
|
+
const second = readMarker("s-dedup");
|
|
79
|
+
assert("dedup preserves started_at", second.started_at === first.started_at);
|
|
80
|
+
assert("dedup preserves hook event", second.hook_event_name === "UserPromptExpansion");
|
|
81
|
+
|
|
82
|
+
const genericHelp = runHook(MARKER_HOOK, {
|
|
83
|
+
hook_event_name: "UserPromptSubmit",
|
|
84
|
+
session_id: "s-help",
|
|
85
|
+
prompt: "/help",
|
|
86
|
+
});
|
|
87
|
+
assert("generic help exits zero", genericHelp.status === 0, genericHelp.stderr);
|
|
88
|
+
assert("generic help writes no marker", !existsSync(markerPath("s-help")));
|
|
89
|
+
|
|
90
|
+
const noMarker = runHook(STOP_HOOK, {
|
|
91
|
+
hook_event_name: "Stop",
|
|
92
|
+
session_id: "s-no-marker",
|
|
93
|
+
last_assistant_message: "plain chat",
|
|
94
|
+
});
|
|
95
|
+
assert("stop without marker exits zero", noMarker.status === 0, noMarker.stderr);
|
|
96
|
+
assert("stop without marker is silent", noMarker.stdout.trim() === "", noMarker.stdout);
|
|
97
|
+
|
|
98
|
+
writeFileSync(markerPath("s-block"), JSON.stringify({
|
|
99
|
+
session_id: "s-block",
|
|
100
|
+
command: "design",
|
|
101
|
+
started_at: new Date().toISOString(),
|
|
102
|
+
}, null, 2));
|
|
103
|
+
const blocked = runHook(STOP_HOOK, {
|
|
104
|
+
hook_event_name: "Stop",
|
|
105
|
+
session_id: "s-block",
|
|
106
|
+
last_assistant_message: "No contract artifacts here.",
|
|
107
|
+
});
|
|
108
|
+
assert("noncompliant stop exits zero", blocked.status === 0, blocked.stderr);
|
|
109
|
+
assert("noncompliant stop blocks", /"decision"\s*:\s*"block"/.test(blocked.stdout), blocked.stdout);
|
|
110
|
+
assert("noncompliant stop retains marker", existsSync(markerPath("s-block")));
|
|
111
|
+
|
|
112
|
+
const reentry = runHook(STOP_HOOK, {
|
|
113
|
+
hook_event_name: "Stop",
|
|
114
|
+
session_id: "s-block",
|
|
115
|
+
stop_hook_active: true,
|
|
116
|
+
last_assistant_message: "Still no contract artifacts.",
|
|
117
|
+
});
|
|
118
|
+
assert("stop reentry exits zero", reentry.status === 0, reentry.stderr);
|
|
119
|
+
assert("stop reentry allows silent pass", reentry.stdout.trim() === "", reentry.stdout);
|
|
120
|
+
assert("stop reentry clears marker", !existsSync(markerPath("s-block")));
|
|
121
|
+
|
|
122
|
+
writeFileSync(markerPath("s-pass"), JSON.stringify({
|
|
123
|
+
session_id: "s-pass",
|
|
124
|
+
command: "review",
|
|
125
|
+
started_at: new Date().toISOString(),
|
|
126
|
+
}, null, 2));
|
|
127
|
+
const compliant = runHook(STOP_HOOK, {
|
|
128
|
+
hook_event_name: "Stop",
|
|
129
|
+
session_id: "s-pass",
|
|
130
|
+
last_assistant_message: "[x] Verified hook behavior\n\n✅ Complete in 2s",
|
|
131
|
+
});
|
|
132
|
+
assert("compliant stop exits zero", compliant.status === 0, compliant.stderr);
|
|
133
|
+
assert("compliant stop is silent", compliant.stdout.trim() === "", compliant.stdout);
|
|
134
|
+
assert("compliant stop clears marker", !existsSync(markerPath("s-pass")));
|
|
135
|
+
} finally {
|
|
136
|
+
rmSync(tmpHome, { recursive: true, force: true });
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (failures.length > 0) {
|
|
140
|
+
console.error("\nrdc hook behavior tests — FAIL\n");
|
|
141
|
+
for (const failure of failures) console.error(` - ${failure}`);
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
console.log("rdc hook behavior tests — PASS");
|
package/skills/build/SKILL.md
CHANGED
|
@@ -301,8 +301,8 @@ Read the task title and description, then:
|
|
|
301
301
|
⚠️ The validator does NOT use `isolation: "worktree"` — it must read the fully merged develop branch. Omit the isolation parameter for this dispatch only.
|
|
302
302
|
|
|
303
303
|
```
|
|
304
|
-
"Read
|
|
305
|
-
Read
|
|
304
|
+
"Read {PROJECT_ROOT}/.rdc/guides/agent-bootstrap.md then {PROJECT_ROOT}/.rdc/guides/verify.md.
|
|
305
|
+
Read {PROJECT_ROOT}/.rdc/guides/engineering-behavior.md before validating scope, deviations, and evidence.
|
|
306
306
|
Validate these work items: [list of IDs and titles].
|
|
307
307
|
Apps touched: [list].
|
|
308
308
|
Git diff since build start: [attach or reference].
|
|
@@ -110,7 +110,7 @@ the source of truth.
|
|
|
110
110
|
"skill": "rdc:review",
|
|
111
111
|
"task": "Audit the current diff for regressions before merge.",
|
|
112
112
|
"context": {
|
|
113
|
-
"repo": "
|
|
113
|
+
"repo": "{PROJECT_ROOT}",
|
|
114
114
|
"work_item_id": "<optional-uuid>",
|
|
115
115
|
"branch": "develop",
|
|
116
116
|
"owned_files": []
|
package/skills/deploy/SKILL.md
CHANGED
|
@@ -66,10 +66,10 @@ Template selection:
|
|
|
66
66
|
|
|
67
67
|
```
|
|
68
68
|
rdc:deploy new: <slug>
|
|
69
|
-
[ ] .dockerignore present at
|
|
69
|
+
[ ] .dockerignore present at project root (`ls {PROJECT_ROOT}/.dockerignore` — STOP if missing)
|
|
70
70
|
[ ] Template loaded from docs/runbooks/coolify-app-templates.json (pick nextjs-app / static-site / mcp-server)
|
|
71
71
|
[ ] Required vars substituted: NAME, APP_PATH, DOMAIN, BRANCH, PROJECT_UUID, ENVIRONMENT_UUID [+ TURBO_FILTER / PORT]
|
|
72
|
-
[ ] DNS path chosen (A:
|
|
72
|
+
[ ] DNS path chosen (A: staging wildcard B: apex C: other zone)
|
|
73
73
|
[ ] DNS record verified or wildcard confirmed
|
|
74
74
|
[ ] Cloudflare proxy setting correct for DNS path
|
|
75
75
|
[ ] Application created via POST /applications/private-github-app (template payload)
|
|
@@ -117,7 +117,7 @@ rdc:deploy audit: fleet scan
|
|
|
117
117
|
[ ] Env var drift (registry.env_vars_needed vs Coolify env)
|
|
118
118
|
[ ] Branch mismatches (Coolify git_branch ≠ expected)
|
|
119
119
|
[ ] Disk space on 64.237.54.189
|
|
120
|
-
[ ]
|
|
120
|
+
[ ] DNS/proxy misconfigs on configured staging wildcard
|
|
121
121
|
[ ] Duplicate apps (same repo, multiple UUIDs)
|
|
122
122
|
|
|
123
123
|
Findings:
|
|
@@ -145,39 +145,39 @@ _COOLIFY=$(curl -s http://127.0.0.1:52437/v/coolify-api)
|
|
|
145
145
|
|
|
146
146
|
# List applications
|
|
147
147
|
curl -s -H "Authorization: Bearer $_COOLIFY" \
|
|
148
|
-
|
|
148
|
+
"$DEPLOY_API_BASE/api/v1/applications"
|
|
149
149
|
|
|
150
150
|
# Get application details
|
|
151
151
|
curl -s -H "Authorization: Bearer $_COOLIFY" \
|
|
152
|
-
|
|
152
|
+
"$DEPLOY_API_BASE/api/v1/applications/<uuid>"
|
|
153
153
|
|
|
154
154
|
# Deploy (trigger)
|
|
155
155
|
curl -s -X POST -H "Authorization: Bearer $_COOLIFY" \
|
|
156
|
-
|
|
156
|
+
"$DEPLOY_API_BASE/api/v1/applications/<uuid>/deploy"
|
|
157
157
|
|
|
158
158
|
# Get deployment logs
|
|
159
159
|
curl -s -H "Authorization: Bearer $_COOLIFY" \
|
|
160
|
-
|
|
160
|
+
"$DEPLOY_API_BASE/api/v1/deployments/<deployment-id>"
|
|
161
161
|
|
|
162
162
|
# Set env var
|
|
163
163
|
curl -s -X POST -H "Authorization: Bearer $_COOLIFY" \
|
|
164
164
|
-H "Content-Type: application/json" \
|
|
165
165
|
-d '{"key":"<KEY>","value":"<VALUE>"}' \
|
|
166
|
-
|
|
166
|
+
"$DEPLOY_API_BASE/api/v1/applications/<uuid>/envs"
|
|
167
167
|
|
|
168
168
|
# Set watch_paths
|
|
169
169
|
curl -s -X PATCH -H "Authorization: Bearer $_COOLIFY" \
|
|
170
170
|
-H "Content-Type: application/json" \
|
|
171
171
|
-d '{"watch_paths":"apps/<name>/**\npackages/**"}' \
|
|
172
|
-
|
|
172
|
+
"$DEPLOY_API_BASE/api/v1/applications/<uuid>"
|
|
173
173
|
```
|
|
174
174
|
|
|
175
175
|
**Never print `$_COOLIFY` to stdout.** Inline from clauth only — do not assign raw strings.
|
|
176
176
|
|
|
177
177
|
If clauth daemon is not responding (`curl -s http://127.0.0.1:52437/ping` fails):
|
|
178
178
|
```
|
|
179
|
-
BLOCKED:
|
|
180
|
-
Fix:
|
|
179
|
+
BLOCKED: credential provider is not responding.
|
|
180
|
+
Fix: start the project's credential provider or configure deployment credentials through env vars, then retry.
|
|
181
181
|
I cannot proceed until this is resolved.
|
|
182
182
|
```
|
|
183
183
|
|
|
@@ -189,7 +189,7 @@ I cannot proceed until this is resolved.
|
|
|
189
189
|
```
|
|
190
190
|
Server UUID: ih386anenvvvn6fy1umtyow0
|
|
191
191
|
Server IP: 64.237.54.189
|
|
192
|
-
Dashboard:
|
|
192
|
+
Dashboard: <deployment-dashboard-url>
|
|
193
193
|
GitHub App UUID: xdmcy60putp5h9j7k4kwg9c3
|
|
194
194
|
```
|
|
195
195
|
|
package/skills/design/SKILL.md
CHANGED
|
@@ -19,7 +19,7 @@ RDC-owned design execution for Studio and LIFEAI interfaces. This skill is the S
|
|
|
19
19
|
|
|
20
20
|
- Studio, Palette Library, brand-token, theme, component, or live-editor work
|
|
21
21
|
- Any design task that must understand RDC's real token tables and Studio routes
|
|
22
|
-
- UI critique, audit, polish, colorize, type, layout, or craft work in `
|
|
22
|
+
- UI critique, audit, polish, colorize, type, layout, or craft work in `{PROJECT_ROOT}`
|
|
23
23
|
- Agent-side color-system exploration using Rampa CLI
|
|
24
24
|
- Preparing token-aware implementation instructions for frontend/backend/data agents
|
|
25
25
|
|
|
@@ -166,7 +166,7 @@ Project docs to read for Studio work:
|
|
|
166
166
|
8. **Verify.**
|
|
167
167
|
- Run scoped tests only.
|
|
168
168
|
- For Studio: prefer route smoke checks, token API checks, and browser screenshots when UI changed.
|
|
169
|
-
- For CLI prompt work: run `node
|
|
169
|
+
- For CLI prompt work: run `node {RDC_SKILLS_ROOT}/scripts/rdc-design-cli.mjs <command> <brief>` and inspect the generated report under `.rdc/reports/rdc-design-cli/`.
|
|
170
170
|
|
|
171
171
|
## Command Menu
|
|
172
172
|
|
|
@@ -188,15 +188,15 @@ Project docs to read for Studio work:
|
|
|
188
188
|
Use the local helper to see exactly how much instruction text the skill is generating before sending it through an agent:
|
|
189
189
|
|
|
190
190
|
```powershell
|
|
191
|
-
node
|
|
192
|
-
node
|
|
193
|
-
node
|
|
191
|
+
node {RDC_SKILLS_ROOT}/scripts/rdc-design-cli.mjs studio "audit the Studio palette page"
|
|
192
|
+
node {RDC_SKILLS_ROOT}/scripts/rdc-design-cli.mjs palette "generate a PRT palette workflow"
|
|
193
|
+
node {RDC_SKILLS_ROOT}/scripts/rdc-design-cli.mjs --json theme "RDC earth-forward light theme"
|
|
194
194
|
```
|
|
195
195
|
|
|
196
196
|
The helper writes logs to:
|
|
197
197
|
|
|
198
198
|
```text
|
|
199
|
-
|
|
199
|
+
{RDC_SKILLS_ROOT}/.rdc/reports/rdc-design-cli/
|
|
200
200
|
```
|
|
201
201
|
|
|
202
202
|
Each run includes character count, word count, approximate token count, references loaded, and the final prompt text.
|
package/skills/fs-mcp/SKILL.md
CHANGED
|
@@ -11,7 +11,7 @@ description: "Usage `rdc:fs-mcp <task>` — Use the File System MCP bridge for l
|
|
|
11
11
|
# rdc:fs-mcp — File System MCP Bridge
|
|
12
12
|
|
|
13
13
|
## When to Use
|
|
14
|
-
- Claude.ai, Cowork, or another remote surface needs live access to `
|
|
14
|
+
- Claude.ai, Cowork, or another remote surface needs live access to `{PROJECT_ROOT}` through the File System MCP.
|
|
15
15
|
- You need to read, search, or list current local repo files without relying on GitHub freshness.
|
|
16
16
|
- You need to write a small/scratch file through FS MCP.
|
|
17
17
|
- You need to move a larger cloud file into the local repo.
|
|
@@ -88,7 +88,7 @@ Required Claude.ai handoff shape:
|
|
|
88
88
|
|
|
89
89
|
```json
|
|
90
90
|
{
|
|
91
|
-
"repo": "
|
|
91
|
+
"repo": "<owner>/<repo>",
|
|
92
92
|
"remote": "origin",
|
|
93
93
|
"ref": "claude-ai/docs-upload-123",
|
|
94
94
|
"paths": ["docs/plans/foo.md"],
|
package/skills/plan/SKILL.md
CHANGED
|
@@ -227,7 +227,7 @@ Q1. Which Coolify project does this app belong to?
|
|
|
227
227
|
→ Record: project_uuid + environment_uuid (staging or production)
|
|
228
228
|
|
|
229
229
|
Q2. What is the domain?
|
|
230
|
-
→
|
|
230
|
+
→ staging wildcard subdomain? (staging / internal tools)
|
|
231
231
|
→ Custom subdomain on an existing zone? (e.g. app.regendevcorp.com)
|
|
232
232
|
→ Apex domain? (e.g. place.fund itself)
|
|
233
233
|
→ Domain on a different zone entirely? (e.g. skymesasouth.com)
|
|
@@ -238,7 +238,7 @@ Q3. Is this domain already in our Cloudflare account?
|
|
|
238
238
|
→ If NS not delegated to Cloudflare: A record in Cloudflare does nothing — must go to registrar
|
|
239
239
|
|
|
240
240
|
Q4. Does this app need Cloudflare proxy (orange cloud)?
|
|
241
|
-
→
|
|
241
|
+
→ Traefik/Let's Encrypt HTTP-01 staging wildcards are often safest unproxied; verify your platform's DNS requirements.
|
|
242
242
|
→ Custom domain needing DDoS/CDN: proxy OK only if SSL mode = Full (strict) + origin cert provisioned
|
|
243
243
|
→ Any doubt: start unproxied, add proxy after confirming SSL works
|
|
244
244
|
|