@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.
@@ -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
+ }
@@ -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
- if (current) {
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
- if (current) {
157
- adapters.push(current);
158
- }
226
+ pushCurrent();
159
227
  return { adapters };
160
228
  }
161
229
 
162
- function validateAdapter(adapter) {
163
- for (const field of ADAPTER_FIELDS) {
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 !== "codex") {
169
- return failure("unsupported", "unsupported-lockfile-shape", "Only codex lockfile entries are supported in this slice.");
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.installed_root !== ".agents/skills") {
178
- return failure("unsupported", "unsupported-lockfile-shape", "Unsupported installed root.");
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 (!isSha256(adapter.archive_sha256) || !isSha256(adapter.tree_sha256)) {
181
- return failure("invalid", "invalid-lockfile", "Adapter hashes must be SHA-256 values.");
182
- }
183
- const fileCount = Number.parseInt(adapter.file_count, 10);
184
- if (!/^\d+$/.test(String(adapter.file_count)) || !Number.isInteger(fileCount) || fileCount < 0) {
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.file_count = fileCount;
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 schema_version 1 YAML.");
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
- if (!/^\d+$/.test(schemaVersion) || Number.parseInt(schemaVersion, 10) !== 1) {
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: 1,
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 adapters = [...lockfile.generated.adapters].sort((left, right) => left.adapter.localeCompare(right.adapter));
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
- "schema_version: 1",
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 adapters) {
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.1.5": {
4
+ "v0.3.0": {
5
5
  "source_repository": "xiongxianfei/rigorloop",
6
- "release_tag": "v0.1.5",
7
- "bundled_metadata": "adapter-artifacts-v0.1.5.json",
8
- "bundled_metadata_sha256": "af43fe972d10f656f229909a9e764e652801fa3c0ae2df40a357ea4d069d7e7e"
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.1.5",
4
- "description": "RigorLoop CLI.",
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"