@ericrisco/rsc 2.0.6 → 2.0.8
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/manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -44,6 +44,12 @@ function agentSpecFor(target, root) {
|
|
|
44
44
|
return { dir: dirname(p), ext };
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
// A real file, or a link that leads to one. `statSync` follows; a dangling link throws and is
|
|
48
|
+
// therefore not a file, which is the answer we want.
|
|
49
|
+
function isFile(path) {
|
|
50
|
+
try { return statSync(path).isFile(); } catch { return false; }
|
|
51
|
+
}
|
|
52
|
+
|
|
47
53
|
// Real files only. Directories (a dir named `foo.md` is not an agent), dangling
|
|
48
54
|
// symlinks and dotfiles are not capabilities.
|
|
49
55
|
function entriesWithExt(dir, ext) {
|
|
@@ -96,8 +102,19 @@ export function listSkills({ target, home, cwd = process.cwd() } = {}) {
|
|
|
96
102
|
try {
|
|
97
103
|
for (const e of readdirSync(paths.dir, { withFileTypes: true })) {
|
|
98
104
|
if (e.name.startsWith('.')) continue;
|
|
99
|
-
|
|
100
|
-
|
|
105
|
+
// Ask what a skill IS, never how it was put on disk. A `Dirent` from `withFileTypes`
|
|
106
|
+
// describes the LINK and not its target, so `isDirectory()`/`isFile()` are both false for
|
|
107
|
+
// every skill on macOS and Linux — where a symlink per assistant, pointing at one shared
|
|
108
|
+
// copy, is the layout the installer deliberately writes. Windows gets real files (relative
|
|
109
|
+
// dir symlinks need Developer Mode there), which is why this read as correct for so long
|
|
110
|
+
// and why CI never caught it.
|
|
111
|
+
//
|
|
112
|
+
// `existsSync` and `statSync` follow links on their own, so one question answers all three
|
|
113
|
+
// platforms with no branch: is there a SKILL.md behind this name? Following a link is not
|
|
114
|
+
// the same as believing anything — a dangling link, a directory with no SKILL.md and a
|
|
115
|
+
// plain file all still fail it.
|
|
116
|
+
if (ext) { if (e.name.endsWith(ext) && isFile(join(paths.dir, e.name))) out.push({ id: e.name.slice(0, -ext.length), scope }); }
|
|
117
|
+
else if (existsSync(join(paths.dir, e.name, 'SKILL.md'))) out.push({ id: e.name, scope });
|
|
101
118
|
}
|
|
102
119
|
} catch { /* unreadable scope → contributes nothing */ }
|
|
103
120
|
}
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
// read. Anything it cannot read with certainty (editor commit, -F file, --amend
|
|
19
19
|
// --no-edit, an unparseable command) is ALLOWED: a guard that guesses is a guard that
|
|
20
20
|
// gets turned off. Every deny names its recovery. Opt out with .rsc/.no-gitmoji.
|
|
21
|
-
import {
|
|
22
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
21
|
+
import { fileURLToPath } from 'node:url';
|
|
22
|
+
import { existsSync, readFileSync, realpathSync } from 'node:fs';
|
|
23
23
|
import { join } from 'node:path';
|
|
24
24
|
|
|
25
25
|
// The official gitmoji set (gitmoji.dev/api/gitmojis), as data: [emoji, code, semver, meaning].
|
|
@@ -222,7 +222,34 @@ export function denyMessage(message) {
|
|
|
222
222
|
|
|
223
223
|
// ---- hook entrypoint -----------------------------------------------------------
|
|
224
224
|
// Skipped when imported by a test (P2: the mechanism is testable without a subprocess).
|
|
225
|
-
|
|
225
|
+
/**
|
|
226
|
+
* "Was this file run directly?" — the same question `clone-bootstrap.mjs` answers, and the same two
|
|
227
|
+
* traps, so the same answer. Written out here rather than imported because hooks are materialized
|
|
228
|
+
* file by file under `.rsc/`: an import would be a second file to copy for six lines.
|
|
229
|
+
*
|
|
230
|
+
* `process.argv[1]` MAY NOT EXIST. Under `node -e`, `--input-type=module` or the REPL there is no
|
|
231
|
+
* script path, and `pathToFileURL(undefined)` throws — at module load, so merely IMPORTING this
|
|
232
|
+
* file takes the importer down with it. A module that cannot be imported is a module nobody can
|
|
233
|
+
* build a tool on top of, and people do exactly that with this package.
|
|
234
|
+
*
|
|
235
|
+
* SYMLINKS MAKE THE TWO SIDES DISAGREE. `import.meta.url` is what the loader resolved (symlinks
|
|
236
|
+
* RESOLVED); `process.argv[1]` is the raw string the client passed (symlinks INTACT). One
|
|
237
|
+
* symlinked component — `/tmp` and `/var` on macOS, or an ordinary `~/code -> /Volumes/…` — and a
|
|
238
|
+
* string compare says "no", the main block never runs, node exits 0 having printed nothing, and
|
|
239
|
+
* the hook becomes a silent no-op that `doctor` still reports as wired.
|
|
240
|
+
*/
|
|
241
|
+
function isMainModule(metaUrl) {
|
|
242
|
+
const invoked = process.argv[1];
|
|
243
|
+
if (!invoked) return false;
|
|
244
|
+
const self = fileURLToPath(metaUrl);
|
|
245
|
+
try {
|
|
246
|
+
return realpathSync(self) === realpathSync(invoked);
|
|
247
|
+
} catch {
|
|
248
|
+
return self === invoked;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (isMainModule(import.meta.url)) {
|
|
226
253
|
const root = process.argv[2] || process.cwd();
|
|
227
254
|
const allow = () => process.exit(0);
|
|
228
255
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { existsSync, readFileSync, realpathSync } from 'node:fs';
|
|
4
4
|
import { resolve, join, dirname } from 'node:path';
|
|
5
5
|
import { capture, resume } from './session-memory-core.mjs';
|
|
6
6
|
|
|
@@ -126,7 +126,34 @@ function stdinJson() {
|
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
-
|
|
129
|
+
/**
|
|
130
|
+
* "Was this file run directly?" — the same question `clone-bootstrap.mjs` answers, and the same two
|
|
131
|
+
* traps, so the same answer. Written out here rather than imported because hooks are materialized
|
|
132
|
+
* file by file under `.rsc/`: an import would be a second file to copy for six lines.
|
|
133
|
+
*
|
|
134
|
+
* `process.argv[1]` MAY NOT EXIST. Under `node -e`, `--input-type=module` or the REPL there is no
|
|
135
|
+
* script path, and `pathToFileURL(undefined)` throws — at module load, so merely IMPORTING this
|
|
136
|
+
* file takes the importer down with it. A module that cannot be imported is a module nobody can
|
|
137
|
+
* build a tool on top of, and people do exactly that with this package.
|
|
138
|
+
*
|
|
139
|
+
* SYMLINKS MAKE THE TWO SIDES DISAGREE. `import.meta.url` is what the loader resolved (symlinks
|
|
140
|
+
* RESOLVED); `process.argv[1]` is the raw string the client passed (symlinks INTACT). One
|
|
141
|
+
* symlinked component — `/tmp` and `/var` on macOS, or an ordinary `~/code -> /Volumes/…` — and a
|
|
142
|
+
* string compare says "no", the main block never runs, node exits 0 having printed nothing, and
|
|
143
|
+
* the hook becomes a silent no-op that `doctor` still reports as wired.
|
|
144
|
+
*/
|
|
145
|
+
function isMainModule(metaUrl) {
|
|
146
|
+
const invoked = process.argv[1];
|
|
147
|
+
if (!invoked) return false;
|
|
148
|
+
const self = fileURLToPath(metaUrl);
|
|
149
|
+
try {
|
|
150
|
+
return realpathSync(self) === realpathSync(invoked);
|
|
151
|
+
} catch {
|
|
152
|
+
return self === invoked;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (isMainModule(import.meta.url)) {
|
|
130
157
|
const result = handleLifecycle({ target: process.argv[2], event: process.argv[3], native: stdinJson() });
|
|
131
158
|
process.stdout.write(`${JSON.stringify(result.output)}\n`);
|
|
132
159
|
}
|