@xiongxianfei/rigorloop 0.1.5 → 0.3.0
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 +111 -2
- package/dist/bin/rigorloop.js +818 -221
- package/dist/lib/adapters.js +50 -0
- package/dist/lib/lockfile.js +268 -33
- package/dist/metadata/adapter-artifacts-v0.2.0.json +31 -0
- package/dist/metadata/adapter-artifacts-v0.3.0.json +65 -0
- package/dist/metadata/releases.json +4 -4
- package/package.json +22 -2
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const ADAPTERS = {
|
|
2
|
+
codex: {
|
|
3
|
+
name: "codex",
|
|
4
|
+
displayName: "Codex",
|
|
5
|
+
installRoots: {
|
|
6
|
+
skills: ".agents/skills",
|
|
7
|
+
},
|
|
8
|
+
directoryPlan: [".agents", ".agents/skills"],
|
|
9
|
+
},
|
|
10
|
+
claude: {
|
|
11
|
+
name: "claude",
|
|
12
|
+
displayName: "Claude Code",
|
|
13
|
+
installRoots: {
|
|
14
|
+
skills: ".claude/skills",
|
|
15
|
+
},
|
|
16
|
+
directoryPlan: [".claude", ".claude/skills"],
|
|
17
|
+
},
|
|
18
|
+
opencode: {
|
|
19
|
+
name: "opencode",
|
|
20
|
+
displayName: "opencode",
|
|
21
|
+
installRoots: {
|
|
22
|
+
skills: ".opencode/skills",
|
|
23
|
+
commands: ".opencode/commands",
|
|
24
|
+
},
|
|
25
|
+
directoryPlan: [".opencode", ".opencode/skills", ".opencode/commands"],
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
function cloneDescriptor(descriptor) {
|
|
30
|
+
return {
|
|
31
|
+
...descriptor,
|
|
32
|
+
installRoots: { ...descriptor.installRoots },
|
|
33
|
+
directoryPlan: [...descriptor.directoryPlan],
|
|
34
|
+
archiveName(releaseTag) {
|
|
35
|
+
return `rigorloop-adapter-${descriptor.name}-${releaseTag}.zip`;
|
|
36
|
+
},
|
|
37
|
+
primaryInstallRoot() {
|
|
38
|
+
return descriptor.installRoots.skills;
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function adapterDescriptor(name) {
|
|
44
|
+
const descriptor = ADAPTERS[name];
|
|
45
|
+
return descriptor ? cloneDescriptor(descriptor) : undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function supportedAdapterNames() {
|
|
49
|
+
return Object.keys(ADAPTERS);
|
|
50
|
+
}
|
package/dist/lib/lockfile.js
CHANGED
|
@@ -2,10 +2,11 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
|
|
3
3
|
const SHA256_PATTERN = /^[0-9a-f]{64}$/i;
|
|
4
4
|
const SUPPORTED_SOURCES = new Set(["release-archive", "local-archive"]);
|
|
5
|
+
const SUPPORTED_ADAPTERS = new Set(["codex", "claude", "opencode"]);
|
|
5
6
|
const TOP_LEVEL_FIELDS = ["schema_version", "rigorloop", "manifest", "generated"];
|
|
6
7
|
const RIGORLOOP_FIELDS = ["package", "version"];
|
|
7
8
|
const MANIFEST_FIELDS = ["path", "sha256"];
|
|
8
|
-
const GENERATED_FIELDS = ["adapters"];
|
|
9
|
+
const GENERATED_FIELDS = ["adapters", "targets"];
|
|
9
10
|
const ADAPTER_FIELDS = [
|
|
10
11
|
"adapter",
|
|
11
12
|
"release",
|
|
@@ -13,10 +14,20 @@ const ADAPTER_FIELDS = [
|
|
|
13
14
|
"archive",
|
|
14
15
|
"archive_sha256",
|
|
15
16
|
"installed_root",
|
|
17
|
+
"installed_roots",
|
|
16
18
|
"tree_hash_algorithm",
|
|
17
19
|
"tree_sha256",
|
|
18
20
|
"file_count",
|
|
21
|
+
"root_hashes",
|
|
19
22
|
];
|
|
23
|
+
const SINGLE_ROOTS = {
|
|
24
|
+
codex: ".agents/skills",
|
|
25
|
+
claude: ".claude/skills",
|
|
26
|
+
};
|
|
27
|
+
const OPENCODE_ROOTS = {
|
|
28
|
+
skills: ".opencode/skills",
|
|
29
|
+
commands: ".opencode/commands",
|
|
30
|
+
};
|
|
20
31
|
|
|
21
32
|
function failure(kind, code, message, path = "rigorloop.lock") {
|
|
22
33
|
return { ok: false, kind, code, message, path };
|
|
@@ -38,6 +49,11 @@ function isSha256(value) {
|
|
|
38
49
|
return typeof value === "string" && SHA256_PATTERN.test(value);
|
|
39
50
|
}
|
|
40
51
|
|
|
52
|
+
function isNonNegativeInteger(value) {
|
|
53
|
+
const text = String(value);
|
|
54
|
+
return /^\d+$/.test(text) && Number.isInteger(Number.parseInt(text, 10));
|
|
55
|
+
}
|
|
56
|
+
|
|
41
57
|
function parseTopLevel(lines) {
|
|
42
58
|
const sections = new Map();
|
|
43
59
|
for (const line of lines) {
|
|
@@ -63,6 +79,10 @@ function parseSection(lines, sectionName, allowedFields) {
|
|
|
63
79
|
if (line.trim() === "") {
|
|
64
80
|
continue;
|
|
65
81
|
}
|
|
82
|
+
const nestedMatch = line.match(/^ ([A-Za-z_][A-Za-z0-9_-]*):\s*$/);
|
|
83
|
+
if (nestedMatch) {
|
|
84
|
+
return failure("unsupported", "unsupported-lockfile-shape", `Unsupported nested field ${sectionName}.${nestedMatch[1]}`);
|
|
85
|
+
}
|
|
66
86
|
const match = line.match(/^ ([A-Za-z_][A-Za-z0-9_-]*):(?:\s*(.*))?$/);
|
|
67
87
|
if (!match) {
|
|
68
88
|
continue;
|
|
@@ -112,6 +132,18 @@ function parseAdapters(lines) {
|
|
|
112
132
|
|
|
113
133
|
const adapters = [];
|
|
114
134
|
let current;
|
|
135
|
+
let nested;
|
|
136
|
+
let rootHashRole;
|
|
137
|
+
|
|
138
|
+
function pushCurrent() {
|
|
139
|
+
if (current) {
|
|
140
|
+
adapters.push(current);
|
|
141
|
+
}
|
|
142
|
+
current = undefined;
|
|
143
|
+
nested = undefined;
|
|
144
|
+
rootHashRole = undefined;
|
|
145
|
+
}
|
|
146
|
+
|
|
115
147
|
for (let index = adaptersStart + 1; index < lines.length; index += 1) {
|
|
116
148
|
const line = lines[index];
|
|
117
149
|
if (/^[A-Za-z_][A-Za-z0-9_-]*:/.test(line)) {
|
|
@@ -124,11 +156,10 @@ function parseAdapters(lines) {
|
|
|
124
156
|
if (generatedFieldMatch) {
|
|
125
157
|
return failure("unsupported", "unsupported-lockfile-shape", `Unsupported field generated.${generatedFieldMatch[1]}`);
|
|
126
158
|
}
|
|
159
|
+
|
|
127
160
|
const startMatch = line.match(/^ - ([A-Za-z_][A-Za-z0-9_-]*):(?:\s*(.*))?$/);
|
|
128
161
|
if (startMatch) {
|
|
129
|
-
|
|
130
|
-
adapters.push(current);
|
|
131
|
-
}
|
|
162
|
+
pushCurrent();
|
|
132
163
|
current = {};
|
|
133
164
|
const [, key, value] = startMatch;
|
|
134
165
|
if (!ADAPTER_FIELDS.includes(key)) {
|
|
@@ -147,26 +178,165 @@ function parseAdapters(lines) {
|
|
|
147
178
|
if (!ADAPTER_FIELDS.includes(key)) {
|
|
148
179
|
return failure("unsupported", "unsupported-lockfile-shape", `Unsupported field generated.adapters[${adapters.length}].${key}`);
|
|
149
180
|
}
|
|
181
|
+
if (key === "installed_roots" || key === "root_hashes") {
|
|
182
|
+
if (value !== undefined && value !== "") {
|
|
183
|
+
return failure("invalid", "invalid-lockfile", `Expected mapping for generated.adapters[${adapters.length}].${key}`);
|
|
184
|
+
}
|
|
185
|
+
current[key] = {};
|
|
186
|
+
nested = key;
|
|
187
|
+
rootHashRole = undefined;
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
150
190
|
if (value === undefined || value === "") {
|
|
151
191
|
return failure("invalid", "invalid-lockfile", `Missing scalar value for generated.adapters[${adapters.length}].${key}`);
|
|
152
192
|
}
|
|
153
193
|
current[key] = parseScalar(value);
|
|
194
|
+
nested = undefined;
|
|
195
|
+
rootHashRole = undefined;
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const installedRootMatch = line.match(/^ ([A-Za-z_][A-Za-z0-9_-]*):\s*(.*)$/);
|
|
200
|
+
if (installedRootMatch && current && nested === "installed_roots") {
|
|
201
|
+
current.installed_roots[installedRootMatch[1]] = parseScalar(installedRootMatch[2]);
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const rootHashRoleMatch = line.match(/^ ([A-Za-z_][A-Za-z0-9_-]*):\s*$/);
|
|
206
|
+
if (rootHashRoleMatch && current && nested === "root_hashes") {
|
|
207
|
+
rootHashRole = rootHashRoleMatch[1];
|
|
208
|
+
current.root_hashes[rootHashRole] = {};
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const rootHashFieldMatch = line.match(/^ ([A-Za-z_][A-Za-z0-9_-]*):\s*(.*)$/);
|
|
213
|
+
if (rootHashFieldMatch && current && nested === "root_hashes" && rootHashRole) {
|
|
214
|
+
const [, key, value] = rootHashFieldMatch;
|
|
215
|
+
if (!["tree_sha256", "file_count"].includes(key)) {
|
|
216
|
+
return failure("unsupported", "unsupported-lockfile-shape", `Unsupported field generated.adapters[${adapters.length}].root_hashes.${rootHashRole}.${key}`);
|
|
217
|
+
}
|
|
218
|
+
current.root_hashes[rootHashRole][key] = key === "file_count" ? Number.parseInt(parseScalar(value), 10) : parseScalar(value);
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (line.startsWith(" ") && current) {
|
|
223
|
+
return failure("unsupported", "unsupported-lockfile-shape", `Unsupported lockfile adapter mapping near: ${line.trim()}`);
|
|
154
224
|
}
|
|
155
225
|
}
|
|
156
|
-
|
|
157
|
-
adapters.push(current);
|
|
158
|
-
}
|
|
226
|
+
pushCurrent();
|
|
159
227
|
return { adapters };
|
|
160
228
|
}
|
|
161
229
|
|
|
162
|
-
function
|
|
163
|
-
|
|
230
|
+
function parseTargets(lines) {
|
|
231
|
+
const transformed = lines.map((line) => {
|
|
232
|
+
if (line === " targets:") {
|
|
233
|
+
return " adapters:";
|
|
234
|
+
}
|
|
235
|
+
return line.replace(/^ - target:/, " - adapter:");
|
|
236
|
+
});
|
|
237
|
+
const parsed = parseAdapters(transformed);
|
|
238
|
+
if (parsed.ok === false || parsed.missing) {
|
|
239
|
+
return parsed;
|
|
240
|
+
}
|
|
241
|
+
return {
|
|
242
|
+
targets: parsed.adapters.map((adapter) => {
|
|
243
|
+
const { adapter: target, ...rest } = adapter;
|
|
244
|
+
return { target, ...rest };
|
|
245
|
+
}),
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function unexpectedFields(adapter, allowed) {
|
|
250
|
+
return Object.keys(adapter).filter((field) => !allowed.includes(field));
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function validateSingleRootAdapter(adapter, schemaVersion) {
|
|
254
|
+
const allowed = [
|
|
255
|
+
"adapter",
|
|
256
|
+
"release",
|
|
257
|
+
"source",
|
|
258
|
+
"archive",
|
|
259
|
+
"archive_sha256",
|
|
260
|
+
"installed_root",
|
|
261
|
+
"tree_hash_algorithm",
|
|
262
|
+
"tree_sha256",
|
|
263
|
+
"file_count",
|
|
264
|
+
];
|
|
265
|
+
const unexpected = unexpectedFields(adapter, allowed);
|
|
266
|
+
if (unexpected.length) {
|
|
267
|
+
return failure("unsupported", "unsupported-lockfile-shape", `Unsupported field generated.adapters[].${unexpected[0]}`);
|
|
268
|
+
}
|
|
269
|
+
for (const field of allowed) {
|
|
270
|
+
if (adapter[field] === undefined) {
|
|
271
|
+
return failure("invalid", "invalid-lockfile", `Missing adapter field: ${field}`);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (adapter.adapter === "opencode") {
|
|
275
|
+
return failure("unsupported", "unsupported-lockfile-shape", "opencode lockfile entries must use installed_roots.");
|
|
276
|
+
}
|
|
277
|
+
if (schemaVersion === 1 && adapter.adapter !== "codex") {
|
|
278
|
+
return failure("unsupported", "unsupported-lockfile-shape", "schema_version 1 supports only Codex lockfile entries.");
|
|
279
|
+
}
|
|
280
|
+
if (adapter.installed_root !== SINGLE_ROOTS[adapter.adapter]) {
|
|
281
|
+
return failure("unsupported", "unsupported-lockfile-shape", "Unsupported installed root.");
|
|
282
|
+
}
|
|
283
|
+
if (!isSha256(adapter.tree_sha256)) {
|
|
284
|
+
return failure("invalid", "invalid-lockfile", "Adapter tree hash must be a SHA-256 value.");
|
|
285
|
+
}
|
|
286
|
+
if (!isNonNegativeInteger(adapter.file_count)) {
|
|
287
|
+
return failure("invalid", "invalid-lockfile", "Adapter file_count must be a non-negative integer.");
|
|
288
|
+
}
|
|
289
|
+
adapter.file_count = Number.parseInt(adapter.file_count, 10);
|
|
290
|
+
return undefined;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function validateMultiRootAdapter(adapter) {
|
|
294
|
+
const allowed = [
|
|
295
|
+
"adapter",
|
|
296
|
+
"release",
|
|
297
|
+
"source",
|
|
298
|
+
"archive",
|
|
299
|
+
"archive_sha256",
|
|
300
|
+
"tree_hash_algorithm",
|
|
301
|
+
"installed_roots",
|
|
302
|
+
"root_hashes",
|
|
303
|
+
];
|
|
304
|
+
const unexpected = unexpectedFields(adapter, allowed);
|
|
305
|
+
if (unexpected.length) {
|
|
306
|
+
return failure("unsupported", "unsupported-lockfile-shape", `Unsupported field generated.adapters[].${unexpected[0]}`);
|
|
307
|
+
}
|
|
308
|
+
for (const field of allowed) {
|
|
164
309
|
if (adapter[field] === undefined) {
|
|
165
310
|
return failure("invalid", "invalid-lockfile", `Missing adapter field: ${field}`);
|
|
166
311
|
}
|
|
167
312
|
}
|
|
168
|
-
if (adapter.adapter !== "
|
|
169
|
-
return failure("unsupported", "unsupported-lockfile-shape", "Only
|
|
313
|
+
if (adapter.adapter !== "opencode") {
|
|
314
|
+
return failure("unsupported", "unsupported-lockfile-shape", "Only opencode supports multi-root lockfile entries.");
|
|
315
|
+
}
|
|
316
|
+
const rootRoles = Object.keys(adapter.installed_roots).sort();
|
|
317
|
+
const hashRoles = Object.keys(adapter.root_hashes).sort();
|
|
318
|
+
if (!rootRoles.length || rootRoles.join("\n") !== hashRoles.join("\n")) {
|
|
319
|
+
return failure("invalid", "invalid-lockfile", "installed_roots and root_hashes roles must match.");
|
|
320
|
+
}
|
|
321
|
+
for (const role of rootRoles) {
|
|
322
|
+
if (!Object.hasOwn(OPENCODE_ROOTS, role) || adapter.installed_roots[role] !== OPENCODE_ROOTS[role]) {
|
|
323
|
+
return failure("unsupported", "unsupported-lockfile-shape", "Unsupported opencode installed root.");
|
|
324
|
+
}
|
|
325
|
+
const hash = adapter.root_hashes[role];
|
|
326
|
+
if (!hash || !isSha256(hash.tree_sha256)) {
|
|
327
|
+
return failure("invalid", "invalid-lockfile", "Adapter root hash must be a SHA-256 value.");
|
|
328
|
+
}
|
|
329
|
+
if (!isNonNegativeInteger(hash.file_count)) {
|
|
330
|
+
return failure("invalid", "invalid-lockfile", "Adapter root file_count must be a non-negative integer.");
|
|
331
|
+
}
|
|
332
|
+
hash.file_count = Number.parseInt(hash.file_count, 10);
|
|
333
|
+
}
|
|
334
|
+
return undefined;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function validateAdapter(adapter, schemaVersion) {
|
|
338
|
+
if (!SUPPORTED_ADAPTERS.has(adapter.adapter)) {
|
|
339
|
+
return failure("unsupported", "unsupported-lockfile-shape", "Unsupported lockfile adapter.");
|
|
170
340
|
}
|
|
171
341
|
if (!SUPPORTED_SOURCES.has(adapter.source)) {
|
|
172
342
|
return failure("unsupported", "unsupported-lockfile-shape", "Unsupported lockfile adapter source.");
|
|
@@ -174,17 +344,27 @@ function validateAdapter(adapter) {
|
|
|
174
344
|
if (adapter.tree_hash_algorithm !== "rigorloop-tree-hash-v1") {
|
|
175
345
|
return failure("unsupported", "unsupported-lockfile-shape", "Unsupported tree hash algorithm.");
|
|
176
346
|
}
|
|
177
|
-
if (adapter.
|
|
178
|
-
return failure("
|
|
347
|
+
if (!isSha256(adapter.archive_sha256)) {
|
|
348
|
+
return failure("invalid", "invalid-lockfile", "Adapter archive hash must be a SHA-256 value.");
|
|
179
349
|
}
|
|
180
|
-
if (
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
return failure("invalid", "invalid-lockfile", "Adapter file_count must be a non-negative integer.");
|
|
350
|
+
if (adapter.installed_roots !== undefined || adapter.root_hashes !== undefined) {
|
|
351
|
+
if (schemaVersion !== 2) {
|
|
352
|
+
return failure("unsupported", "unsupported-lockfile-shape", "Multi-root lockfile entries require schema_version 2.");
|
|
353
|
+
}
|
|
354
|
+
return validateMultiRootAdapter(adapter);
|
|
186
355
|
}
|
|
187
|
-
adapter
|
|
356
|
+
return validateSingleRootAdapter(adapter, schemaVersion);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function validateTarget(target) {
|
|
360
|
+
const adapter = { ...target, adapter: target.target };
|
|
361
|
+
delete adapter.target;
|
|
362
|
+
const error = validateAdapter(adapter, 2);
|
|
363
|
+
if (error) {
|
|
364
|
+
return error;
|
|
365
|
+
}
|
|
366
|
+
const { adapter: normalizedTarget, ...rest } = adapter;
|
|
367
|
+
Object.assign(target, { target: normalizedTarget, ...rest });
|
|
188
368
|
return undefined;
|
|
189
369
|
}
|
|
190
370
|
|
|
@@ -193,7 +373,7 @@ export function parseLockfile(text) {
|
|
|
193
373
|
return failure("invalid", "invalid-lockfile", "rigorloop.lock is empty or not text.");
|
|
194
374
|
}
|
|
195
375
|
if (/[\[\]{}]/.test(text)) {
|
|
196
|
-
return failure("invalid", "invalid-lockfile", "rigorloop.lock is not valid strict
|
|
376
|
+
return failure("invalid", "invalid-lockfile", "rigorloop.lock is not valid strict YAML.");
|
|
197
377
|
}
|
|
198
378
|
|
|
199
379
|
const lines = text.replace(/\r\n?/g, "\n").split("\n");
|
|
@@ -209,7 +389,8 @@ export function parseLockfile(text) {
|
|
|
209
389
|
}
|
|
210
390
|
}
|
|
211
391
|
const schemaVersion = parseScalar(top.get("schema_version"));
|
|
212
|
-
|
|
392
|
+
const parsedSchemaVersion = Number.parseInt(schemaVersion, 10);
|
|
393
|
+
if (!/^\d+$/.test(schemaVersion) || ![1, 2, 3].includes(parsedSchemaVersion)) {
|
|
213
394
|
return failure("unsupported", "unsupported-lockfile-shape", "Unsupported lockfile schema_version.");
|
|
214
395
|
}
|
|
215
396
|
|
|
@@ -235,6 +416,33 @@ export function parseLockfile(text) {
|
|
|
235
416
|
return failure("invalid", "invalid-lockfile", "Invalid manifest lockfile entry.");
|
|
236
417
|
}
|
|
237
418
|
|
|
419
|
+
if (parsedSchemaVersion === 3) {
|
|
420
|
+
const targetResult = parseTargets(lines);
|
|
421
|
+
if (targetResult.ok === false) {
|
|
422
|
+
return targetResult;
|
|
423
|
+
}
|
|
424
|
+
if (targetResult.missing || !targetResult.targets.length) {
|
|
425
|
+
return failure("invalid", "invalid-lockfile", "generated.targets must contain at least one target entry.");
|
|
426
|
+
}
|
|
427
|
+
for (const target of targetResult.targets) {
|
|
428
|
+
const targetError = validateTarget(target);
|
|
429
|
+
if (targetError) {
|
|
430
|
+
return targetError;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
return {
|
|
434
|
+
ok: true,
|
|
435
|
+
lockfile: {
|
|
436
|
+
schema_version: parsedSchemaVersion,
|
|
437
|
+
rigorloop: rigorloop.fields,
|
|
438
|
+
manifest: manifest.fields,
|
|
439
|
+
generated: {
|
|
440
|
+
targets: targetResult.targets,
|
|
441
|
+
},
|
|
442
|
+
},
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
|
|
238
446
|
const adapterResult = parseAdapters(lines);
|
|
239
447
|
if (adapterResult.ok === false) {
|
|
240
448
|
return adapterResult;
|
|
@@ -243,7 +451,7 @@ export function parseLockfile(text) {
|
|
|
243
451
|
return failure("invalid", "invalid-lockfile", "generated.adapters must contain at least one adapter entry.");
|
|
244
452
|
}
|
|
245
453
|
for (const adapter of adapterResult.adapters) {
|
|
246
|
-
const adapterError = validateAdapter(adapter);
|
|
454
|
+
const adapterError = validateAdapter(adapter, parsedSchemaVersion);
|
|
247
455
|
if (adapterError) {
|
|
248
456
|
return adapterError;
|
|
249
457
|
}
|
|
@@ -252,7 +460,7 @@ export function parseLockfile(text) {
|
|
|
252
460
|
return {
|
|
253
461
|
ok: true,
|
|
254
462
|
lockfile: {
|
|
255
|
-
schema_version:
|
|
463
|
+
schema_version: parsedSchemaVersion,
|
|
256
464
|
rigorloop: rigorloop.fields,
|
|
257
465
|
manifest: manifest.fields,
|
|
258
466
|
generated: {
|
|
@@ -263,9 +471,19 @@ export function parseLockfile(text) {
|
|
|
263
471
|
}
|
|
264
472
|
|
|
265
473
|
export function serializeLockfile(lockfile) {
|
|
266
|
-
const
|
|
474
|
+
const isTargetSchema = lockfile.schema_version === 3;
|
|
475
|
+
const sourceEntries = lockfile.generated.targets ?? lockfile.generated.adapters;
|
|
476
|
+
const entries = [...sourceEntries].map((entry) => {
|
|
477
|
+
if (isTargetSchema || entry.adapter) {
|
|
478
|
+
return entry;
|
|
479
|
+
}
|
|
480
|
+
const { target, ...rest } = entry;
|
|
481
|
+
return { adapter: target, ...rest };
|
|
482
|
+
}).sort((left, right) =>
|
|
483
|
+
(left.target ?? left.adapter).localeCompare(right.target ?? right.adapter),
|
|
484
|
+
);
|
|
267
485
|
const lines = [
|
|
268
|
-
|
|
486
|
+
`schema_version: ${lockfile.schema_version ?? 2}`,
|
|
269
487
|
"",
|
|
270
488
|
"rigorloop:",
|
|
271
489
|
` package: "${lockfile.rigorloop.package}"`,
|
|
@@ -276,20 +494,37 @@ export function serializeLockfile(lockfile) {
|
|
|
276
494
|
` sha256: "${lockfile.manifest.sha256}"`,
|
|
277
495
|
"",
|
|
278
496
|
"generated:",
|
|
279
|
-
" adapters:",
|
|
497
|
+
isTargetSchema ? " targets:" : " adapters:",
|
|
280
498
|
];
|
|
281
|
-
for (const adapter of
|
|
499
|
+
for (const adapter of entries) {
|
|
282
500
|
lines.push(
|
|
283
|
-
` - adapter: ${adapter.adapter}`,
|
|
501
|
+
isTargetSchema ? ` - target: ${adapter.target}` : ` - adapter: ${adapter.adapter}`,
|
|
284
502
|
` release: "${adapter.release}"`,
|
|
285
503
|
` source: ${adapter.source}`,
|
|
286
504
|
` archive: "${adapter.archive}"`,
|
|
287
505
|
` archive_sha256: "${adapter.archive_sha256}"`,
|
|
288
|
-
` installed_root: "${adapter.installed_root}"`,
|
|
289
|
-
` tree_hash_algorithm: ${adapter.tree_hash_algorithm}`,
|
|
290
|
-
` tree_sha256: "${adapter.tree_sha256}"`,
|
|
291
|
-
` file_count: ${adapter.file_count}`,
|
|
292
506
|
);
|
|
507
|
+
if (adapter.installed_roots) {
|
|
508
|
+
lines.push(` tree_hash_algorithm: ${adapter.tree_hash_algorithm}`, " installed_roots:");
|
|
509
|
+
for (const [role, root] of Object.entries(adapter.installed_roots)) {
|
|
510
|
+
lines.push(` ${role}: "${root}"`);
|
|
511
|
+
}
|
|
512
|
+
lines.push(" root_hashes:");
|
|
513
|
+
for (const [role, hash] of Object.entries(adapter.root_hashes)) {
|
|
514
|
+
lines.push(
|
|
515
|
+
` ${role}:`,
|
|
516
|
+
` tree_sha256: "${hash.tree_sha256}"`,
|
|
517
|
+
` file_count: ${hash.file_count}`,
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
} else {
|
|
521
|
+
lines.push(
|
|
522
|
+
` installed_root: "${adapter.installed_root}"`,
|
|
523
|
+
` tree_hash_algorithm: ${adapter.tree_hash_algorithm}`,
|
|
524
|
+
` tree_sha256: "${adapter.tree_sha256}"`,
|
|
525
|
+
` file_count: ${adapter.file_count}`,
|
|
526
|
+
);
|
|
527
|
+
}
|
|
293
528
|
}
|
|
294
529
|
return `${lines.join("\n")}\n`;
|
|
295
530
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": 1,
|
|
3
|
+
"release": {
|
|
4
|
+
"version": "v0.2.0",
|
|
5
|
+
"source_repository": "xiongxianfei/rigorloop",
|
|
6
|
+
"source_commit": "0649434a75561bb8e2922a612e0121a095233687",
|
|
7
|
+
"release_tag": "v0.2.0",
|
|
8
|
+
"published_at": "2026-05-23"
|
|
9
|
+
},
|
|
10
|
+
"metadata": {
|
|
11
|
+
"url": "https://github.com/xiongxianfei/rigorloop/releases/download/v0.2.0/adapter-artifacts-v0.2.0.json",
|
|
12
|
+
"sha256": "0000000000000000000000000000000000000000000000000000000000000000"
|
|
13
|
+
},
|
|
14
|
+
"artifacts": [
|
|
15
|
+
{
|
|
16
|
+
"adapter": "codex",
|
|
17
|
+
"archive": "rigorloop-adapter-codex-v0.2.0.zip",
|
|
18
|
+
"url": "https://github.com/xiongxianfei/rigorloop/releases/download/v0.2.0/rigorloop-adapter-codex-v0.2.0.zip",
|
|
19
|
+
"sha256": "b7a8b5fcb4abf25eccb90868eb43d87943ee0bb526451910fb2d59f4844d41b3",
|
|
20
|
+
"size_bytes": 105317,
|
|
21
|
+
"install_root": ".agents/skills",
|
|
22
|
+
"tree_hash_algorithm": "rigorloop-tree-hash-v1",
|
|
23
|
+
"tree_sha256": "6c01d71d2a0e4cd3b276092728f52de23345095b25ce2135b1a0759afd25c12e",
|
|
24
|
+
"file_count": 38
|
|
25
|
+
}
|
|
26
|
+
],
|
|
27
|
+
"validation": {
|
|
28
|
+
"command": "python scripts/validate-adapters.py --root <release-output-dir> --version v0.2.0",
|
|
29
|
+
"result": "pass"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": 1,
|
|
3
|
+
"release": {
|
|
4
|
+
"version": "v0.3.0",
|
|
5
|
+
"source_repository": "xiongxianfei/rigorloop",
|
|
6
|
+
"source_commit": "02a9d7d6d514fc99908abf32898494dbbbae00c9",
|
|
7
|
+
"release_tag": "v0.3.0",
|
|
8
|
+
"published_at": "2026-05-24"
|
|
9
|
+
},
|
|
10
|
+
"metadata": {
|
|
11
|
+
"url": "https://github.com/xiongxianfei/rigorloop/releases/download/v0.3.0/adapter-artifacts-v0.3.0.json",
|
|
12
|
+
"sha256": "0000000000000000000000000000000000000000000000000000000000000000"
|
|
13
|
+
},
|
|
14
|
+
"artifacts": [
|
|
15
|
+
{
|
|
16
|
+
"adapter": "codex",
|
|
17
|
+
"archive": "rigorloop-adapter-codex-v0.3.0.zip",
|
|
18
|
+
"url": "https://github.com/xiongxianfei/rigorloop/releases/download/v0.3.0/rigorloop-adapter-codex-v0.3.0.zip",
|
|
19
|
+
"sha256": "ee5c35aa5ffaa9007aea2f2687b82c72135a42489edf56fe6427b37c55159550",
|
|
20
|
+
"size_bytes": 105318,
|
|
21
|
+
"install_root": ".agents/skills",
|
|
22
|
+
"tree_hash_algorithm": "rigorloop-tree-hash-v1",
|
|
23
|
+
"tree_sha256": "6c01d71d2a0e4cd3b276092728f52de23345095b25ce2135b1a0759afd25c12e",
|
|
24
|
+
"file_count": 38
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"adapter": "claude",
|
|
28
|
+
"archive": "rigorloop-adapter-claude-v0.3.0.zip",
|
|
29
|
+
"url": "https://github.com/xiongxianfei/rigorloop/releases/download/v0.3.0/rigorloop-adapter-claude-v0.3.0.zip",
|
|
30
|
+
"sha256": "91e0dd0d1573b7b3cfa5dfefb54a98b85dfe66d042889ecb8ba691f5c7bdc9e3",
|
|
31
|
+
"size_bytes": 104643,
|
|
32
|
+
"install_root": ".claude/skills",
|
|
33
|
+
"tree_hash_algorithm": "rigorloop-tree-hash-v1",
|
|
34
|
+
"tree_sha256": "8d1378e2b66be8502ef07a0013a60b19276b500c719056824a50335e4d1d3d1b",
|
|
35
|
+
"file_count": 38
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"adapter": "opencode",
|
|
39
|
+
"archive": "rigorloop-adapter-opencode-v0.3.0.zip",
|
|
40
|
+
"url": "https://github.com/xiongxianfei/rigorloop/releases/download/v0.3.0/rigorloop-adapter-opencode-v0.3.0.zip",
|
|
41
|
+
"sha256": "89f0b8f58681dc4563da6e4918ea039a2ed0dcca1dc84caabaf9836ea32fa0db",
|
|
42
|
+
"size_bytes": 104887,
|
|
43
|
+
"install_root": ".opencode/skills",
|
|
44
|
+
"tree_hash_algorithm": "rigorloop-tree-hash-v1",
|
|
45
|
+
"tree_sha256": "8d1378e2b66be8502ef07a0013a60b19276b500c719056824a50335e4d1d3d1b",
|
|
46
|
+
"file_count": 38,
|
|
47
|
+
"skills_only_compatibility": {
|
|
48
|
+
"releases": [
|
|
49
|
+
"v0.3.0"
|
|
50
|
+
]
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
],
|
|
54
|
+
"compatibility": {
|
|
55
|
+
"opencode_skills_only": {
|
|
56
|
+
"releases": [
|
|
57
|
+
"v0.3.0"
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"validation": {
|
|
62
|
+
"command": "python scripts/validate-adapters.py --root <release-output-dir> --version v0.3.0",
|
|
63
|
+
"result": "pass"
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema_version": 1,
|
|
3
3
|
"releases": {
|
|
4
|
-
"v0.
|
|
4
|
+
"v0.3.0": {
|
|
5
5
|
"source_repository": "xiongxianfei/rigorloop",
|
|
6
|
-
"release_tag": "v0.
|
|
7
|
-
"bundled_metadata": "adapter-artifacts-v0.
|
|
8
|
-
"bundled_metadata_sha256": "
|
|
6
|
+
"release_tag": "v0.3.0",
|
|
7
|
+
"bundled_metadata": "adapter-artifacts-v0.3.0.json",
|
|
8
|
+
"bundled_metadata_sha256": "510060d820851b98ec864d7727c3acb730e03afe8212bdfdd9ecead07100563d"
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
11
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xiongxianfei/rigorloop",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Git-first workflow for AI coding agents: proposals, specs, tests, review gates, and durable validation evidence from idea to PR.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"ai-agents",
|
|
7
|
+
"ai-coding",
|
|
8
|
+
"coding-agent",
|
|
9
|
+
"agentic-workflow",
|
|
10
|
+
"llm",
|
|
11
|
+
"developer-tools",
|
|
12
|
+
"software-engineering",
|
|
13
|
+
"code-review",
|
|
14
|
+
"git-workflow",
|
|
15
|
+
"cli",
|
|
16
|
+
"npm-package",
|
|
17
|
+
"claude-code",
|
|
18
|
+
"codex",
|
|
19
|
+
"opencode",
|
|
20
|
+
"workflow",
|
|
21
|
+
"testing",
|
|
22
|
+
"validation",
|
|
23
|
+
"pull-requests"
|
|
24
|
+
],
|
|
5
25
|
"repository": {
|
|
6
26
|
"type": "git",
|
|
7
27
|
"url": "https://github.com/xiongxianfei/rigorloop"
|