@functional-systems/srcx 0.1.0-dev.3 → 0.1.0-dev.31
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/CHANGELOG.md +137 -1
- package/DESIGN.md +193 -58
- package/README.md +553 -38
- package/RELEASING.md +30 -9
- package/VALIDATION.md +855 -25
- package/dist/build.d.ts +81 -1
- package/dist/build.js +187 -37
- package/dist/build.js.map +1 -1
- package/dist/chunk.d.ts +9 -3
- package/dist/chunk.js +341 -14
- package/dist/chunk.js.map +1 -1
- package/dist/cli.js +103 -19
- package/dist/cli.js.map +1 -1
- package/dist/file-policy.d.ts +20 -0
- package/dist/file-policy.js +96 -0
- package/dist/file-policy.js.map +1 -0
- package/dist/implementation.d.ts +11 -0
- package/dist/implementation.js +191 -0
- package/dist/implementation.js.map +1 -0
- package/dist/publish.d.ts +18 -0
- package/dist/publish.js +158 -34
- package/dist/publish.js.map +1 -1
- package/dist/releases.js +21 -3
- package/dist/releases.js.map +1 -1
- package/dist/remote.d.ts +1 -0
- package/dist/remote.js +50 -9
- package/dist/remote.js.map +1 -1
- package/dist/repository.d.ts +4 -1
- package/dist/repository.js +26 -11
- package/dist/repository.js.map +1 -1
- package/dist/rerank.d.ts +16 -0
- package/dist/rerank.js +87 -0
- package/dist/rerank.js.map +1 -0
- package/dist/search.d.ts +14 -4
- package/dist/search.js +119 -23
- package/dist/search.js.map +1 -1
- package/dist/skills.d.ts +24 -0
- package/dist/skills.js +155 -0
- package/dist/skills.js.map +1 -0
- package/package.json +16 -5
- package/runtime/grammars/README.md +50 -0
- package/runtime/grammars/tree-sitter-bash.LICENSE +21 -0
- package/runtime/grammars/tree-sitter-bash.wasm +0 -0
- package/runtime/grammars/tree-sitter-sql.LICENSE +21 -0
- package/runtime/grammars/tree-sitter-sql.wasm +0 -0
- package/runtime/grammars/tree-sitter-wasm.LICENSE +21 -0
- package/runtime/qwen.py +132 -0
- package/runtime/requirements.txt +34 -0
- package/skills/srcx-search/SKILL.md +85 -0
- package/src/build.ts +258 -47
- package/src/chunk.ts +360 -12
- package/src/cli.ts +212 -24
- package/src/file-policy.ts +157 -0
- package/src/implementation.ts +253 -0
- package/src/publish.ts +226 -34
- package/src/releases.ts +36 -3
- package/src/remote.ts +70 -13
- package/src/repository.ts +40 -11
- package/src/rerank.ts +131 -0
- package/src/search.ts +154 -28
- package/src/skills.ts +205 -0
package/src/skills.ts
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import {
|
|
3
|
+
lstat,
|
|
4
|
+
mkdir,
|
|
5
|
+
readFile,
|
|
6
|
+
readdir,
|
|
7
|
+
realpath,
|
|
8
|
+
rename,
|
|
9
|
+
rmdir,
|
|
10
|
+
unlink,
|
|
11
|
+
writeFile,
|
|
12
|
+
} from "node:fs/promises";
|
|
13
|
+
import { homedir } from "node:os";
|
|
14
|
+
import { join, resolve } from "node:path";
|
|
15
|
+
import { hash, invariant } from "./common.js";
|
|
16
|
+
|
|
17
|
+
export const SKILL_AGENTS = ["codex", "claude"] as const;
|
|
18
|
+
export const SKILL_SCOPES = ["user", "project"] as const;
|
|
19
|
+
type Agent = (typeof SKILL_AGENTS)[number];
|
|
20
|
+
type Scope = (typeof SKILL_SCOPES)[number];
|
|
21
|
+
type Operation = "install" | "update" | "remove" | "status";
|
|
22
|
+
const name = "srcx-search";
|
|
23
|
+
const owner = "@functional-systems/srcx";
|
|
24
|
+
const pkg = JSON.parse(
|
|
25
|
+
readFileSync(new URL("../package.json", import.meta.url), "utf8"),
|
|
26
|
+
);
|
|
27
|
+
const marker = "<!-- srcx-install ";
|
|
28
|
+
|
|
29
|
+
async function stat(path: string) {
|
|
30
|
+
try {
|
|
31
|
+
return await lstat(path);
|
|
32
|
+
} catch (e) {
|
|
33
|
+
if ((e as NodeJS.ErrnoException).code === "ENOENT") return undefined;
|
|
34
|
+
throw e;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
async function directory(path: string, create = false): Promise<boolean> {
|
|
38
|
+
if (create) {
|
|
39
|
+
try {
|
|
40
|
+
await mkdir(path);
|
|
41
|
+
} catch (e) {
|
|
42
|
+
if ((e as NodeJS.ErrnoException).code !== "EEXIST") throw e;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const s = await stat(path);
|
|
46
|
+
invariant(
|
|
47
|
+
!s || (s.isDirectory() && !s.isSymbolicLink()),
|
|
48
|
+
`Expected a directory, not a file or symlink: ${path}`,
|
|
49
|
+
);
|
|
50
|
+
return !!s;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function manageSkill(
|
|
54
|
+
operation: Operation,
|
|
55
|
+
options: {
|
|
56
|
+
agent: Agent;
|
|
57
|
+
scope: Scope;
|
|
58
|
+
path?: string;
|
|
59
|
+
},
|
|
60
|
+
userHome = homedir(),
|
|
61
|
+
) {
|
|
62
|
+
invariant(
|
|
63
|
+
SKILL_AGENTS.includes(options.agent),
|
|
64
|
+
"Choose --agent codex or claude.",
|
|
65
|
+
);
|
|
66
|
+
invariant(
|
|
67
|
+
SKILL_SCOPES.includes(options.scope),
|
|
68
|
+
"Choose --scope user or project.",
|
|
69
|
+
);
|
|
70
|
+
invariant(
|
|
71
|
+
options.scope === "project" || options.path === undefined,
|
|
72
|
+
"--path requires --scope project.",
|
|
73
|
+
);
|
|
74
|
+
// Resolve the chosen root, then refuse redirected managed path components.
|
|
75
|
+
const root = await realpath(
|
|
76
|
+
options.scope === "user" ? userHome : resolve(options.path ?? "."),
|
|
77
|
+
);
|
|
78
|
+
invariant(
|
|
79
|
+
(await lstat(root)).isDirectory(),
|
|
80
|
+
"Skill scope root must be a directory.",
|
|
81
|
+
);
|
|
82
|
+
const dirs = [join(root, options.agent === "codex" ? ".agents" : ".claude")];
|
|
83
|
+
dirs.push(join(dirs[0]!, "skills"));
|
|
84
|
+
const parent = dirs[1]!,
|
|
85
|
+
target = join(parent, name),
|
|
86
|
+
file = join(target, "SKILL.md");
|
|
87
|
+
const bundled = await readFile(
|
|
88
|
+
new URL("../skills/srcx-search/SKILL.md", import.meta.url),
|
|
89
|
+
"utf8",
|
|
90
|
+
);
|
|
91
|
+
const bundledHash = hash(bundled);
|
|
92
|
+
const info = {
|
|
93
|
+
agent: options.agent,
|
|
94
|
+
scope: options.scope,
|
|
95
|
+
path: target,
|
|
96
|
+
bundledVersion: pkg.version as string,
|
|
97
|
+
};
|
|
98
|
+
const encoded = `${bundled}${marker}${JSON.stringify({ package: owner, version: pkg.version, sha256: bundledHash })} -->\n`;
|
|
99
|
+
async function inspect() {
|
|
100
|
+
if (!(await directory(target))) return { status: "not-installed" as const };
|
|
101
|
+
const entries = await readdir(target);
|
|
102
|
+
const s = await stat(file);
|
|
103
|
+
if (!s?.isFile() || s.isSymbolicLink())
|
|
104
|
+
return { status: "unmanaged" as const };
|
|
105
|
+
const text = await readFile(file, "utf8");
|
|
106
|
+
const at = text.lastIndexOf(marker);
|
|
107
|
+
let installed: { package?: unknown; version?: unknown; sha256?: unknown };
|
|
108
|
+
try {
|
|
109
|
+
installed = JSON.parse(text.slice(at + marker.length, -5));
|
|
110
|
+
} catch {
|
|
111
|
+
return { status: "unmanaged" as const };
|
|
112
|
+
}
|
|
113
|
+
if (
|
|
114
|
+
at < 0 ||
|
|
115
|
+
!text.endsWith(" -->\n") ||
|
|
116
|
+
installed?.package !== owner ||
|
|
117
|
+
typeof installed.version !== "string" ||
|
|
118
|
+
!installed.version ||
|
|
119
|
+
typeof installed.sha256 !== "string" ||
|
|
120
|
+
!/^[a-f0-9]{64}$/.test(installed.sha256)
|
|
121
|
+
) {
|
|
122
|
+
return { status: "unmanaged" as const };
|
|
123
|
+
}
|
|
124
|
+
const status =
|
|
125
|
+
entries.length !== 1 || hash(text.slice(0, at)) !== installed.sha256
|
|
126
|
+
? "modified"
|
|
127
|
+
: installed.sha256 === bundledHash && installed.version === pkg.version
|
|
128
|
+
? "current"
|
|
129
|
+
: "update-available";
|
|
130
|
+
return { status, installedVersion: installed.version };
|
|
131
|
+
}
|
|
132
|
+
// Status and absent removals are read-only, including missing parent directories.
|
|
133
|
+
for (const dir of dirs) {
|
|
134
|
+
if (!(await directory(dir, operation === "install"))) {
|
|
135
|
+
invariant(
|
|
136
|
+
operation !== "update",
|
|
137
|
+
"Skill is not installed; run skills install first.",
|
|
138
|
+
);
|
|
139
|
+
return { ...info, status: "not-installed" };
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (operation === "status" || operation === "remove") {
|
|
143
|
+
const current = await inspect();
|
|
144
|
+
if (operation === "status" || current.status === "not-installed")
|
|
145
|
+
return { ...info, ...current };
|
|
146
|
+
}
|
|
147
|
+
const lock = join(parent, `.${name}.srcx-lock`);
|
|
148
|
+
try {
|
|
149
|
+
await mkdir(lock);
|
|
150
|
+
} catch (e) {
|
|
151
|
+
if ((e as NodeJS.ErrnoException).code === "EEXIST")
|
|
152
|
+
throw new Error(
|
|
153
|
+
`Another skill operation may be active. Retry later; inspect a stale lock before removing it: ${lock}`,
|
|
154
|
+
);
|
|
155
|
+
throw e;
|
|
156
|
+
}
|
|
157
|
+
try {
|
|
158
|
+
// Recheck after locking before changing an existing installation.
|
|
159
|
+
const current = await inspect();
|
|
160
|
+
invariant(
|
|
161
|
+
current.status !== "unmanaged" && current.status !== "modified",
|
|
162
|
+
`Existing skill is ${current.status}; preserve or move it before ${operation}: ${target}`,
|
|
163
|
+
);
|
|
164
|
+
if (operation === "remove") {
|
|
165
|
+
if (current.status === "not-installed")
|
|
166
|
+
return { ...info, status: "not-installed" };
|
|
167
|
+
await unlink(file);
|
|
168
|
+
await rmdir(target); // Never recursively remove unrecognized files.
|
|
169
|
+
return { ...info, status: "removed" };
|
|
170
|
+
}
|
|
171
|
+
if (current.status === "current") return { ...info, ...current };
|
|
172
|
+
invariant(
|
|
173
|
+
operation !== "update" || current.status !== "not-installed",
|
|
174
|
+
"Skill is not installed; run skills install first.",
|
|
175
|
+
);
|
|
176
|
+
invariant(
|
|
177
|
+
operation !== "install" || current.status === "not-installed",
|
|
178
|
+
"A different bundled skill is installed; run skills update.",
|
|
179
|
+
);
|
|
180
|
+
// One file carries both instructions and provenance, so updates cannot tear
|
|
181
|
+
// a separate ownership manifest away from the installed content.
|
|
182
|
+
const temp = join(lock, "SKILL.md");
|
|
183
|
+
await writeFile(temp, encoded, { flag: "wx", mode: 0o644 });
|
|
184
|
+
const fresh = current.status === "not-installed";
|
|
185
|
+
if (fresh) await mkdir(target);
|
|
186
|
+
try {
|
|
187
|
+
await rename(temp, file);
|
|
188
|
+
} catch (e) {
|
|
189
|
+
if (fresh) await rmdir(target);
|
|
190
|
+
throw e;
|
|
191
|
+
}
|
|
192
|
+
return {
|
|
193
|
+
...info,
|
|
194
|
+
status: fresh ? "installed" : "updated",
|
|
195
|
+
installedVersion: pkg.version as string,
|
|
196
|
+
};
|
|
197
|
+
} finally {
|
|
198
|
+
try {
|
|
199
|
+
await unlink(join(lock, "SKILL.md"));
|
|
200
|
+
} catch (e) {
|
|
201
|
+
if ((e as NodeJS.ErrnoException).code !== "ENOENT") throw e;
|
|
202
|
+
}
|
|
203
|
+
await rmdir(lock);
|
|
204
|
+
}
|
|
205
|
+
}
|