@xiongxianfei/rigorloop 0.1.4
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/LICENSE +21 -0
- package/README.md +5 -0
- package/dist/bin/rigorloop.js +1499 -0
- package/dist/lib/command-result.js +33 -0
- package/dist/lib/lockfile.js +303 -0
- package/dist/lib/new-change-filesystem.js +154 -0
- package/dist/lib/new-change.js +226 -0
- package/dist/lib/official-archive-url.js +42 -0
- package/dist/metadata/adapter-artifacts-v0.1.4.json +30 -0
- package/dist/metadata/releases.json +11 -0
- package/package.json +19 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
const CHANGE_ID_PATTERN = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
|
|
2
|
+
const CLASSIFICATION_PATTERN = /^[a-z][a-z0-9-]{0,63}$/;
|
|
3
|
+
const VALID_RISKS = new Set(["low", "medium", "high"]);
|
|
4
|
+
const VALID_PROFILES = new Set(["standard", "minimal"]);
|
|
5
|
+
|
|
6
|
+
function result(ok, code, message) {
|
|
7
|
+
return ok ? { ok: true } : { ok: false, code, message };
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function validateChangeId(value) {
|
|
11
|
+
if (typeof value !== "string" || !CHANGE_ID_PATTERN.test(value)) {
|
|
12
|
+
return result(false, "invalid-change-id", "Change id must be a lowercase repository-safe path segment.");
|
|
13
|
+
}
|
|
14
|
+
return result(true);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function validateClassification(value) {
|
|
18
|
+
if (typeof value !== "string" || !CLASSIFICATION_PATTERN.test(value)) {
|
|
19
|
+
return result(false, "invalid-classification", "Classification must be a lowercase token.");
|
|
20
|
+
}
|
|
21
|
+
return result(true);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function validateRisk(value) {
|
|
25
|
+
if (!VALID_RISKS.has(value)) {
|
|
26
|
+
return result(false, "invalid-risk", "Risk must be low, medium, or high.");
|
|
27
|
+
}
|
|
28
|
+
return result(true);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function validateProfile(value) {
|
|
32
|
+
if (!VALID_PROFILES.has(value)) {
|
|
33
|
+
return result(false, "unsupported-profile", "Profile must be standard or minimal.");
|
|
34
|
+
}
|
|
35
|
+
return result(true);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function yamlString(value) {
|
|
39
|
+
return JSON.stringify(String(value));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function renderChangeMetadata({ changeId, title, classification, risk }) {
|
|
43
|
+
return `change_id: ${yamlString(changeId)}
|
|
44
|
+
title: ${yamlString(title)}
|
|
45
|
+
classification: ${yamlString(classification)}
|
|
46
|
+
risk: ${yamlString(risk)}
|
|
47
|
+
artifacts: {}
|
|
48
|
+
requirements: []
|
|
49
|
+
tests: []
|
|
50
|
+
validation: []
|
|
51
|
+
changed_files: []
|
|
52
|
+
review:
|
|
53
|
+
status: "pending"
|
|
54
|
+
unresolved_items: 0
|
|
55
|
+
`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function buildNewChangeDraft({ changeId, title, classification = "default", risk = "medium", profile = "standard" }) {
|
|
59
|
+
const root = `docs/changes/${changeId}`;
|
|
60
|
+
const metadataPath = `${root}/change.yaml`;
|
|
61
|
+
return {
|
|
62
|
+
change: {
|
|
63
|
+
change_id: changeId,
|
|
64
|
+
root,
|
|
65
|
+
metadata_path: metadataPath,
|
|
66
|
+
profile,
|
|
67
|
+
},
|
|
68
|
+
planned_change_metadata: {
|
|
69
|
+
path: metadataPath,
|
|
70
|
+
content: renderChangeMetadata({ changeId, title, classification, risk }),
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function parseOptionValue(args, index, code, message) {
|
|
76
|
+
const value = args[index + 1];
|
|
77
|
+
if (value === undefined || value.startsWith("--")) {
|
|
78
|
+
return {
|
|
79
|
+
error: {
|
|
80
|
+
code,
|
|
81
|
+
message,
|
|
82
|
+
},
|
|
83
|
+
consumed: 0,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
return { value, consumed: 1 };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function parseNewChangeArgs(args, env = process.env) {
|
|
90
|
+
const flags = {
|
|
91
|
+
json: false,
|
|
92
|
+
quiet: false,
|
|
93
|
+
debug: false,
|
|
94
|
+
noColor: Boolean(env.NO_COLOR),
|
|
95
|
+
dryRun: false,
|
|
96
|
+
};
|
|
97
|
+
for (const arg of args) {
|
|
98
|
+
if (arg === "--json") {
|
|
99
|
+
flags.json = true;
|
|
100
|
+
} else if (arg === "--quiet") {
|
|
101
|
+
flags.quiet = true;
|
|
102
|
+
} else if (arg === "--debug") {
|
|
103
|
+
flags.debug = true;
|
|
104
|
+
} else if (arg === "--no-color") {
|
|
105
|
+
flags.noColor = true;
|
|
106
|
+
} else if (arg === "--dry-run") {
|
|
107
|
+
flags.dryRun = true;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const options = {
|
|
112
|
+
classification: "default",
|
|
113
|
+
risk: "medium",
|
|
114
|
+
profile: "standard",
|
|
115
|
+
};
|
|
116
|
+
const positional = [];
|
|
117
|
+
|
|
118
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
119
|
+
const arg = args[index];
|
|
120
|
+
if (arg === "--json") {
|
|
121
|
+
flags.json = true;
|
|
122
|
+
} else if (arg === "--quiet") {
|
|
123
|
+
flags.quiet = true;
|
|
124
|
+
} else if (arg === "--debug") {
|
|
125
|
+
flags.debug = true;
|
|
126
|
+
} else if (arg === "--no-color") {
|
|
127
|
+
flags.noColor = true;
|
|
128
|
+
} else if (arg === "--dry-run") {
|
|
129
|
+
flags.dryRun = true;
|
|
130
|
+
} else if (arg === "--title") {
|
|
131
|
+
const parsed = parseOptionValue(args, index, "missing-title", "--title requires a non-empty value.");
|
|
132
|
+
if (parsed.error) {
|
|
133
|
+
return { flags, error: parsed.error };
|
|
134
|
+
}
|
|
135
|
+
options.title = parsed.value;
|
|
136
|
+
index += parsed.consumed;
|
|
137
|
+
} else if (arg === "--type") {
|
|
138
|
+
const parsed = parseOptionValue(args, index, "invalid-classification", "--type requires a classification token.");
|
|
139
|
+
if (parsed.error) {
|
|
140
|
+
return { flags, error: parsed.error };
|
|
141
|
+
}
|
|
142
|
+
options.classification = parsed.value;
|
|
143
|
+
index += parsed.consumed;
|
|
144
|
+
} else if (arg === "--risk") {
|
|
145
|
+
const parsed = parseOptionValue(args, index, "invalid-risk", "--risk requires low, medium, or high.");
|
|
146
|
+
if (parsed.error) {
|
|
147
|
+
return { flags, error: parsed.error };
|
|
148
|
+
}
|
|
149
|
+
options.risk = parsed.value;
|
|
150
|
+
index += parsed.consumed;
|
|
151
|
+
} else if (arg === "--profile") {
|
|
152
|
+
const parsed = parseOptionValue(args, index, "unsupported-profile", "--profile requires standard or minimal.");
|
|
153
|
+
if (parsed.error) {
|
|
154
|
+
return { flags, error: parsed.error };
|
|
155
|
+
}
|
|
156
|
+
options.profile = parsed.value;
|
|
157
|
+
index += parsed.consumed;
|
|
158
|
+
} else if (arg.startsWith("--")) {
|
|
159
|
+
return {
|
|
160
|
+
flags,
|
|
161
|
+
error: {
|
|
162
|
+
code: "invalid-usage",
|
|
163
|
+
message: `Unknown option for new-change: ${arg}`,
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
} else {
|
|
167
|
+
positional.push(arg);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const [changeId, ...extra] = positional;
|
|
172
|
+
if (!changeId) {
|
|
173
|
+
return {
|
|
174
|
+
flags,
|
|
175
|
+
error: {
|
|
176
|
+
code: "missing-change-id",
|
|
177
|
+
message: "new-change requires <change-id>.",
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
if (extra.length > 0) {
|
|
182
|
+
return {
|
|
183
|
+
flags,
|
|
184
|
+
error: {
|
|
185
|
+
code: "invalid-usage",
|
|
186
|
+
message: "new-change accepts exactly one <change-id>.",
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
const changeIdValidation = validateChangeId(changeId);
|
|
191
|
+
if (!changeIdValidation.ok) {
|
|
192
|
+
return { flags, error: changeIdValidation };
|
|
193
|
+
}
|
|
194
|
+
if (!options.title || options.title.length === 0) {
|
|
195
|
+
return {
|
|
196
|
+
flags,
|
|
197
|
+
error: {
|
|
198
|
+
code: "missing-title",
|
|
199
|
+
message: "new-change requires --title <title>.",
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
const classificationValidation = validateClassification(options.classification);
|
|
204
|
+
if (!classificationValidation.ok) {
|
|
205
|
+
return { flags, error: classificationValidation };
|
|
206
|
+
}
|
|
207
|
+
const riskValidation = validateRisk(options.risk);
|
|
208
|
+
if (!riskValidation.ok) {
|
|
209
|
+
return { flags, error: riskValidation };
|
|
210
|
+
}
|
|
211
|
+
const profileValidation = validateProfile(options.profile);
|
|
212
|
+
if (!profileValidation.ok) {
|
|
213
|
+
return { flags, error: profileValidation };
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
flags,
|
|
218
|
+
value: {
|
|
219
|
+
changeId,
|
|
220
|
+
title: options.title,
|
|
221
|
+
classification: options.classification,
|
|
222
|
+
risk: options.risk,
|
|
223
|
+
profile: options.profile,
|
|
224
|
+
},
|
|
225
|
+
};
|
|
226
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const OFFICIAL_HOST = "github.com";
|
|
2
|
+
const OFFICIAL_OWNER = "xiongxianfei";
|
|
3
|
+
const OFFICIAL_REPO = "rigorloop";
|
|
4
|
+
|
|
5
|
+
export function expectedArchiveUrl({ releaseTag, archive }) {
|
|
6
|
+
return `https://${OFFICIAL_HOST}/${OFFICIAL_OWNER}/${OFFICIAL_REPO}/releases/download/${releaseTag}/${archive}`;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function validateOfficialArchiveUrl({ url, releaseTag, archive }) {
|
|
10
|
+
let parsed;
|
|
11
|
+
try {
|
|
12
|
+
parsed = new URL(url);
|
|
13
|
+
} catch {
|
|
14
|
+
return {
|
|
15
|
+
ok: false,
|
|
16
|
+
code: "invalid-archive-url",
|
|
17
|
+
message: "Adapter archive URL is not a valid URL.",
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const expected = new URL(expectedArchiveUrl({ releaseTag, archive }));
|
|
22
|
+
const isOfficial =
|
|
23
|
+
parsed.protocol === "https:" &&
|
|
24
|
+
parsed.hostname === expected.hostname &&
|
|
25
|
+
parsed.pathname === expected.pathname &&
|
|
26
|
+
parsed.search === "" &&
|
|
27
|
+
parsed.hash === "" &&
|
|
28
|
+
parsed.username === "" &&
|
|
29
|
+
parsed.password === "" &&
|
|
30
|
+
parsed.port === "";
|
|
31
|
+
|
|
32
|
+
if (!isOfficial) {
|
|
33
|
+
return {
|
|
34
|
+
ok: false,
|
|
35
|
+
code: "non-official-archive-url",
|
|
36
|
+
message: "Network adapter install may fetch only official RigorLoop GitHub release archive URLs.",
|
|
37
|
+
path: "metadata.artifacts[codex].url",
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return { ok: true };
|
|
42
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": 1,
|
|
3
|
+
"release": {
|
|
4
|
+
"version": "v0.1.4",
|
|
5
|
+
"source_repository": "xiongxianfei/rigorloop",
|
|
6
|
+
"source_commit": "c9cfaf24949d5b2093ee250d216e7762ca2fdf41",
|
|
7
|
+
"release_tag": "v0.1.4",
|
|
8
|
+
"published_at": "2026-05-16"
|
|
9
|
+
},
|
|
10
|
+
"metadata": {
|
|
11
|
+
"url": "https://github.com/xiongxianfei/rigorloop/releases/download/v0.1.4/adapter-artifacts-v0.1.4.json",
|
|
12
|
+
"sha256": "2475f8942524fa022229ba14f80d75d4f3d08a2fe7e1f19c9868526f393e0dc2"
|
|
13
|
+
},
|
|
14
|
+
"artifacts": [
|
|
15
|
+
{
|
|
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",
|
|
20
|
+
"size_bytes": 91928,
|
|
21
|
+
"install_root": ".agents/skills",
|
|
22
|
+
"tree_hash_algorithm": "rigorloop-tree-hash-v1",
|
|
23
|
+
"tree_sha256": "af477470c492a1b68303b462824a055b72a0ede0912616c1c641053f923d5bd4"
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
"validation": {
|
|
27
|
+
"command": "python scripts/validate-adapters.py --root <release-output-dir> --version v0.1.4",
|
|
28
|
+
"result": "pass"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": 1,
|
|
3
|
+
"releases": {
|
|
4
|
+
"v0.1.4": {
|
|
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"
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@xiongxianfei/rigorloop",
|
|
3
|
+
"version": "0.1.4",
|
|
4
|
+
"description": "RigorLoop CLI.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"rigorloop": "dist/bin/rigorloop.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist/",
|
|
11
|
+
"package.json",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"test": "node --test"
|
|
17
|
+
},
|
|
18
|
+
"license": "MIT"
|
|
19
|
+
}
|