@litfamily/litcodex 1.0.0 → 1.0.2
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/README.md +18 -21
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +2 -38
- package/dist/config-migration/profile-files.d.ts +4 -0
- package/dist/config-migration/profile-files.js +22 -0
- package/dist/install/capability-preflight-error.js +5 -1
- package/dist/install/codex.d.ts +1 -0
- package/dist/install/generated-skill-payload-hashes.d.ts +1 -6
- package/dist/install/generated-skill-payload-hashes.js +1 -6
- package/dist/install/host-capabilities.d.ts +2 -0
- package/dist/install/host-capabilities.js +13 -1
- package/dist/install/index.js +3 -1
- package/dist/install/skill-catalog.d.ts +1 -1
- package/dist/install/skill-catalog.js +1 -6
- package/dist/install/skill-resource-hashes.d.ts +1 -1
- package/dist/install/skill-resource-hashes.js +1 -1
- package/dist/install/strict-config-probe.d.ts +2 -0
- package/dist/install/strict-config-probe.js +34 -5
- package/dist/install/uninstall.d.ts +2 -0
- package/dist/install/uninstall.js +34 -0
- package/marketplace/plugins/litcodex/.codex-plugin/plugin.json +1 -1
- package/marketplace/plugins/litcodex/components/auto-update/package.json +1 -1
- package/marketplace/plugins/litcodex/components/comment-checker/package.json +1 -1
- package/marketplace/plugins/litcodex/components/git-bash/package.json +1 -1
- package/marketplace/plugins/litcodex/components/lit-loop/dist/cli.js +3 -20
- package/marketplace/plugins/litcodex/components/lit-loop/dist/codex-hook.js +1 -8
- package/marketplace/plugins/litcodex/components/lit-loop/dist/hook-cli.d.ts +2 -1
- package/marketplace/plugins/litcodex/components/lit-loop/dist/hook-cli.js +4 -27
- package/marketplace/plugins/litcodex/components/lit-loop/dist/markers.d.ts +0 -2
- package/marketplace/plugins/litcodex/components/lit-loop/dist/markers.js +0 -2
- package/marketplace/plugins/litcodex/components/lit-loop/dist/modes.d.ts +1 -3
- package/marketplace/plugins/litcodex/components/lit-loop/dist/modes.js +1 -10
- package/marketplace/plugins/litcodex/components/lit-loop/package.json +1 -1
- package/marketplace/plugins/litcodex/components/lsp/package.json +1 -1
- package/marketplace/plugins/litcodex/components/rules/dist/static-injection.js +2 -2
- package/marketplace/plugins/litcodex/components/rules/package.json +1 -1
- package/marketplace/plugins/litcodex/components/start-work-continuation/package.json +1 -1
- package/marketplace/plugins/litcodex/components/telemetry/package.json +1 -1
- package/marketplace/plugins/litcodex/components/wikify-knowledge/package.json +1 -1
- package/marketplace/plugins/litcodex/skills/browser-drive/scripts/capability-probe.mjs +13 -5
- package/node_modules/@litcodex/lit-loop/README.md +1 -7
- package/node_modules/@litcodex/lit-loop/dist/cli.js +3 -20
- package/node_modules/@litcodex/lit-loop/dist/codex-hook.js +1 -8
- package/node_modules/@litcodex/lit-loop/dist/hook-cli.d.ts +2 -1
- package/node_modules/@litcodex/lit-loop/dist/hook-cli.js +4 -27
- package/node_modules/@litcodex/lit-loop/dist/markers.d.ts +0 -2
- package/node_modules/@litcodex/lit-loop/dist/markers.js +0 -2
- package/node_modules/@litcodex/lit-loop/dist/modes.d.ts +1 -3
- package/node_modules/@litcodex/lit-loop/dist/modes.js +1 -10
- package/node_modules/@litcodex/lit-loop/package.json +1 -1
- package/node_modules/@litcodex/lit-loop/skills/browser-drive/scripts/capability-probe.mjs +13 -5
- package/package.json +2 -2
- package/readme-assets/badge-version.svg +1 -1
- package/readme-assets/cover-motion.webp +0 -0
- package/marketplace/plugins/litcodex/components/lit-loop/directives/skill-observer.md +0 -105
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-loop/apply.d.ts +0 -16
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-loop/apply.js +0 -227
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-loop/config.d.ts +0 -13
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-loop/config.js +0 -12
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-loop/curator.d.ts +0 -14
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-loop/curator.js +0 -112
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-loop/hook.d.ts +0 -19
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-loop/hook.js +0 -435
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-loop/ownership.d.ts +0 -6
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-loop/ownership.js +0 -145
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-loop/paths.d.ts +0 -18
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-loop/paths.js +0 -43
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-loop/review.d.ts +0 -28
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-loop/review.js +0 -702
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-loop/store.d.ts +0 -32
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-loop/store.js +0 -667
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-loop/transaction.d.ts +0 -26
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-loop/transaction.js +0 -524
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-loop/types.d.ts +0 -81
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-loop/types.js +0 -246
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-loop/usage.d.ts +0 -6
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-loop/usage.js +0 -119
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-loop-cli.d.ts +0 -8
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-loop-cli.js +0 -111
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-observer-cli.d.ts +0 -3
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-observer-cli.js +0 -116
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-observer-route.d.ts +0 -6
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-observer-route.js +0 -12
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-observer.d.ts +0 -18
- package/marketplace/plugins/litcodex/components/lit-loop/dist/skill-observer.js +0 -946
- package/marketplace/plugins/litcodex/skills/skill-observer/SKILL.md +0 -215
- package/marketplace/plugins/litcodex/skills/skill-observer/agents/openai.yaml +0 -6
- package/marketplace/plugins/litcodex/skills/skill-observer/references/review-contract.md +0 -77
- package/node_modules/@litcodex/lit-loop/directives/skill-observer.md +0 -105
- package/node_modules/@litcodex/lit-loop/dist/skill-loop/apply.d.ts +0 -16
- package/node_modules/@litcodex/lit-loop/dist/skill-loop/apply.js +0 -227
- package/node_modules/@litcodex/lit-loop/dist/skill-loop/config.d.ts +0 -13
- package/node_modules/@litcodex/lit-loop/dist/skill-loop/config.js +0 -12
- package/node_modules/@litcodex/lit-loop/dist/skill-loop/curator.d.ts +0 -14
- package/node_modules/@litcodex/lit-loop/dist/skill-loop/curator.js +0 -112
- package/node_modules/@litcodex/lit-loop/dist/skill-loop/hook.d.ts +0 -19
- package/node_modules/@litcodex/lit-loop/dist/skill-loop/hook.js +0 -435
- package/node_modules/@litcodex/lit-loop/dist/skill-loop/ownership.d.ts +0 -6
- package/node_modules/@litcodex/lit-loop/dist/skill-loop/ownership.js +0 -145
- package/node_modules/@litcodex/lit-loop/dist/skill-loop/paths.d.ts +0 -18
- package/node_modules/@litcodex/lit-loop/dist/skill-loop/paths.js +0 -43
- package/node_modules/@litcodex/lit-loop/dist/skill-loop/review.d.ts +0 -28
- package/node_modules/@litcodex/lit-loop/dist/skill-loop/review.js +0 -702
- package/node_modules/@litcodex/lit-loop/dist/skill-loop/store.d.ts +0 -32
- package/node_modules/@litcodex/lit-loop/dist/skill-loop/store.js +0 -667
- package/node_modules/@litcodex/lit-loop/dist/skill-loop/transaction.d.ts +0 -26
- package/node_modules/@litcodex/lit-loop/dist/skill-loop/transaction.js +0 -524
- package/node_modules/@litcodex/lit-loop/dist/skill-loop/types.d.ts +0 -81
- package/node_modules/@litcodex/lit-loop/dist/skill-loop/types.js +0 -246
- package/node_modules/@litcodex/lit-loop/dist/skill-loop/usage.d.ts +0 -6
- package/node_modules/@litcodex/lit-loop/dist/skill-loop/usage.js +0 -119
- package/node_modules/@litcodex/lit-loop/dist/skill-loop-cli.d.ts +0 -8
- package/node_modules/@litcodex/lit-loop/dist/skill-loop-cli.js +0 -111
- package/node_modules/@litcodex/lit-loop/dist/skill-observer-cli.d.ts +0 -3
- package/node_modules/@litcodex/lit-loop/dist/skill-observer-cli.js +0 -116
- package/node_modules/@litcodex/lit-loop/dist/skill-observer-route.d.ts +0 -6
- package/node_modules/@litcodex/lit-loop/dist/skill-observer-route.js +0 -12
- package/node_modules/@litcodex/lit-loop/dist/skill-observer.d.ts +0 -18
- package/node_modules/@litcodex/lit-loop/dist/skill-observer.js +0 -946
- package/node_modules/@litcodex/lit-loop/skills/skill-observer/SKILL.md +0 -215
- package/node_modules/@litcodex/lit-loop/skills/skill-observer/references/review-contract.md +0 -77
|
@@ -1,435 +0,0 @@
|
|
|
1
|
-
import { spawnSync } from "node:child_process";
|
|
2
|
-
import { createHash } from "node:crypto";
|
|
3
|
-
import { closeSync, constants, fstatSync, lstatSync, opendirSync, openSync, readSync, realpathSync } from "node:fs";
|
|
4
|
-
import { basename, dirname, join, relative, resolve, sep } from "node:path";
|
|
5
|
-
import { normalizeSessionId } from "../state-paths.js";
|
|
6
|
-
import { SkillLoopError } from "./types.js";
|
|
7
|
-
const PENDING_SCHEMA = "litcodex.skill-review-pending/v1";
|
|
8
|
-
const MAX_GOALS_BYTES = 64_000;
|
|
9
|
-
const MAX_SESSION_DIRECTORY_ENTRIES = 64;
|
|
10
|
-
const MAX_SESSION_DIRECTORIES = 16;
|
|
11
|
-
const SOURCE_FILE_CAPS = new Map([
|
|
12
|
-
["brief.md", 64_000],
|
|
13
|
-
["goals.json", MAX_GOALS_BYTES],
|
|
14
|
-
["ledger.jsonl", 256_000],
|
|
15
|
-
]);
|
|
16
|
-
const MAX_SESSION_SOURCE_BYTES = 384_000;
|
|
17
|
-
const ANCHORED_PENDING_WRITE = `
|
|
18
|
-
import {
|
|
19
|
-
closeSync,
|
|
20
|
-
constants,
|
|
21
|
-
existsSync,
|
|
22
|
-
fsyncSync,
|
|
23
|
-
lstatSync,
|
|
24
|
-
openSync,
|
|
25
|
-
readFileSync,
|
|
26
|
-
realpathSync,
|
|
27
|
-
renameSync,
|
|
28
|
-
unlinkSync,
|
|
29
|
-
writeSync,
|
|
30
|
-
} from "node:fs";
|
|
31
|
-
import { randomBytes } from "node:crypto";
|
|
32
|
-
|
|
33
|
-
const [expectedRealPath, expectedDev, expectedIno, expectedUid, expectedMode, expectedBirthtimeMs] = process.argv.slice(1);
|
|
34
|
-
const matches = () => {
|
|
35
|
-
const stat = lstatSync(".");
|
|
36
|
-
return stat.isDirectory() &&
|
|
37
|
-
!stat.isSymbolicLink() &&
|
|
38
|
-
String(stat.dev) === expectedDev &&
|
|
39
|
-
String(stat.ino) === expectedIno &&
|
|
40
|
-
String(stat.uid) === expectedUid &&
|
|
41
|
-
String(stat.mode) === expectedMode &&
|
|
42
|
-
String(stat.birthtimeMs) === expectedBirthtimeMs &&
|
|
43
|
-
(stat.mode & 0o022) === 0 &&
|
|
44
|
-
realpathSync.native(".") === expectedRealPath;
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
if (!matches()) process.exit(41);
|
|
48
|
-
const bytes = readFileSync(0);
|
|
49
|
-
if (bytes.byteLength > 16_384) process.exit(42);
|
|
50
|
-
const temporary = ".pending-review.json.litcodex-" + process.pid + "-" + randomBytes(8).toString("hex");
|
|
51
|
-
let fileDescriptor;
|
|
52
|
-
let directoryDescriptor;
|
|
53
|
-
try {
|
|
54
|
-
fileDescriptor = openSync(
|
|
55
|
-
temporary,
|
|
56
|
-
constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY | constants.O_NOFOLLOW,
|
|
57
|
-
0o600,
|
|
58
|
-
);
|
|
59
|
-
let offset = 0;
|
|
60
|
-
while (offset < bytes.byteLength) {
|
|
61
|
-
const written = writeSync(fileDescriptor, bytes, offset, bytes.byteLength - offset, null);
|
|
62
|
-
if (written <= 0) throw new Error("short write");
|
|
63
|
-
offset += written;
|
|
64
|
-
}
|
|
65
|
-
fsyncSync(fileDescriptor);
|
|
66
|
-
closeSync(fileDescriptor);
|
|
67
|
-
fileDescriptor = undefined;
|
|
68
|
-
if (!matches()) throw new Error("directory authority changed before rename");
|
|
69
|
-
renameSync(temporary, "pending-review.json");
|
|
70
|
-
directoryDescriptor = openSync(".", constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
71
|
-
fsyncSync(directoryDescriptor);
|
|
72
|
-
closeSync(directoryDescriptor);
|
|
73
|
-
directoryDescriptor = undefined;
|
|
74
|
-
if (!matches()) throw new Error("directory authority changed after rename");
|
|
75
|
-
} finally {
|
|
76
|
-
if (fileDescriptor !== undefined) closeSync(fileDescriptor);
|
|
77
|
-
if (directoryDescriptor !== undefined) closeSync(directoryDescriptor);
|
|
78
|
-
if (existsSync(temporary)) unlinkSync(temporary);
|
|
79
|
-
}
|
|
80
|
-
`;
|
|
81
|
-
function record(value) {
|
|
82
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
83
|
-
}
|
|
84
|
-
function sameStat(left, right) {
|
|
85
|
-
return (left.dev === right.dev &&
|
|
86
|
-
left.ino === right.ino &&
|
|
87
|
-
left.uid === right.uid &&
|
|
88
|
-
left.mode === right.mode &&
|
|
89
|
-
left.nlink === right.nlink &&
|
|
90
|
-
left.size === right.size &&
|
|
91
|
-
left.mtimeMs === right.mtimeMs &&
|
|
92
|
-
left.ctimeMs === right.ctimeMs &&
|
|
93
|
-
left.birthtimeMs === right.birthtimeMs);
|
|
94
|
-
}
|
|
95
|
-
function captureDirectoryPin(path, expectedRealPath) {
|
|
96
|
-
const stat = lstatSync(path);
|
|
97
|
-
const uid = typeof process.getuid === "function" ? process.getuid() : stat.uid;
|
|
98
|
-
if (stat.isSymbolicLink() ||
|
|
99
|
-
!stat.isDirectory() ||
|
|
100
|
-
(stat.uid !== uid && stat.uid !== 0) ||
|
|
101
|
-
(stat.mode & 0o022) !== 0 ||
|
|
102
|
-
realpathSync.native(path) !== expectedRealPath) {
|
|
103
|
-
throw new SkillLoopError("REVIEW_SESSION_UNSAFE", "a lit-loop directory authority is unsafe");
|
|
104
|
-
}
|
|
105
|
-
return {
|
|
106
|
-
path,
|
|
107
|
-
realPath: expectedRealPath,
|
|
108
|
-
dev: stat.dev,
|
|
109
|
-
ino: stat.ino,
|
|
110
|
-
uid: stat.uid,
|
|
111
|
-
mode: stat.mode,
|
|
112
|
-
birthtimeMs: stat.birthtimeMs,
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
function sameDirectoryPin(left, right) {
|
|
116
|
-
return (left.path === right.path &&
|
|
117
|
-
left.realPath === right.realPath &&
|
|
118
|
-
left.dev === right.dev &&
|
|
119
|
-
left.ino === right.ino &&
|
|
120
|
-
left.uid === right.uid &&
|
|
121
|
-
left.mode === right.mode &&
|
|
122
|
-
left.birthtimeMs === right.birthtimeMs);
|
|
123
|
-
}
|
|
124
|
-
function verifyDirectoryAuthority(authority) {
|
|
125
|
-
for (const expected of authority) {
|
|
126
|
-
let current;
|
|
127
|
-
try {
|
|
128
|
-
current = captureDirectoryPin(expected.path, expected.realPath);
|
|
129
|
-
}
|
|
130
|
-
catch {
|
|
131
|
-
throw new SkillLoopError("REVIEW_SESSION_UNSAFE", "a lit-loop directory authority changed");
|
|
132
|
-
}
|
|
133
|
-
if (!sameDirectoryPin(current, expected)) {
|
|
134
|
-
throw new SkillLoopError("REVIEW_SESSION_UNSAFE", "a lit-loop directory authority changed");
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
function appendDirectoryAuthority(authority, path, expectedRealPath) {
|
|
139
|
-
verifyDirectoryAuthority(authority);
|
|
140
|
-
let pin;
|
|
141
|
-
try {
|
|
142
|
-
pin = captureDirectoryPin(path, expectedRealPath);
|
|
143
|
-
}
|
|
144
|
-
catch {
|
|
145
|
-
throw new SkillLoopError("REVIEW_SESSION_UNSAFE", "a lit-loop directory authority is unsafe");
|
|
146
|
-
}
|
|
147
|
-
verifyDirectoryAuthority(authority);
|
|
148
|
-
return [...authority, pin];
|
|
149
|
-
}
|
|
150
|
-
function workspaceAuthority(workspaceRoot) {
|
|
151
|
-
const workspace = resolve(workspaceRoot);
|
|
152
|
-
let physical;
|
|
153
|
-
try {
|
|
154
|
-
physical = realpathSync.native(workspace);
|
|
155
|
-
}
|
|
156
|
-
catch {
|
|
157
|
-
throw new SkillLoopError("REVIEW_SESSION_UNSAFE", "the workspace authority is unavailable");
|
|
158
|
-
}
|
|
159
|
-
return [captureDirectoryPin(workspace, physical)];
|
|
160
|
-
}
|
|
161
|
-
function childAuthorityOrNull(authority, path, expectedRealPath) {
|
|
162
|
-
verifyDirectoryAuthority(authority);
|
|
163
|
-
try {
|
|
164
|
-
lstatSync(path);
|
|
165
|
-
}
|
|
166
|
-
catch (error) {
|
|
167
|
-
if (error.code === "ENOENT") {
|
|
168
|
-
verifyDirectoryAuthority(authority);
|
|
169
|
-
return null;
|
|
170
|
-
}
|
|
171
|
-
throw new SkillLoopError("REVIEW_SESSION_UNSAFE", "a lit-loop directory could not be inspected safely");
|
|
172
|
-
}
|
|
173
|
-
return appendDirectoryAuthority(authority, path, expectedRealPath);
|
|
174
|
-
}
|
|
175
|
-
function pinnedBytes(path, cap, authority, beforeRead) {
|
|
176
|
-
verifyDirectoryAuthority(authority);
|
|
177
|
-
let before;
|
|
178
|
-
try {
|
|
179
|
-
before = lstatSync(path);
|
|
180
|
-
}
|
|
181
|
-
catch (error) {
|
|
182
|
-
if (error.code === "ENOENT") {
|
|
183
|
-
verifyDirectoryAuthority(authority);
|
|
184
|
-
return null;
|
|
185
|
-
}
|
|
186
|
-
throw new SkillLoopError("REVIEW_SESSION_UNSAFE", "a lit-loop source could not be inspected safely");
|
|
187
|
-
}
|
|
188
|
-
if (before.size > cap)
|
|
189
|
-
throw new SkillLoopError("REVIEW_SESSION_LIMIT", "a lit-loop source exceeded its byte cap");
|
|
190
|
-
const uid = typeof process.getuid === "function" ? process.getuid() : before.uid;
|
|
191
|
-
if (before.isSymbolicLink() ||
|
|
192
|
-
!before.isFile() ||
|
|
193
|
-
before.nlink !== 1 ||
|
|
194
|
-
(before.uid !== uid && before.uid !== 0) ||
|
|
195
|
-
dirname(path) !== authority.at(-1)?.path) {
|
|
196
|
-
throw new SkillLoopError("REVIEW_SESSION_UNSAFE", "a lit-loop source path is unsafe");
|
|
197
|
-
}
|
|
198
|
-
beforeRead?.(path);
|
|
199
|
-
verifyDirectoryAuthority(authority);
|
|
200
|
-
let descriptor;
|
|
201
|
-
try {
|
|
202
|
-
descriptor = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
203
|
-
const opened = fstatSync(descriptor);
|
|
204
|
-
const current = lstatSync(path);
|
|
205
|
-
if (!opened.isFile() ||
|
|
206
|
-
opened.isSymbolicLink() ||
|
|
207
|
-
opened.nlink !== 1 ||
|
|
208
|
-
!sameStat(opened, before) ||
|
|
209
|
-
!sameStat(current, before)) {
|
|
210
|
-
throw new SkillLoopError("REVIEW_SESSION_UNSAFE", "a lit-loop source changed before descriptor read");
|
|
211
|
-
}
|
|
212
|
-
const bytes = Buffer.alloc(opened.size);
|
|
213
|
-
let total = 0;
|
|
214
|
-
while (total < opened.size) {
|
|
215
|
-
const count = readSync(descriptor, bytes, total, opened.size - total, total);
|
|
216
|
-
if (count === 0)
|
|
217
|
-
break;
|
|
218
|
-
total += count;
|
|
219
|
-
}
|
|
220
|
-
const after = fstatSync(descriptor);
|
|
221
|
-
const pathAfter = lstatSync(path);
|
|
222
|
-
verifyDirectoryAuthority(authority);
|
|
223
|
-
if (!sameStat(after, before) || !sameStat(pathAfter, before)) {
|
|
224
|
-
throw new SkillLoopError("REVIEW_SESSION_UNSAFE", "a lit-loop source changed during descriptor read");
|
|
225
|
-
}
|
|
226
|
-
return bytes.subarray(0, total);
|
|
227
|
-
}
|
|
228
|
-
catch (error) {
|
|
229
|
-
if (error instanceof SkillLoopError)
|
|
230
|
-
throw error;
|
|
231
|
-
throw new SkillLoopError("REVIEW_SESSION_UNSAFE", "a lit-loop source could not be opened without following links");
|
|
232
|
-
}
|
|
233
|
-
finally {
|
|
234
|
-
if (descriptor !== undefined)
|
|
235
|
-
closeSync(descriptor);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
function validatedSession(session, loopRoot, beforeRead) {
|
|
239
|
-
const directory = session.path;
|
|
240
|
-
const goalsPath = join(directory, "goals.json");
|
|
241
|
-
const bytes = pinnedBytes(goalsPath, MAX_GOALS_BYTES, session.authority, beforeRead);
|
|
242
|
-
if (bytes === null)
|
|
243
|
-
return null;
|
|
244
|
-
let goals;
|
|
245
|
-
try {
|
|
246
|
-
goals = JSON.parse(bytes.toString("utf8"));
|
|
247
|
-
}
|
|
248
|
-
catch {
|
|
249
|
-
return null;
|
|
250
|
-
}
|
|
251
|
-
if (!record(goals) || goals["version"] !== 1 || !Array.isArray(goals["goals"]) || goals["goals"].length === 0) {
|
|
252
|
-
return null;
|
|
253
|
-
}
|
|
254
|
-
if (goals["goals"].some((goal) => !record(goal) ||
|
|
255
|
-
typeof goal["id"] !== "string" ||
|
|
256
|
-
!["pending", "in_progress", "complete", "failed", "blocked"].includes(goal["status"]))) {
|
|
257
|
-
return null;
|
|
258
|
-
}
|
|
259
|
-
if (directory === loopRoot)
|
|
260
|
-
return "unscoped";
|
|
261
|
-
const directoryId = basename(directory);
|
|
262
|
-
const declared = typeof goals["sessionId"] === "string" ? normalizeSessionId(goals["sessionId"]) : directoryId;
|
|
263
|
-
return declared === directoryId ? directoryId : null;
|
|
264
|
-
}
|
|
265
|
-
function sessionDirectories(workspaceRoot, beforeRead, afterEnumerate) {
|
|
266
|
-
const workspace = resolve(workspaceRoot);
|
|
267
|
-
const stateRoot = join(workspace, ".litcodex");
|
|
268
|
-
const loopRoot = join(stateRoot, "lit-loop");
|
|
269
|
-
const baseAuthority = workspaceAuthority(workspace);
|
|
270
|
-
const physicalWorkspace = baseAuthority[0]?.realPath;
|
|
271
|
-
const stateAuthority = childAuthorityOrNull(baseAuthority, stateRoot, join(physicalWorkspace, ".litcodex"));
|
|
272
|
-
if (stateAuthority === null)
|
|
273
|
-
return [];
|
|
274
|
-
const loopAuthority = childAuthorityOrNull(stateAuthority, loopRoot, join(physicalWorkspace, ".litcodex", "lit-loop"));
|
|
275
|
-
if (loopAuthority === null)
|
|
276
|
-
return [];
|
|
277
|
-
const directories = [{ path: loopRoot, authority: loopAuthority }];
|
|
278
|
-
const handle = opendirSync(loopRoot);
|
|
279
|
-
let visited = 0;
|
|
280
|
-
try {
|
|
281
|
-
for (let entry = handle.readSync(); entry !== null; entry = handle.readSync()) {
|
|
282
|
-
visited += 1;
|
|
283
|
-
if (visited > MAX_SESSION_DIRECTORY_ENTRIES) {
|
|
284
|
-
throw new SkillLoopError("REVIEW_SESSION_LIMIT", "the lit-loop session directory cap was exceeded");
|
|
285
|
-
}
|
|
286
|
-
if (!entry.isDirectory() || entry.isSymbolicLink())
|
|
287
|
-
continue;
|
|
288
|
-
if (directories.length >= MAX_SESSION_DIRECTORIES) {
|
|
289
|
-
throw new SkillLoopError("REVIEW_SESSION_LIMIT", "the lit-loop session count cap was exceeded");
|
|
290
|
-
}
|
|
291
|
-
const path = join(loopRoot, entry.name);
|
|
292
|
-
const relation = relative(loopRoot, path);
|
|
293
|
-
if (relation.length === 0 || relation.includes(sep) || relation === ".." || relation.startsWith(`..${sep}`)) {
|
|
294
|
-
throw new SkillLoopError("REVIEW_SESSION_UNSAFE", "a lit-loop session path escaped its root");
|
|
295
|
-
}
|
|
296
|
-
const authority = appendDirectoryAuthority(loopAuthority, path, join(loopAuthority.at(-1)?.realPath, relation));
|
|
297
|
-
directories.push({ path, authority });
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
finally {
|
|
301
|
-
handle.closeSync();
|
|
302
|
-
}
|
|
303
|
-
for (const directory of directories)
|
|
304
|
-
verifyDirectoryAuthority(directory.authority);
|
|
305
|
-
afterEnumerate?.();
|
|
306
|
-
for (const directory of directories)
|
|
307
|
-
verifyDirectoryAuthority(directory.authority);
|
|
308
|
-
return directories.filter((directory) => validatedSession(directory, loopRoot, beforeRead) !== null);
|
|
309
|
-
}
|
|
310
|
-
function readPending(session, beforeRead) {
|
|
311
|
-
const path = join(session.path, "pending-review.json");
|
|
312
|
-
const bytes = pinnedBytes(path, 16_384, session.authority, beforeRead);
|
|
313
|
-
if (bytes === null)
|
|
314
|
-
return null;
|
|
315
|
-
try {
|
|
316
|
-
const value = JSON.parse(bytes.toString("utf8"));
|
|
317
|
-
if (value.schema !== PENDING_SCHEMA ||
|
|
318
|
-
typeof value.sessionRef !== "string" ||
|
|
319
|
-
typeof value.createdAt !== "string" ||
|
|
320
|
-
(value.status !== "pending" && value.status !== "reviewed")) {
|
|
321
|
-
return null;
|
|
322
|
-
}
|
|
323
|
-
return value;
|
|
324
|
-
}
|
|
325
|
-
catch {
|
|
326
|
-
return null;
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
function writePending(session, pending, beforeWrite, afterWriteVerify) {
|
|
330
|
-
const path = join(session.path, "pending-review.json");
|
|
331
|
-
verifyDirectoryAuthority(session.authority);
|
|
332
|
-
beforeWrite?.(path);
|
|
333
|
-
verifyDirectoryAuthority(session.authority);
|
|
334
|
-
afterWriteVerify?.(path);
|
|
335
|
-
const pin = session.authority.at(-1);
|
|
336
|
-
if (pin === undefined)
|
|
337
|
-
throw new SkillLoopError("REVIEW_SESSION_UNSAFE", "the pending write authority is missing");
|
|
338
|
-
const content = `${JSON.stringify(pending, null, 2)}\n`;
|
|
339
|
-
if (Buffer.byteLength(content) > 16_384) {
|
|
340
|
-
throw new SkillLoopError("REVIEW_SESSION_LIMIT", "the pending review state exceeded its byte cap");
|
|
341
|
-
}
|
|
342
|
-
const result = spawnSync(process.execPath, [
|
|
343
|
-
"--input-type=module",
|
|
344
|
-
"--eval",
|
|
345
|
-
ANCHORED_PENDING_WRITE,
|
|
346
|
-
pin.realPath,
|
|
347
|
-
String(pin.dev),
|
|
348
|
-
String(pin.ino),
|
|
349
|
-
String(pin.uid),
|
|
350
|
-
String(pin.mode),
|
|
351
|
-
String(pin.birthtimeMs),
|
|
352
|
-
], {
|
|
353
|
-
cwd: session.path,
|
|
354
|
-
env: {},
|
|
355
|
-
input: content,
|
|
356
|
-
encoding: "utf8",
|
|
357
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
358
|
-
maxBuffer: 1_024,
|
|
359
|
-
timeout: 5_000,
|
|
360
|
-
});
|
|
361
|
-
if (result.error !== undefined || result.status !== 0) {
|
|
362
|
-
throw new SkillLoopError("REVIEW_SESSION_UNSAFE", "the anchored pending-state write was refused");
|
|
363
|
-
}
|
|
364
|
-
verifyDirectoryAuthority(session.authority);
|
|
365
|
-
}
|
|
366
|
-
function sourceDigest(session, beforeRead) {
|
|
367
|
-
const hash = createHash("sha256");
|
|
368
|
-
let aggregate = 0;
|
|
369
|
-
for (const [name, cap] of SOURCE_FILE_CAPS) {
|
|
370
|
-
const path = join(session.path, name);
|
|
371
|
-
const bytes = pinnedBytes(path, cap, session.authority, beforeRead);
|
|
372
|
-
if (bytes === null)
|
|
373
|
-
continue;
|
|
374
|
-
if (aggregate + bytes.byteLength > MAX_SESSION_SOURCE_BYTES) {
|
|
375
|
-
throw new SkillLoopError("REVIEW_SESSION_LIMIT", "a lit-loop session source exceeded its byte cap");
|
|
376
|
-
}
|
|
377
|
-
aggregate += bytes.byteLength;
|
|
378
|
-
hash.update(name);
|
|
379
|
-
hash.update(bytes);
|
|
380
|
-
}
|
|
381
|
-
return hash.digest("hex");
|
|
382
|
-
}
|
|
383
|
-
export function markPendingReviews(workspaceRoot, input, options = {}) {
|
|
384
|
-
void input;
|
|
385
|
-
if (options.env?.["LITFAMILY_SKILL_REVIEW"] === "1")
|
|
386
|
-
return { marked: 0, skipped: 0 };
|
|
387
|
-
let marked = 0;
|
|
388
|
-
let skipped = 0;
|
|
389
|
-
const loopRoot = join(resolve(workspaceRoot), ".litcodex", "lit-loop");
|
|
390
|
-
for (const session of sessionDirectories(workspaceRoot, options.beforeRead, options.afterEnumerate)) {
|
|
391
|
-
const existing = readPending(session, options.beforeRead);
|
|
392
|
-
const digest = sourceDigest(session, options.beforeRead);
|
|
393
|
-
if (existing !== null && (existing.status === "pending" || existing.sourceDigest === digest)) {
|
|
394
|
-
skipped += 1;
|
|
395
|
-
continue;
|
|
396
|
-
}
|
|
397
|
-
const sessionRef = validatedSession(session, loopRoot, options.beforeRead);
|
|
398
|
-
if (sessionRef === null) {
|
|
399
|
-
skipped += 1;
|
|
400
|
-
continue;
|
|
401
|
-
}
|
|
402
|
-
writePending(session, {
|
|
403
|
-
schema: PENDING_SCHEMA,
|
|
404
|
-
sessionRef,
|
|
405
|
-
createdAt: (options.now ?? (() => new Date()))().toISOString(),
|
|
406
|
-
status: "pending",
|
|
407
|
-
sourceDigest: digest,
|
|
408
|
-
}, options.beforeWrite, options.afterWriteVerify);
|
|
409
|
-
marked += 1;
|
|
410
|
-
}
|
|
411
|
-
return { marked, skipped };
|
|
412
|
-
}
|
|
413
|
-
export function pendingReviewNotice(workspaceRoot, options = {}) {
|
|
414
|
-
const pending = sessionDirectories(workspaceRoot, options.beforeRead, options.afterEnumerate)
|
|
415
|
-
.map((session) => ({ session, value: readPending(session, options.beforeRead) }))
|
|
416
|
-
.filter((item) => item.value !== null && item.value.status === "pending" && item.value.surfacedAt === undefined);
|
|
417
|
-
if (pending.length === 0)
|
|
418
|
-
return null;
|
|
419
|
-
const timestamp = (options.now ?? (() => new Date()))().toISOString();
|
|
420
|
-
for (const item of pending)
|
|
421
|
-
writePending(item.session, { ...item.value, surfacedAt: timestamp }, options.beforeWrite, options.afterWriteVerify);
|
|
422
|
-
return `${pending.length} skill review${pending.length === 1 ? "" : "s"} pending — run \`litcodex skill-loop review\`.`;
|
|
423
|
-
}
|
|
424
|
-
export function pendingReviewDirectories(workspaceRoot, options = {}) {
|
|
425
|
-
return sessionDirectories(workspaceRoot, options.beforeRead, options.afterEnumerate)
|
|
426
|
-
.filter((session) => readPending(session, options.beforeRead)?.status === "pending")
|
|
427
|
-
.map((session) => session.path);
|
|
428
|
-
}
|
|
429
|
-
export function markReviewsComplete(workspaceRoot, timestamp) {
|
|
430
|
-
for (const session of sessionDirectories(workspaceRoot)) {
|
|
431
|
-
const pending = readPending(session);
|
|
432
|
-
if (pending?.status === "pending")
|
|
433
|
-
writePending(session, { ...pending, status: "reviewed", reviewedAt: timestamp });
|
|
434
|
-
}
|
|
435
|
-
}
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import type { skillLoopPaths } from "./paths.js";
|
|
2
|
-
import type { SkillSnapshotItem } from "./types.js";
|
|
3
|
-
export declare function markerPresent(content: string): boolean;
|
|
4
|
-
export declare function stampMarker(content: string, skillId: string): string;
|
|
5
|
-
export declare function decodeSkillItem(paths: ReturnType<typeof skillLoopPaths>, item: SkillSnapshotItem): string;
|
|
6
|
-
export declare function snapshotIsAgentOwned(paths: ReturnType<typeof skillLoopPaths>, snapshot: readonly SkillSnapshotItem[]): boolean;
|
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
import { TextDecoder } from "node:util";
|
|
2
|
-
import { readBlob } from "./store.js";
|
|
3
|
-
import { SkillLoopError } from "./types.js";
|
|
4
|
-
const utf8 = new TextDecoder("utf-8", { fatal: true });
|
|
5
|
-
const MARKER_KEY = "litcodexAgentGenerated";
|
|
6
|
-
const metadataEntry = /^metadata\s*:\s*$/u;
|
|
7
|
-
const markerEntry = new RegExp(`^${MARKER_KEY}:\\s*(true|false)\\s*$`, "u");
|
|
8
|
-
const exactMetadataKeys = new Set(["metadata"]);
|
|
9
|
-
const exactMarkerKeys = new Set(["litcodexAgentGenerated"]);
|
|
10
|
-
function mappingKey(line) {
|
|
11
|
-
const indent = /^ */u.exec(line)?.[0].length ?? 0;
|
|
12
|
-
const body = line.slice(indent);
|
|
13
|
-
const colon = body.indexOf(":");
|
|
14
|
-
if (colon < 0)
|
|
15
|
-
return null;
|
|
16
|
-
return { indent, key: body.slice(0, colon).trim() };
|
|
17
|
-
}
|
|
18
|
-
function hasUnsafeSemanticAlias(line, expectedIndent) {
|
|
19
|
-
const parsed = mappingKey(line);
|
|
20
|
-
if (parsed === null || (expectedIndent !== undefined && parsed.indent !== expectedIndent))
|
|
21
|
-
return false;
|
|
22
|
-
if (parsed.key.includes("\\"))
|
|
23
|
-
return true;
|
|
24
|
-
const normalized = parsed.key.normalize("NFKC");
|
|
25
|
-
if (parsed.indent === 0 && normalized.toLocaleLowerCase("en-US").includes("metadata")) {
|
|
26
|
-
return !exactMetadataKeys.has(parsed.key);
|
|
27
|
-
}
|
|
28
|
-
if (normalized.toLocaleLowerCase("en-US").includes("litcodexagentgenerated")) {
|
|
29
|
-
return !exactMarkerKeys.has(parsed.key);
|
|
30
|
-
}
|
|
31
|
-
return false;
|
|
32
|
-
}
|
|
33
|
-
function hasUnsupportedYamlSyntax(line) {
|
|
34
|
-
const body = line.trim();
|
|
35
|
-
if (body === "" || body.startsWith("#"))
|
|
36
|
-
return false;
|
|
37
|
-
if (/^[?%{[]/u.test(body))
|
|
38
|
-
return true;
|
|
39
|
-
const parsed = mappingKey(line);
|
|
40
|
-
if (parsed === null)
|
|
41
|
-
return false;
|
|
42
|
-
if (parsed.key === "<<" || /^[&*!{[]/u.test(parsed.key))
|
|
43
|
-
return true;
|
|
44
|
-
const colon = line.indexOf(":");
|
|
45
|
-
const value = colon < 0 ? "" : line.slice(colon + 1).trimStart();
|
|
46
|
-
return (/^(?:[!{[]|\*[^\s]+)/u.test(value) ||
|
|
47
|
-
/(?:^|\s)&[A-Za-z0-9_-]+(?:\s|$)/u.test(value) ||
|
|
48
|
-
/(?:^|\s)\*[A-Za-z0-9_-]+(?:\s|$)/u.test(value));
|
|
49
|
-
}
|
|
50
|
-
export function markerPresent(content) {
|
|
51
|
-
if (!content.startsWith("---\n"))
|
|
52
|
-
return false;
|
|
53
|
-
const lines = content.split("\n");
|
|
54
|
-
const end = lines.indexOf("---", 1);
|
|
55
|
-
if (end < 0)
|
|
56
|
-
return false;
|
|
57
|
-
const frontmatter = lines.slice(1, end);
|
|
58
|
-
if (frontmatter.some((line) => hasUnsupportedYamlSyntax(line) || hasUnsafeSemanticAlias(line)))
|
|
59
|
-
return false;
|
|
60
|
-
const metadataEntries = frontmatter
|
|
61
|
-
.map((line, index) => ({ line, index }))
|
|
62
|
-
.filter(({ line }) => /^metadata\s*:/u.test(line));
|
|
63
|
-
if (metadataEntries.length !== 1 || !metadataEntry.test(metadataEntries[0]?.line ?? ""))
|
|
64
|
-
return false;
|
|
65
|
-
const metadata = metadataEntries[0]?.index;
|
|
66
|
-
if (metadata === undefined)
|
|
67
|
-
return false;
|
|
68
|
-
let directIndent;
|
|
69
|
-
let markerCount = 0;
|
|
70
|
-
let markerIsTrue = false;
|
|
71
|
-
for (let index = metadata + 1; index < frontmatter.length; index += 1) {
|
|
72
|
-
const line = frontmatter[index] ?? "";
|
|
73
|
-
if (line.trim() === "" || line.trimStart().startsWith("#"))
|
|
74
|
-
continue;
|
|
75
|
-
const indent = /^ */u.exec(line)?.[0].length ?? 0;
|
|
76
|
-
if (indent === 0)
|
|
77
|
-
break;
|
|
78
|
-
directIndent ??= indent;
|
|
79
|
-
if (indent === directIndent) {
|
|
80
|
-
if (hasUnsafeSemanticAlias(line, directIndent))
|
|
81
|
-
return false;
|
|
82
|
-
const direct = line.slice(indent);
|
|
83
|
-
if (mappingKey(line)?.key !== MARKER_KEY)
|
|
84
|
-
continue;
|
|
85
|
-
markerCount += 1;
|
|
86
|
-
const marker = markerEntry.exec(direct);
|
|
87
|
-
if (marker === null || marker[1] !== "true")
|
|
88
|
-
return false;
|
|
89
|
-
markerIsTrue = true;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
return markerCount === 1 && markerIsTrue;
|
|
93
|
-
}
|
|
94
|
-
export function stampMarker(content, skillId) {
|
|
95
|
-
if (markerPresent(content))
|
|
96
|
-
return content;
|
|
97
|
-
if (!content.startsWith("---\n")) {
|
|
98
|
-
return `---\nname: ${skillId}\ndescription: Agent-owned reusable skill.\nmetadata:\n litcodexAgentGenerated: true\n---\n\n${content}`;
|
|
99
|
-
}
|
|
100
|
-
const lines = content.split("\n");
|
|
101
|
-
const end = lines.indexOf("---", 1);
|
|
102
|
-
if (end < 0)
|
|
103
|
-
throw new SkillLoopError("TARGET_MARKER_INVALID", "SKILL.md frontmatter is incomplete");
|
|
104
|
-
const frontmatter = lines.slice(1, end);
|
|
105
|
-
if (frontmatter.some((line) => hasUnsupportedYamlSyntax(line) || hasUnsafeSemanticAlias(line))) {
|
|
106
|
-
throw new SkillLoopError("TARGET_MARKER_INVALID", "SKILL.md metadata contains a noncanonical YAML key");
|
|
107
|
-
}
|
|
108
|
-
const metadataEntries = frontmatter
|
|
109
|
-
.map((line, index) => ({ line, index }))
|
|
110
|
-
.filter(({ line }) => /^metadata\s*:/u.test(line));
|
|
111
|
-
if (metadataEntries.length > 1 ||
|
|
112
|
-
(metadataEntries[0] !== undefined && !metadataEntry.test(metadataEntries[0].line))) {
|
|
113
|
-
throw new SkillLoopError("TARGET_MARKER_INVALID", "SKILL.md metadata must be a direct YAML mapping");
|
|
114
|
-
}
|
|
115
|
-
const metadata = metadataEntries[0]?.index;
|
|
116
|
-
if (metadata === undefined)
|
|
117
|
-
frontmatter.push("metadata:", " litcodexAgentGenerated: true");
|
|
118
|
-
else {
|
|
119
|
-
let indent = " ";
|
|
120
|
-
for (let index = metadata + 1; index < frontmatter.length; index += 1) {
|
|
121
|
-
const line = frontmatter[index] ?? "";
|
|
122
|
-
if (line.trim() === "" || line.trimStart().startsWith("#"))
|
|
123
|
-
continue;
|
|
124
|
-
const current = /^ */u.exec(line)?.[0] ?? "";
|
|
125
|
-
if (current.length === 0)
|
|
126
|
-
break;
|
|
127
|
-
indent = current;
|
|
128
|
-
break;
|
|
129
|
-
}
|
|
130
|
-
frontmatter.splice(metadata + 1, 0, `${indent}litcodexAgentGenerated: true`);
|
|
131
|
-
}
|
|
132
|
-
return ["---", ...frontmatter, "---", ...lines.slice(end + 1)].join("\n");
|
|
133
|
-
}
|
|
134
|
-
export function decodeSkillItem(paths, item) {
|
|
135
|
-
try {
|
|
136
|
-
return utf8.decode(readBlob(paths, item));
|
|
137
|
-
}
|
|
138
|
-
catch {
|
|
139
|
-
throw new SkillLoopError("TARGET_TEXT_INVALID", "the target skill file is not valid UTF-8");
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
export function snapshotIsAgentOwned(paths, snapshot) {
|
|
143
|
-
const skillFile = snapshot.find((item) => item.file === "SKILL.md");
|
|
144
|
-
return skillFile !== undefined && markerPresent(decodeSkillItem(paths, skillFile));
|
|
145
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
export interface SkillLoopPaths {
|
|
2
|
-
readonly workspaceRoot: string;
|
|
3
|
-
readonly stateRoot: string;
|
|
4
|
-
readonly proposalsDir: string;
|
|
5
|
-
readonly ledgerPath: string;
|
|
6
|
-
readonly blobsDir: string;
|
|
7
|
-
readonly lockPath: string;
|
|
8
|
-
readonly agentMutationLockPath: string;
|
|
9
|
-
readonly transactionPath: string;
|
|
10
|
-
readonly agentRoot: string;
|
|
11
|
-
readonly usagePath: string;
|
|
12
|
-
readonly archiveRoot: string;
|
|
13
|
-
readonly curatorBackupsRoot: string;
|
|
14
|
-
}
|
|
15
|
-
export declare function agentSkillRoot(env?: NodeJS.ProcessEnv): string;
|
|
16
|
-
export declare function skillLoopPaths(workspaceRoot: string, env?: NodeJS.ProcessEnv): SkillLoopPaths;
|
|
17
|
-
export declare function isWithin(root: string, candidate: string): boolean;
|
|
18
|
-
export declare function targetSkillDir(paths: SkillLoopPaths, skillId: string): string;
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { isAbsolute, join, resolve, sep } from "node:path";
|
|
2
|
-
import { SkillLoopError } from "./types.js";
|
|
3
|
-
export function agentSkillRoot(env = process.env) {
|
|
4
|
-
const home = env["HOME"]?.trim();
|
|
5
|
-
if (!home || !isAbsolute(home)) {
|
|
6
|
-
throw new SkillLoopError("HOME_INVALID", "the skill-loop HOME is unavailable");
|
|
7
|
-
}
|
|
8
|
-
return resolve(home, ".agents", "skills");
|
|
9
|
-
}
|
|
10
|
-
export function skillLoopPaths(workspaceRoot, env = process.env) {
|
|
11
|
-
if (!isAbsolute(workspaceRoot)) {
|
|
12
|
-
throw new SkillLoopError("WORKSPACE_ROOT_INVALID", "the skill-loop workspace root must be absolute");
|
|
13
|
-
}
|
|
14
|
-
const root = resolve(workspaceRoot);
|
|
15
|
-
const stateRoot = join(root, ".litcodex");
|
|
16
|
-
const agentRoot = agentSkillRoot(env);
|
|
17
|
-
return {
|
|
18
|
-
workspaceRoot: root,
|
|
19
|
-
stateRoot,
|
|
20
|
-
proposalsDir: join(stateRoot, "skill-proposals"),
|
|
21
|
-
ledgerPath: join(stateRoot, "skill-ledger.jsonl"),
|
|
22
|
-
blobsDir: join(stateRoot, "skill-ledger-blobs"),
|
|
23
|
-
lockPath: join(stateRoot, "skill-loop.lock"),
|
|
24
|
-
agentMutationLockPath: join(resolve(agentRoot, ".."), ".litcodex-skill-loop-mutation.lock"),
|
|
25
|
-
transactionPath: join(stateRoot, "skill-transaction.json"),
|
|
26
|
-
agentRoot,
|
|
27
|
-
usagePath: join(agentRoot, ".litcodex-usage.json"),
|
|
28
|
-
archiveRoot: join(agentRoot, ".litcodex-archive"),
|
|
29
|
-
curatorBackupsRoot: join(agentRoot, ".litcodex-curator-backups"),
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
export function isWithin(root, candidate) {
|
|
33
|
-
const normalizedRoot = resolve(root);
|
|
34
|
-
const normalizedCandidate = resolve(candidate);
|
|
35
|
-
return normalizedCandidate === normalizedRoot || normalizedCandidate.startsWith(`${normalizedRoot}${sep}`);
|
|
36
|
-
}
|
|
37
|
-
export function targetSkillDir(paths, skillId) {
|
|
38
|
-
const target = resolve(paths.agentRoot, skillId);
|
|
39
|
-
if (!isWithin(paths.agentRoot, target) || target === paths.agentRoot) {
|
|
40
|
-
throw new SkillLoopError("TARGET_NOT_AGENT_OWNED", "the proposal target is not agent-owned");
|
|
41
|
-
}
|
|
42
|
-
return target;
|
|
43
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
export interface ReviewOptions {
|
|
2
|
-
readonly env?: NodeJS.ProcessEnv | undefined;
|
|
3
|
-
readonly now?: () => Date;
|
|
4
|
-
readonly spawn?: ReviewSpawn;
|
|
5
|
-
/** Test-only race seam; production callers leave this unset. */
|
|
6
|
-
readonly beforeRead?: ((path: string) => void) | undefined;
|
|
7
|
-
}
|
|
8
|
-
export interface ReviewSpawnOptions {
|
|
9
|
-
readonly cwd: string;
|
|
10
|
-
readonly env: NodeJS.ProcessEnv;
|
|
11
|
-
readonly encoding: "utf8";
|
|
12
|
-
readonly stdio: ["ignore", "pipe", "pipe"];
|
|
13
|
-
readonly maxBuffer: number;
|
|
14
|
-
readonly timeout: number;
|
|
15
|
-
}
|
|
16
|
-
export interface ReviewSpawnResult {
|
|
17
|
-
readonly status: number | null;
|
|
18
|
-
readonly stdout: string;
|
|
19
|
-
readonly stderr: string;
|
|
20
|
-
readonly error?: Error;
|
|
21
|
-
}
|
|
22
|
-
export type ReviewSpawn = (command: string, args: readonly string[], options: ReviewSpawnOptions) => ReviewSpawnResult;
|
|
23
|
-
export declare function buildReviewPacket(workspaceRoot: string, options?: Pick<ReviewOptions, "env" | "beforeRead">): string;
|
|
24
|
-
export declare function runCodexReview(workspaceRoot: string, options?: ReviewOptions): {
|
|
25
|
-
readonly ok: true;
|
|
26
|
-
readonly proposalIds: readonly string[];
|
|
27
|
-
readonly noOp: boolean;
|
|
28
|
-
};
|