clean-room-skill 0.1.12 → 0.1.13
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/README.md +32 -5
- package/agents/clean-architect.md +3 -0
- package/agents/clean-implementer-verifier-shell.md +3 -0
- package/agents/clean-polish-reviewer.md +3 -0
- package/agents/clean-qa-editor.md +3 -0
- package/agents/contaminated-handoff-sanitizer.md +3 -0
- package/agents/contaminated-manager-verifier.md +3 -0
- package/agents/contaminated-source-analyst.md +3 -0
- package/bin/install.js +11 -1621
- package/docs/ARCHITECTURE.md +1 -1
- package/docs/HOOKS.md +14 -10
- package/docs/REFERENCE.md +24 -4
- package/examples/codex/.codex/agents/clean-architect.toml +3 -3
- package/examples/codex/.codex/agents/clean-polish-reviewer.toml +2 -2
- package/examples/codex/.codex/agents/clean-qa-editor.toml +2 -2
- package/examples/codex/.codex/agents/contaminated-handoff-sanitizer.toml +2 -2
- package/examples/codex/.codex/agents/contaminated-manager-verifier.toml +3 -3
- package/examples/codex/.codex/agents/contaminated-source-analyst.toml +2 -2
- package/lib/bootstrap.cjs +5 -1
- package/lib/doctor.cjs +157 -5
- package/lib/hooks.cjs +18 -0
- package/lib/install-artifacts.cjs +178 -4
- package/lib/install-claude-plugin.cjs +374 -0
- package/lib/install-cli.cjs +99 -0
- package/lib/install-operations.cjs +376 -0
- package/lib/install-options.cjs +149 -0
- package/lib/install-runtime-selection.cjs +180 -0
- package/lib/install-status.cjs +292 -0
- package/lib/install-tui.cjs +359 -0
- package/lib/preflight-bootstrap.cjs +39 -0
- package/lib/preflight-cli.cjs +95 -0
- package/lib/preflight-constants.cjs +25 -0
- package/lib/preflight-output.cjs +37 -0
- package/lib/preflight-paths.cjs +67 -0
- package/lib/preflight-template.cjs +103 -0
- package/lib/preflight-validation.cjs +276 -0
- package/lib/preflight.cjs +18 -461
- package/lib/run-clean-artifacts.cjs +276 -0
- package/lib/run-cli.cjs +90 -0
- package/lib/run-constants.cjs +171 -0
- package/lib/run-controller.cjs +247 -0
- package/lib/run-coverage.cjs +350 -0
- package/lib/run-hooks.cjs +96 -0
- package/lib/run-manifest.cjs +111 -0
- package/lib/run-progress.cjs +160 -0
- package/lib/run-results.cjs +433 -0
- package/lib/run-roots.cjs +230 -0
- package/lib/run-stages.cjs +409 -0
- package/lib/run.cjs +4 -2254
- package/lib/runtime-layout.cjs +12 -5
- package/package.json +8 -2
- package/plugin.json +1 -1
- package/skills/attended/SKILL.md +2 -0
- package/skills/clean-room/SKILL.md +2 -2
- package/skills/unattended/SKILL.md +2 -0
package/lib/preflight.cjs
CHANGED
|
@@ -1,470 +1,27 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const fs = require('node:fs');
|
|
4
3
|
const os = require('node:os');
|
|
5
4
|
const path = require('node:path');
|
|
6
5
|
|
|
7
|
-
const {
|
|
8
|
-
atomicWriteFile,
|
|
9
|
-
atomicWriteFileNoOverwrite,
|
|
10
|
-
readJsonFile,
|
|
11
|
-
} = require('./fs-utils.cjs');
|
|
6
|
+
const { readJsonFile } = require('./fs-utils.cjs');
|
|
12
7
|
const { resolveBootstrapScaffold } = require('./bootstrap.cjs');
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
console.log(`Usage: clean-room-skill preflight (--template | --input <path>) (--output <path> | --bootstrap <path>) [options]
|
|
31
|
-
|
|
32
|
-
Create or validate a clean-room preflight goal contract.
|
|
33
|
-
|
|
34
|
-
Options:
|
|
35
|
-
--template Write an attended draft with blocking open questions
|
|
36
|
-
--input <path> Validate and normalize/copy a completed preflight goal
|
|
37
|
-
--output <path> Destination preflight-goal.json
|
|
38
|
-
--bootstrap <path> Generated task root or clean-room-bootstrap.json
|
|
39
|
-
--mode <mode> attended or unattended (template supports attended only)
|
|
40
|
-
--dry-run Print actions without writing files
|
|
41
|
-
--force Overwrite output if it already exists
|
|
42
|
-
-h, --help Show this help
|
|
43
|
-
`);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function parsePreflightArgs(argv) {
|
|
47
|
-
const options = {
|
|
48
|
-
template: false,
|
|
49
|
-
input: null,
|
|
50
|
-
output: null,
|
|
51
|
-
bootstrap: null,
|
|
52
|
-
mode: 'attended',
|
|
53
|
-
dryRun: false,
|
|
54
|
-
force: false,
|
|
55
|
-
help: false,
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
for (let index = 0; index < argv.length; index += 1) {
|
|
59
|
-
const arg = argv[index];
|
|
60
|
-
if (arg === '-h' || arg === '--help') {
|
|
61
|
-
options.help = true;
|
|
62
|
-
} else if (arg === '--template') {
|
|
63
|
-
options.template = true;
|
|
64
|
-
} else if (arg === '--dry-run') {
|
|
65
|
-
options.dryRun = true;
|
|
66
|
-
} else if (arg === '--force') {
|
|
67
|
-
options.force = true;
|
|
68
|
-
} else if (arg === '--input') {
|
|
69
|
-
index += 1;
|
|
70
|
-
options.input = requiredValue(argv, index, '--input');
|
|
71
|
-
} else if (arg.startsWith('--input=')) {
|
|
72
|
-
options.input = arg.slice('--input='.length);
|
|
73
|
-
} else if (arg === '--output') {
|
|
74
|
-
index += 1;
|
|
75
|
-
options.output = requiredValue(argv, index, '--output');
|
|
76
|
-
} else if (arg.startsWith('--output=')) {
|
|
77
|
-
options.output = arg.slice('--output='.length);
|
|
78
|
-
} else if (arg === '--bootstrap') {
|
|
79
|
-
index += 1;
|
|
80
|
-
options.bootstrap = requiredValue(argv, index, '--bootstrap');
|
|
81
|
-
} else if (arg.startsWith('--bootstrap=')) {
|
|
82
|
-
options.bootstrap = arg.slice('--bootstrap='.length);
|
|
83
|
-
} else if (arg === '--mode') {
|
|
84
|
-
index += 1;
|
|
85
|
-
options.mode = requiredValue(argv, index, '--mode');
|
|
86
|
-
} else if (arg.startsWith('--mode=')) {
|
|
87
|
-
options.mode = arg.slice('--mode='.length);
|
|
88
|
-
} else {
|
|
89
|
-
throw new Error(`unknown preflight option: ${arg}`);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return options;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function requiredValue(argv, index, flag) {
|
|
97
|
-
if (index >= argv.length || argv[index] === '') {
|
|
98
|
-
throw new Error(`${flag} requires a value`);
|
|
99
|
-
}
|
|
100
|
-
return argv[index];
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function expandTilde(value, homeDir = os.homedir()) {
|
|
104
|
-
if (value === '~') return homeDir;
|
|
105
|
-
if (typeof value === 'string' && value.startsWith('~/')) return path.join(homeDir, value.slice(2));
|
|
106
|
-
return value;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function resolveOutputPath(value, cwd = process.cwd(), homeDir = os.homedir()) {
|
|
110
|
-
if (typeof value !== 'string' || value.trim() === '') {
|
|
111
|
-
throw new Error('--output requires a path');
|
|
112
|
-
}
|
|
113
|
-
const expanded = expandTilde(value, homeDir);
|
|
114
|
-
return path.resolve(cwd, expanded);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function resolveInputPath(value, cwd = process.cwd(), homeDir = os.homedir()) {
|
|
118
|
-
if (typeof value !== 'string' || value.trim() === '') {
|
|
119
|
-
throw new Error('--input requires a path');
|
|
120
|
-
}
|
|
121
|
-
const expanded = expandTilde(value, homeDir);
|
|
122
|
-
return path.resolve(cwd, expanded);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function resolveGoalPath(value, cwd, homeDir) {
|
|
126
|
-
if (typeof value !== 'string' || value.trim() === '') {
|
|
127
|
-
return null;
|
|
128
|
-
}
|
|
129
|
-
return path.resolve(cwd, expandTilde(value, homeDir));
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function applyBootstrapOutputPolicy(goal, bootstrap) {
|
|
133
|
-
goal.output_policy.artifact_base_root = bootstrap.outputRoot;
|
|
134
|
-
goal.output_policy.implementation_root = bootstrap.roots.implementation;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function validateBootstrapOutputPolicy(goal, bootstrap, cwd, homeDir) {
|
|
138
|
-
const errors = [];
|
|
139
|
-
const artifactBaseRoot = resolveGoalPath(goal?.output_policy?.artifact_base_root, cwd, homeDir);
|
|
140
|
-
const implementationRoot = resolveGoalPath(goal?.output_policy?.implementation_root, cwd, homeDir);
|
|
141
|
-
if (artifactBaseRoot !== bootstrap.outputRoot) {
|
|
142
|
-
errors.push(`output_policy.artifact_base_root must match bootstrap task root: ${bootstrap.outputRoot}`);
|
|
143
|
-
}
|
|
144
|
-
if (implementationRoot !== bootstrap.roots.implementation) {
|
|
145
|
-
errors.push(`output_policy.implementation_root must match bootstrap implementation root: ${bootstrap.roots.implementation}`);
|
|
146
|
-
}
|
|
147
|
-
return errors;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
function buildTemplate(mode = 'attended') {
|
|
151
|
-
if (mode !== 'attended') {
|
|
152
|
-
throw new Error('preflight --template supports attended mode only');
|
|
153
|
-
}
|
|
154
|
-
return {
|
|
155
|
-
goal_id: 'goal-task-xxxxxxxx',
|
|
156
|
-
created_at: new Date().toISOString(),
|
|
157
|
-
end_goal: {
|
|
158
|
-
intent: 'clean-room-reimplementation',
|
|
159
|
-
success_definition: 'TBD: define the observable result the clean implementation must achieve.',
|
|
160
|
-
destination_kind: 'new-project',
|
|
161
|
-
existing_destination_policy: 'inspect-and-preserve',
|
|
162
|
-
},
|
|
163
|
-
target_stack: {
|
|
164
|
-
language: 'TBD',
|
|
165
|
-
runtime: null,
|
|
166
|
-
framework: null,
|
|
167
|
-
package_manager: null,
|
|
168
|
-
test_framework: null,
|
|
169
|
-
},
|
|
170
|
-
license_policy: {
|
|
171
|
-
source_license_notes: 'unknown',
|
|
172
|
-
destination_license: 'TBD',
|
|
173
|
-
dependency_license_allowlist: ['MIT', 'Apache-2.0', 'BSD-2-Clause', 'BSD-3-Clause'],
|
|
174
|
-
dependency_license_blocklist: ['GPL-3.0', 'AGPL-3.0'],
|
|
175
|
-
},
|
|
176
|
-
dependency_policy: {
|
|
177
|
-
allow_new_dependencies: true,
|
|
178
|
-
prefer_stdlib: true,
|
|
179
|
-
require_user_approval_for_native_deps: true,
|
|
180
|
-
blocked_dependencies: [],
|
|
181
|
-
},
|
|
182
|
-
compatibility_policy: {
|
|
183
|
-
mirror_public_behavior: true,
|
|
184
|
-
mirror_public_api_names: true,
|
|
185
|
-
mirror_private_structure: false,
|
|
186
|
-
mirror_comments_or_internal_names: false,
|
|
187
|
-
allowed_exactness: [
|
|
188
|
-
'public API names',
|
|
189
|
-
'CLI flags',
|
|
190
|
-
'serialized outputs',
|
|
191
|
-
'documented protocol behavior',
|
|
192
|
-
'public error codes',
|
|
193
|
-
],
|
|
194
|
-
},
|
|
195
|
-
feature_policy: {
|
|
196
|
-
preserve_features: [],
|
|
197
|
-
remove_features: [],
|
|
198
|
-
add_features: [],
|
|
199
|
-
non_goals: [],
|
|
200
|
-
},
|
|
201
|
-
code_hygiene_policy: {
|
|
202
|
-
max_lines_per_code_file: 500,
|
|
203
|
-
max_lines_per_test_file: 800,
|
|
204
|
-
max_files_per_iteration: 12,
|
|
205
|
-
split_large_files_by: ['module boundary', 'public type', 'feature area'],
|
|
206
|
-
exceptions: ['generated files', 'fixtures', 'snapshots'],
|
|
207
|
-
forbidden_patterns: ['god file', 'source-shaped layout'],
|
|
208
|
-
},
|
|
209
|
-
output_policy: {
|
|
210
|
-
artifact_base_root: '~/Documents/CleanRoom/<task-id>/',
|
|
211
|
-
implementation_root: '~/Documents/CleanRoom/<task-id>/implementation/',
|
|
212
|
-
assumed_output_directory: 'implementation/',
|
|
213
|
-
write_mode: 'create-or-preserve-existing',
|
|
214
|
-
},
|
|
215
|
-
execution_policy: {
|
|
216
|
-
backend: 'host',
|
|
217
|
-
preferred_container_profile: 'node22',
|
|
218
|
-
network_policy: 'off',
|
|
219
|
-
dependency_install_policy: 'locked',
|
|
220
|
-
allow_native_toolchain: false,
|
|
221
|
-
resource_limits: {
|
|
222
|
-
cpus: 2,
|
|
223
|
-
memory_mb: 2048,
|
|
224
|
-
timeout_seconds: 300,
|
|
225
|
-
},
|
|
226
|
-
},
|
|
227
|
-
controller_policy: {
|
|
228
|
-
mode: 'attended',
|
|
229
|
-
unattended_allowed_after_preflight: false,
|
|
230
|
-
max_iterations: 10,
|
|
231
|
-
},
|
|
232
|
-
open_questions: [
|
|
233
|
-
{
|
|
234
|
-
question_id: 'goal-end-state',
|
|
235
|
-
question: 'Define the end goal, target stack, compatibility exactness, dependency policy, license policy, and output root before execution.',
|
|
236
|
-
blocking: true,
|
|
237
|
-
default_assumption: 'Do not start source analysis until this is answered.',
|
|
238
|
-
},
|
|
239
|
-
],
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
function expectObject(value, label, errors) {
|
|
244
|
-
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
245
|
-
errors.push(`${label} must be an object`);
|
|
246
|
-
return false;
|
|
247
|
-
}
|
|
248
|
-
return true;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
function expectArray(value, label, errors) {
|
|
252
|
-
if (!Array.isArray(value)) {
|
|
253
|
-
errors.push(`${label} must be an array`);
|
|
254
|
-
return false;
|
|
255
|
-
}
|
|
256
|
-
return true;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
function expectString(value, label, errors, allowEmpty = false) {
|
|
260
|
-
if (typeof value !== 'string' || (!allowEmpty && value.length === 0)) {
|
|
261
|
-
errors.push(`${label} must be a non-empty string`);
|
|
262
|
-
return false;
|
|
263
|
-
}
|
|
264
|
-
return true;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
function expectBoolean(value, label, errors) {
|
|
268
|
-
if (typeof value !== 'boolean') {
|
|
269
|
-
errors.push(`${label} must be a boolean`);
|
|
270
|
-
return false;
|
|
271
|
-
}
|
|
272
|
-
return true;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
function expectPositiveInteger(value, label, errors) {
|
|
276
|
-
if (!Number.isInteger(value) || value < 1) {
|
|
277
|
-
errors.push(`${label} must be a positive integer`);
|
|
278
|
-
return false;
|
|
279
|
-
}
|
|
280
|
-
return true;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
function validateStringArray(root, field, errors) {
|
|
284
|
-
if (!expectArray(root?.[field], field, errors)) return;
|
|
285
|
-
for (const [index, item] of root[field].entries()) {
|
|
286
|
-
expectString(item, `${field}[${index}]`, errors);
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
function validateGoalContract(goal, options = {}) {
|
|
291
|
-
const errors = [];
|
|
292
|
-
if (!expectObject(goal, 'preflight goal', errors)) {
|
|
293
|
-
return errors;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
expectString(goal.goal_id, 'goal_id', errors);
|
|
297
|
-
expectString(goal.created_at, 'created_at', errors);
|
|
298
|
-
|
|
299
|
-
if (expectObject(goal.end_goal, 'end_goal', errors)) {
|
|
300
|
-
if (!VALID_INTENTS.has(goal.end_goal.intent)) {
|
|
301
|
-
errors.push('end_goal.intent is not supported');
|
|
302
|
-
}
|
|
303
|
-
expectString(goal.end_goal.success_definition, 'end_goal.success_definition', errors);
|
|
304
|
-
expectString(goal.end_goal.destination_kind, 'end_goal.destination_kind', errors);
|
|
305
|
-
expectString(goal.end_goal.existing_destination_policy, 'end_goal.existing_destination_policy', errors);
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
if (expectObject(goal.target_stack, 'target_stack', errors)) {
|
|
309
|
-
expectString(goal.target_stack.language, 'target_stack.language', errors);
|
|
310
|
-
for (const field of ['runtime', 'framework', 'package_manager', 'test_framework']) {
|
|
311
|
-
if (goal.target_stack[field] !== null) {
|
|
312
|
-
expectString(goal.target_stack[field], `target_stack.${field}`, errors);
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
if (expectObject(goal.license_policy, 'license_policy', errors)) {
|
|
318
|
-
expectString(goal.license_policy.source_license_notes, 'license_policy.source_license_notes', errors, true);
|
|
319
|
-
expectString(goal.license_policy.destination_license, 'license_policy.destination_license', errors);
|
|
320
|
-
validateStringArray(goal.license_policy, 'dependency_license_allowlist', errors);
|
|
321
|
-
validateStringArray(goal.license_policy, 'dependency_license_blocklist', errors);
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
if (expectObject(goal.dependency_policy, 'dependency_policy', errors)) {
|
|
325
|
-
expectBoolean(goal.dependency_policy.allow_new_dependencies, 'dependency_policy.allow_new_dependencies', errors);
|
|
326
|
-
expectBoolean(goal.dependency_policy.prefer_stdlib, 'dependency_policy.prefer_stdlib', errors);
|
|
327
|
-
expectBoolean(
|
|
328
|
-
goal.dependency_policy.require_user_approval_for_native_deps,
|
|
329
|
-
'dependency_policy.require_user_approval_for_native_deps',
|
|
330
|
-
errors
|
|
331
|
-
);
|
|
332
|
-
validateStringArray(goal.dependency_policy, 'blocked_dependencies', errors);
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
if (expectObject(goal.compatibility_policy, 'compatibility_policy', errors)) {
|
|
336
|
-
expectBoolean(goal.compatibility_policy.mirror_public_behavior, 'compatibility_policy.mirror_public_behavior', errors);
|
|
337
|
-
expectBoolean(goal.compatibility_policy.mirror_public_api_names, 'compatibility_policy.mirror_public_api_names', errors);
|
|
338
|
-
if (goal.compatibility_policy.mirror_private_structure !== false) {
|
|
339
|
-
errors.push('compatibility_policy.mirror_private_structure must be false');
|
|
340
|
-
}
|
|
341
|
-
if (goal.compatibility_policy.mirror_comments_or_internal_names !== false) {
|
|
342
|
-
errors.push('compatibility_policy.mirror_comments_or_internal_names must be false');
|
|
343
|
-
}
|
|
344
|
-
validateStringArray(goal.compatibility_policy, 'allowed_exactness', errors);
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
if (expectObject(goal.feature_policy, 'feature_policy', errors)) {
|
|
348
|
-
for (const field of ['preserve_features', 'remove_features', 'add_features', 'non_goals']) {
|
|
349
|
-
validateStringArray(goal.feature_policy, field, errors);
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
validateCodeHygienePolicy(goal.code_hygiene_policy, errors);
|
|
354
|
-
|
|
355
|
-
if (expectObject(goal.output_policy, 'output_policy', errors)) {
|
|
356
|
-
expectString(goal.output_policy.artifact_base_root, 'output_policy.artifact_base_root', errors);
|
|
357
|
-
expectString(goal.output_policy.implementation_root, 'output_policy.implementation_root', errors);
|
|
358
|
-
expectString(goal.output_policy.assumed_output_directory, 'output_policy.assumed_output_directory', errors);
|
|
359
|
-
expectString(goal.output_policy.write_mode, 'output_policy.write_mode', errors);
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
if (goal.execution_policy !== undefined) {
|
|
363
|
-
validateExecutionPolicy(goal.execution_policy, errors);
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
if (expectObject(goal.controller_policy, 'controller_policy', errors)) {
|
|
367
|
-
if (!VALID_MODES.has(goal.controller_policy.mode)) {
|
|
368
|
-
errors.push('controller_policy.mode must be attended or unattended');
|
|
369
|
-
}
|
|
370
|
-
expectBoolean(
|
|
371
|
-
goal.controller_policy.unattended_allowed_after_preflight,
|
|
372
|
-
'controller_policy.unattended_allowed_after_preflight',
|
|
373
|
-
errors
|
|
374
|
-
);
|
|
375
|
-
expectPositiveInteger(goal.controller_policy.max_iterations, 'controller_policy.max_iterations', errors);
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
if (expectArray(goal.open_questions, 'open_questions', errors)) {
|
|
379
|
-
for (const [index, question] of goal.open_questions.entries()) {
|
|
380
|
-
if (!expectObject(question, `open_questions[${index}]`, errors)) continue;
|
|
381
|
-
expectString(question.question_id, `open_questions[${index}].question_id`, errors);
|
|
382
|
-
expectString(question.question, `open_questions[${index}].question`, errors);
|
|
383
|
-
expectBoolean(question.blocking, `open_questions[${index}].blocking`, errors);
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
if (goal.controller_policy?.mode === 'unattended') {
|
|
388
|
-
if (goal.controller_policy.unattended_allowed_after_preflight !== true) {
|
|
389
|
-
errors.push('unattended preflight requires unattended_allowed_after_preflight=true');
|
|
390
|
-
}
|
|
391
|
-
if (Array.isArray(goal.open_questions) && goal.open_questions.length > 0) {
|
|
392
|
-
errors.push('unattended preflight requires no open_questions');
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
if (options.requireComplete && Array.isArray(goal.open_questions)) {
|
|
397
|
-
const blocking = goal.open_questions.filter((question) => question?.blocking === true);
|
|
398
|
-
if (blocking.length > 0) {
|
|
399
|
-
errors.push('completed preflight input must not contain blocking open_questions');
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
return errors;
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
function validateExecutionPolicy(policy, errors) {
|
|
407
|
-
if (!expectObject(policy, 'execution_policy', errors)) return;
|
|
408
|
-
if (!VALID_EXECUTION_BACKENDS.has(policy.backend)) {
|
|
409
|
-
errors.push('execution_policy.backend must be host, docker, or podman');
|
|
410
|
-
}
|
|
411
|
-
if (!VALID_CONTAINER_PROFILES.has(policy.preferred_container_profile)) {
|
|
412
|
-
errors.push('execution_policy.preferred_container_profile is not supported');
|
|
413
|
-
}
|
|
414
|
-
if (!VALID_NETWORK_POLICIES.has(policy.network_policy)) {
|
|
415
|
-
errors.push('execution_policy.network_policy must be off, deps-only, or on');
|
|
416
|
-
}
|
|
417
|
-
if (!VALID_DEPENDENCY_INSTALL_POLICIES.has(policy.dependency_install_policy)) {
|
|
418
|
-
errors.push('execution_policy.dependency_install_policy must be offline, locked, or allow-new');
|
|
419
|
-
}
|
|
420
|
-
expectBoolean(policy.allow_native_toolchain, 'execution_policy.allow_native_toolchain', errors);
|
|
421
|
-
if (!expectObject(policy.resource_limits, 'execution_policy.resource_limits', errors)) return;
|
|
422
|
-
expectPositiveInteger(policy.resource_limits.memory_mb, 'execution_policy.resource_limits.memory_mb', errors);
|
|
423
|
-
expectPositiveInteger(policy.resource_limits.timeout_seconds, 'execution_policy.resource_limits.timeout_seconds', errors);
|
|
424
|
-
if (typeof policy.resource_limits.cpus !== 'number' || policy.resource_limits.cpus < 1 || policy.resource_limits.cpus > 16) {
|
|
425
|
-
errors.push('execution_policy.resource_limits.cpus must be a number between 1 and 16');
|
|
426
|
-
}
|
|
427
|
-
if (Number.isInteger(policy.resource_limits.memory_mb) && policy.resource_limits.memory_mb > 65536) {
|
|
428
|
-
errors.push('execution_policy.resource_limits.memory_mb must be at most 65536');
|
|
429
|
-
}
|
|
430
|
-
if (Number.isInteger(policy.resource_limits.timeout_seconds) && policy.resource_limits.timeout_seconds > 600) {
|
|
431
|
-
errors.push('execution_policy.resource_limits.timeout_seconds must be at most 600');
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
function validateCodeHygienePolicy(policy, errors) {
|
|
436
|
-
if (!expectObject(policy, 'code_hygiene_policy', errors)) return;
|
|
437
|
-
expectPositiveInteger(policy.max_lines_per_code_file, 'code_hygiene_policy.max_lines_per_code_file', errors);
|
|
438
|
-
expectPositiveInteger(policy.max_lines_per_test_file, 'code_hygiene_policy.max_lines_per_test_file', errors);
|
|
439
|
-
expectPositiveInteger(policy.max_files_per_iteration, 'code_hygiene_policy.max_files_per_iteration', errors);
|
|
440
|
-
validateStringArray(policy, 'split_large_files_by', errors);
|
|
441
|
-
validateStringArray(policy, 'exceptions', errors);
|
|
442
|
-
if (policy.forbidden_patterns !== undefined) {
|
|
443
|
-
validateStringArray(policy, 'forbidden_patterns', errors);
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
function writePreflightOutput(outputPath, goal, options) {
|
|
448
|
-
const data = `${JSON.stringify(goal, null, 2)}\n`;
|
|
449
|
-
if (options.dryRun) {
|
|
450
|
-
console.log(`Would write preflight goal: ${outputPath}`);
|
|
451
|
-
return;
|
|
452
|
-
}
|
|
453
|
-
try {
|
|
454
|
-
if (options.force) {
|
|
455
|
-
atomicWriteFile(outputPath, data, 'utf8');
|
|
456
|
-
} else {
|
|
457
|
-
atomicWriteFileNoOverwrite(outputPath, data, 'utf8');
|
|
458
|
-
}
|
|
459
|
-
} catch (err) {
|
|
460
|
-
if (err?.code === 'EEXIST') {
|
|
461
|
-
throw new Error(`preflight output already exists; use --force to overwrite: ${outputPath}`);
|
|
462
|
-
}
|
|
463
|
-
throw err;
|
|
464
|
-
}
|
|
465
|
-
console.log(`Wrote preflight goal: ${outputPath}`);
|
|
466
|
-
}
|
|
467
|
-
|
|
8
|
+
const { VALID_MODES } = require('./preflight-constants.cjs');
|
|
9
|
+
const { parsePreflightArgs, printPreflightHelp } = require('./preflight-cli.cjs');
|
|
10
|
+
const {
|
|
11
|
+
applyBootstrapOutputPolicy,
|
|
12
|
+
validateBootstrapOutputPolicy,
|
|
13
|
+
} = require('./preflight-bootstrap.cjs');
|
|
14
|
+
const { resolveInputPath, resolveOutputPath } = require('./preflight-paths.cjs');
|
|
15
|
+
const { buildTemplate } = require('./preflight-template.cjs');
|
|
16
|
+
const { validateGoalContract } = require('./preflight-validation.cjs');
|
|
17
|
+
const { writePreflightOutput } = require('./preflight-output.cjs');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Run the preflight flow from argv.
|
|
21
|
+
* @param {string[]} argv - Command line arguments.
|
|
22
|
+
* @param {object} [context={}] - Environment context containing cwd and homeDir.
|
|
23
|
+
* @returns {object|null} Parsed preflight result containing outputPath and goal, or null if help printed.
|
|
24
|
+
*/
|
|
468
25
|
function runPreflight(argv, context = {}) {
|
|
469
26
|
const parsed = parsePreflightArgs(argv);
|
|
470
27
|
if (parsed.help) {
|