@xiongxianfei/rigorloop 0.1.4 → 0.2.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,6 +2,7 @@ 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"];
@@ -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,146 @@ 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 unexpectedFields(adapter, allowed) {
231
+ return Object.keys(adapter).filter((field) => !allowed.includes(field));
232
+ }
233
+
234
+ function validateSingleRootAdapter(adapter, schemaVersion) {
235
+ const allowed = [
236
+ "adapter",
237
+ "release",
238
+ "source",
239
+ "archive",
240
+ "archive_sha256",
241
+ "installed_root",
242
+ "tree_hash_algorithm",
243
+ "tree_sha256",
244
+ "file_count",
245
+ ];
246
+ const unexpected = unexpectedFields(adapter, allowed);
247
+ if (unexpected.length) {
248
+ return failure("unsupported", "unsupported-lockfile-shape", `Unsupported field generated.adapters[].${unexpected[0]}`);
249
+ }
250
+ for (const field of allowed) {
164
251
  if (adapter[field] === undefined) {
165
252
  return failure("invalid", "invalid-lockfile", `Missing adapter field: ${field}`);
166
253
  }
167
254
  }
168
- if (adapter.adapter !== "codex") {
169
- return failure("unsupported", "unsupported-lockfile-shape", "Only codex lockfile entries are supported in this slice.");
255
+ if (adapter.adapter === "opencode") {
256
+ return failure("unsupported", "unsupported-lockfile-shape", "opencode lockfile entries must use installed_roots.");
257
+ }
258
+ if (schemaVersion === 1 && adapter.adapter !== "codex") {
259
+ return failure("unsupported", "unsupported-lockfile-shape", "schema_version 1 supports only Codex lockfile entries.");
260
+ }
261
+ if (adapter.installed_root !== SINGLE_ROOTS[adapter.adapter]) {
262
+ return failure("unsupported", "unsupported-lockfile-shape", "Unsupported installed root.");
263
+ }
264
+ if (!isSha256(adapter.tree_sha256)) {
265
+ return failure("invalid", "invalid-lockfile", "Adapter tree hash must be a SHA-256 value.");
266
+ }
267
+ if (!isNonNegativeInteger(adapter.file_count)) {
268
+ return failure("invalid", "invalid-lockfile", "Adapter file_count must be a non-negative integer.");
269
+ }
270
+ adapter.file_count = Number.parseInt(adapter.file_count, 10);
271
+ return undefined;
272
+ }
273
+
274
+ function validateMultiRootAdapter(adapter) {
275
+ const allowed = [
276
+ "adapter",
277
+ "release",
278
+ "source",
279
+ "archive",
280
+ "archive_sha256",
281
+ "tree_hash_algorithm",
282
+ "installed_roots",
283
+ "root_hashes",
284
+ ];
285
+ const unexpected = unexpectedFields(adapter, allowed);
286
+ if (unexpected.length) {
287
+ return failure("unsupported", "unsupported-lockfile-shape", `Unsupported field generated.adapters[].${unexpected[0]}`);
288
+ }
289
+ for (const field of allowed) {
290
+ if (adapter[field] === undefined) {
291
+ return failure("invalid", "invalid-lockfile", `Missing adapter field: ${field}`);
292
+ }
293
+ }
294
+ if (adapter.adapter !== "opencode") {
295
+ return failure("unsupported", "unsupported-lockfile-shape", "Only opencode supports multi-root lockfile entries.");
296
+ }
297
+ const rootRoles = Object.keys(adapter.installed_roots).sort();
298
+ const hashRoles = Object.keys(adapter.root_hashes).sort();
299
+ if (!rootRoles.length || rootRoles.join("\n") !== hashRoles.join("\n")) {
300
+ return failure("invalid", "invalid-lockfile", "installed_roots and root_hashes roles must match.");
301
+ }
302
+ for (const role of rootRoles) {
303
+ if (!Object.hasOwn(OPENCODE_ROOTS, role) || adapter.installed_roots[role] !== OPENCODE_ROOTS[role]) {
304
+ return failure("unsupported", "unsupported-lockfile-shape", "Unsupported opencode installed root.");
305
+ }
306
+ const hash = adapter.root_hashes[role];
307
+ if (!hash || !isSha256(hash.tree_sha256)) {
308
+ return failure("invalid", "invalid-lockfile", "Adapter root hash must be a SHA-256 value.");
309
+ }
310
+ if (!isNonNegativeInteger(hash.file_count)) {
311
+ return failure("invalid", "invalid-lockfile", "Adapter root file_count must be a non-negative integer.");
312
+ }
313
+ hash.file_count = Number.parseInt(hash.file_count, 10);
314
+ }
315
+ return undefined;
316
+ }
317
+
318
+ function validateAdapter(adapter, schemaVersion) {
319
+ if (!SUPPORTED_ADAPTERS.has(adapter.adapter)) {
320
+ return failure("unsupported", "unsupported-lockfile-shape", "Unsupported lockfile adapter.");
170
321
  }
171
322
  if (!SUPPORTED_SOURCES.has(adapter.source)) {
172
323
  return failure("unsupported", "unsupported-lockfile-shape", "Unsupported lockfile adapter source.");
@@ -174,18 +325,16 @@ function validateAdapter(adapter) {
174
325
  if (adapter.tree_hash_algorithm !== "rigorloop-tree-hash-v1") {
175
326
  return failure("unsupported", "unsupported-lockfile-shape", "Unsupported tree hash algorithm.");
176
327
  }
177
- if (adapter.installed_root !== ".agents/skills") {
178
- return failure("unsupported", "unsupported-lockfile-shape", "Unsupported installed root.");
328
+ if (!isSha256(adapter.archive_sha256)) {
329
+ return failure("invalid", "invalid-lockfile", "Adapter archive hash must be a SHA-256 value.");
179
330
  }
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.");
331
+ if (adapter.installed_roots !== undefined || adapter.root_hashes !== undefined) {
332
+ if (schemaVersion !== 2) {
333
+ return failure("unsupported", "unsupported-lockfile-shape", "Multi-root lockfile entries require schema_version 2.");
334
+ }
335
+ return validateMultiRootAdapter(adapter);
186
336
  }
187
- adapter.file_count = fileCount;
188
- return undefined;
337
+ return validateSingleRootAdapter(adapter, schemaVersion);
189
338
  }
190
339
 
191
340
  export function parseLockfile(text) {
@@ -193,7 +342,7 @@ export function parseLockfile(text) {
193
342
  return failure("invalid", "invalid-lockfile", "rigorloop.lock is empty or not text.");
194
343
  }
195
344
  if (/[\[\]{}]/.test(text)) {
196
- return failure("invalid", "invalid-lockfile", "rigorloop.lock is not valid strict schema_version 1 YAML.");
345
+ return failure("invalid", "invalid-lockfile", "rigorloop.lock is not valid strict YAML.");
197
346
  }
198
347
 
199
348
  const lines = text.replace(/\r\n?/g, "\n").split("\n");
@@ -209,7 +358,8 @@ export function parseLockfile(text) {
209
358
  }
210
359
  }
211
360
  const schemaVersion = parseScalar(top.get("schema_version"));
212
- if (!/^\d+$/.test(schemaVersion) || Number.parseInt(schemaVersion, 10) !== 1) {
361
+ const parsedSchemaVersion = Number.parseInt(schemaVersion, 10);
362
+ if (!/^\d+$/.test(schemaVersion) || ![1, 2].includes(parsedSchemaVersion)) {
213
363
  return failure("unsupported", "unsupported-lockfile-shape", "Unsupported lockfile schema_version.");
214
364
  }
215
365
 
@@ -243,7 +393,7 @@ export function parseLockfile(text) {
243
393
  return failure("invalid", "invalid-lockfile", "generated.adapters must contain at least one adapter entry.");
244
394
  }
245
395
  for (const adapter of adapterResult.adapters) {
246
- const adapterError = validateAdapter(adapter);
396
+ const adapterError = validateAdapter(adapter, parsedSchemaVersion);
247
397
  if (adapterError) {
248
398
  return adapterError;
249
399
  }
@@ -252,7 +402,7 @@ export function parseLockfile(text) {
252
402
  return {
253
403
  ok: true,
254
404
  lockfile: {
255
- schema_version: 1,
405
+ schema_version: parsedSchemaVersion,
256
406
  rigorloop: rigorloop.fields,
257
407
  manifest: manifest.fields,
258
408
  generated: {
@@ -265,7 +415,7 @@ export function parseLockfile(text) {
265
415
  export function serializeLockfile(lockfile) {
266
416
  const adapters = [...lockfile.generated.adapters].sort((left, right) => left.adapter.localeCompare(right.adapter));
267
417
  const lines = [
268
- "schema_version: 1",
418
+ `schema_version: ${lockfile.schema_version ?? 2}`,
269
419
  "",
270
420
  "rigorloop:",
271
421
  ` package: "${lockfile.rigorloop.package}"`,
@@ -285,11 +435,28 @@ export function serializeLockfile(lockfile) {
285
435
  ` source: ${adapter.source}`,
286
436
  ` archive: "${adapter.archive}"`,
287
437
  ` 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
438
  );
439
+ if (adapter.installed_roots) {
440
+ lines.push(` tree_hash_algorithm: ${adapter.tree_hash_algorithm}`, " installed_roots:");
441
+ for (const [role, root] of Object.entries(adapter.installed_roots)) {
442
+ lines.push(` ${role}: "${root}"`);
443
+ }
444
+ lines.push(" root_hashes:");
445
+ for (const [role, hash] of Object.entries(adapter.root_hashes)) {
446
+ lines.push(
447
+ ` ${role}:`,
448
+ ` tree_sha256: "${hash.tree_sha256}"`,
449
+ ` file_count: ${hash.file_count}`,
450
+ );
451
+ }
452
+ } else {
453
+ lines.push(
454
+ ` installed_root: "${adapter.installed_root}"`,
455
+ ` tree_hash_algorithm: ${adapter.tree_hash_algorithm}`,
456
+ ` tree_sha256: "${adapter.tree_sha256}"`,
457
+ ` file_count: ${adapter.file_count}`,
458
+ );
459
+ }
293
460
  }
294
461
  return `${lines.join("\n")}\n`;
295
462
  }
@@ -1,22 +1,22 @@
1
1
  {
2
2
  "schema_version": 1,
3
3
  "release": {
4
- "version": "v0.1.4",
4
+ "version": "v0.1.5",
5
5
  "source_repository": "xiongxianfei/rigorloop",
6
- "source_commit": "c9cfaf24949d5b2093ee250d216e7762ca2fdf41",
7
- "release_tag": "v0.1.4",
6
+ "source_commit": "5315a6d08b9d79e52d3276fd532b02f97c727e55",
7
+ "release_tag": "v0.1.5",
8
8
  "published_at": "2026-05-16"
9
9
  },
10
10
  "metadata": {
11
- "url": "https://github.com/xiongxianfei/rigorloop/releases/download/v0.1.4/adapter-artifacts-v0.1.4.json",
12
- "sha256": "2475f8942524fa022229ba14f80d75d4f3d08a2fe7e1f19c9868526f393e0dc2"
11
+ "url": "https://github.com/xiongxianfei/rigorloop/releases/download/v0.1.5/adapter-artifacts-v0.1.5.json",
12
+ "sha256": "0000000000000000000000000000000000000000000000000000000000000000"
13
13
  },
14
14
  "artifacts": [
15
15
  {
16
16
  "adapter": "codex",
17
- "archive": "rigorloop-adapter-codex-v0.1.4.zip",
18
- "url": "https://github.com/xiongxianfei/rigorloop/releases/download/v0.1.4/rigorloop-adapter-codex-v0.1.4.zip",
19
- "sha256": "6c44d186c28507d44573666453b76b6d1568fa49f4f152d10151feafe04858b1",
17
+ "archive": "rigorloop-adapter-codex-v0.1.5.zip",
18
+ "url": "https://github.com/xiongxianfei/rigorloop/releases/download/v0.1.5/rigorloop-adapter-codex-v0.1.5.zip",
19
+ "sha256": "97991ad31b0926ea3bcf8ab98d6aa0f93511ab25a51157d9fa701c1e822a32fd",
20
20
  "size_bytes": 91928,
21
21
  "install_root": ".agents/skills",
22
22
  "tree_hash_algorithm": "rigorloop-tree-hash-v1",
@@ -24,7 +24,7 @@
24
24
  }
25
25
  ],
26
26
  "validation": {
27
- "command": "python scripts/validate-adapters.py --root <release-output-dir> --version v0.1.4",
27
+ "command": "python scripts/validate-adapters.py --root <release-output-dir> --version v0.1.5",
28
28
  "result": "pass"
29
29
  }
30
30
  }
@@ -0,0 +1,30 @@
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": "698be6488b06534ed927278f7d0671fd9ee7ead8f4e20d1df838894b45d139bf"
24
+ }
25
+ ],
26
+ "validation": {
27
+ "command": "python scripts/validate-adapters.py --root <release-output-dir> --version v0.2.0",
28
+ "result": "pass"
29
+ }
30
+ }
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "schema_version": 1,
3
3
  "releases": {
4
- "v0.1.4": {
4
+ "v0.2.0": {
5
5
  "source_repository": "xiongxianfei/rigorloop",
6
- "release_tag": "v0.1.4",
7
- "bundled_metadata": "adapter-artifacts-v0.1.4.json",
8
- "bundled_metadata_sha256": "da4505ff2edbbb298aae10a4a147a617e0f8cd73b7805ea285a65ecb18824563"
6
+ "release_tag": "v0.2.0",
7
+ "bundled_metadata": "adapter-artifacts-v0.2.0.json",
8
+ "bundled_metadata_sha256": "686e41154de342f2852dd4745a5378d1440bc02a39255630cc009cc28f39fa06"
9
9
  }
10
10
  }
11
11
  }
package/package.json CHANGED
@@ -1,7 +1,11 @@
1
1
  {
2
2
  "name": "@xiongxianfei/rigorloop",
3
- "version": "0.1.4",
3
+ "version": "0.2.0",
4
4
  "description": "RigorLoop CLI.",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/xiongxianfei/rigorloop"
8
+ },
5
9
  "type": "module",
6
10
  "bin": {
7
11
  "rigorloop": "dist/bin/rigorloop.js"