genesis-compiler 1.2.4 → 1.2.6
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 +11 -2
- package/package.json +2 -2
- package/plugins/genesis/.codex-plugin/plugin.json +1 -1
- package/prompts/adopt.txt +6 -0
- package/src/cli.js +18 -2
- package/src/index/check.js +24 -0
- package/src/index/contracts.js +7 -0
- package/src/index/deployment.js +2 -0
- package/src/index/environment-files.js +2 -0
- package/src/index/launch.js +2 -0
- package/src/index/process.js +119 -5
- package/src/index/verification.js +13 -0
- package/src/index/workspace-setup.js +8 -0
- package/src/index.js +2 -0
package/README.md
CHANGED
|
@@ -201,8 +201,9 @@ genesis inspect deployment --json
|
|
|
201
201
|
```
|
|
202
202
|
|
|
203
203
|
Without `--json`, the commands print concise human-readable summaries. They
|
|
204
|
-
never
|
|
205
|
-
|
|
204
|
+
never start the application, probe a server, materialize environment files, or
|
|
205
|
+
deploy. `genesis prepare` is the separate explicit command that executes only
|
|
206
|
+
the finite Workspace setup recipe.
|
|
206
207
|
|
|
207
208
|
At session start, Codex receives only a short explanation of how Genesis is
|
|
208
209
|
organized. After it locates the source involved in a request, it can load the
|
|
@@ -428,6 +429,8 @@ genesis check
|
|
|
428
429
|
- Blueprint and Stack validity;
|
|
429
430
|
- selected Agent Skill presence and structural validity;
|
|
430
431
|
- Program presence and structural validity;
|
|
432
|
+
- Workspace setup, environment, Launch, and Deployment as `ready`, `blocked`,
|
|
433
|
+
or honestly `unconfigured`;
|
|
431
434
|
- missing Stack-declared environment inputs (without claiming the service is
|
|
432
435
|
reachable);
|
|
433
436
|
- verification evidence as `current`, `stale`, `missing`, `invalid`, or
|
|
@@ -484,6 +487,12 @@ identity, secret, and browser policy.
|
|
|
484
487
|
`inspectDeployment()` returns the separate normalized production recipe without
|
|
485
488
|
executing or provisioning it.
|
|
486
489
|
|
|
490
|
+
Normalized operational results identify their stable public contract in the
|
|
491
|
+
`contract` field: `genesis.workspace-setup.v1`, `genesis.environment.v1`,
|
|
492
|
+
`genesis.launch.v1`, `genesis.deployment.v1`, or
|
|
493
|
+
`genesis.verification.v1`. Hosts validate that identity instead of
|
|
494
|
+
feature-detecting individual fields.
|
|
495
|
+
|
|
487
496
|
A host such as Vibe64 can send the generated prompt to its existing agent:
|
|
488
497
|
|
|
489
498
|
```js
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "genesis-compiler",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "An agent-independent prompt, multi-language code-index, cleanup, and verification companion with optional Codex hooks.",
|
|
6
6
|
"repository": {
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"@ast-grep/lang-ruby": "^0.0.7",
|
|
59
59
|
"@ast-grep/lang-rust": "^0.0.7",
|
|
60
60
|
"@ast-grep/napi": "^0.45.1",
|
|
61
|
-
"@jskit-ai/agent-docs": "0.1.
|
|
61
|
+
"@jskit-ai/agent-docs": "0.1.134",
|
|
62
62
|
"yaml": "^2.9.0"
|
|
63
63
|
}
|
|
64
64
|
}
|
package/prompts/adopt.txt
CHANGED
|
@@ -50,6 +50,12 @@ or tool-specific manifests as evidence and carry every still-valid fact into
|
|
|
50
50
|
Genesis; never add a permanent legacy reader or shim. Do not delete an old
|
|
51
51
|
manifest until all of its live facts have been accounted for.
|
|
52
52
|
|
|
53
|
+
The resulting project contract may therefore own complete `## Resources`,
|
|
54
|
+
`## Environment defaults`, `## Environment files`, `## Workspace setup`,
|
|
55
|
+
`## Commands`, `## Launch`, and `## Deployment` sections. Treat these as one
|
|
56
|
+
operational inventory: do not preserve setup while silently dropping its
|
|
57
|
+
resource, launch, readiness, identity, verification, or deployment counterpart.
|
|
58
|
+
|
|
53
59
|
Create the non-technical Blueprint and useful subsystem-oriented Program from
|
|
54
60
|
current source and tests. Do not cite archive internals or Git object storage as
|
|
55
61
|
implementation source; Program sources must be current Git-visible files.
|
package/src/cli.js
CHANGED
|
@@ -131,6 +131,10 @@ function writeCheck(result) {
|
|
|
131
131
|
line(process.stdout, `Stack: ${result.stack}`);
|
|
132
132
|
line(process.stdout, `Agent Skills: ${result.skills}`);
|
|
133
133
|
line(process.stdout, `Program: ${result.program}`);
|
|
134
|
+
line(process.stdout, `Workspace setup: ${result.workspaceSetup}`);
|
|
135
|
+
line(process.stdout, `Environment: ${result.environment}`);
|
|
136
|
+
line(process.stdout, `Launch: ${result.launch}`);
|
|
137
|
+
line(process.stdout, `Deployment: ${result.deployment}`);
|
|
134
138
|
line(process.stdout, `Resource inputs: ${result.resources}`);
|
|
135
139
|
line(process.stdout, `Verification: ${result.verification}`);
|
|
136
140
|
namedItems('Program files', result.programFiles);
|
|
@@ -262,7 +266,7 @@ async function hookInput() {
|
|
|
262
266
|
}
|
|
263
267
|
}
|
|
264
268
|
|
|
265
|
-
async function execute({ command, operands, options }) {
|
|
269
|
+
async function execute({ command, operands, options }, { signal } = {}) {
|
|
266
270
|
const projectRoot = options.projectRoot || process.cwd();
|
|
267
271
|
if (command === 'init') return initialize({ projectRoot });
|
|
268
272
|
if (command === 'adopt') {
|
|
@@ -294,6 +298,7 @@ async function execute({ command, operands, options }) {
|
|
|
294
298
|
if (command === 'prepare') {
|
|
295
299
|
return prepareWorkspace({
|
|
296
300
|
projectRoot,
|
|
301
|
+
signal,
|
|
297
302
|
onEvent: (event) => {
|
|
298
303
|
if (!options.json) line(process.stderr, event.message);
|
|
299
304
|
},
|
|
@@ -318,6 +323,7 @@ async function execute({ command, operands, options }) {
|
|
|
318
323
|
if (command === 'verify') {
|
|
319
324
|
return verify({
|
|
320
325
|
projectRoot,
|
|
326
|
+
signal,
|
|
321
327
|
onEvent: (event) => {
|
|
322
328
|
if (!options.json) line(process.stderr, event.message);
|
|
323
329
|
},
|
|
@@ -329,6 +335,8 @@ async function execute({ command, operands, options }) {
|
|
|
329
335
|
|
|
330
336
|
export async function runCli(argv = process.argv.slice(2)) {
|
|
331
337
|
let options = {};
|
|
338
|
+
const controller = new AbortController();
|
|
339
|
+
const abort = () => controller.abort();
|
|
332
340
|
try {
|
|
333
341
|
const parsed = parseCommand(argv);
|
|
334
342
|
if (parsed.command === 'help') {
|
|
@@ -336,7 +344,12 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
336
344
|
return 0;
|
|
337
345
|
}
|
|
338
346
|
options = parsed.options;
|
|
339
|
-
const
|
|
347
|
+
const finiteCommand = ['prepare', 'verify'].includes(parsed.command);
|
|
348
|
+
if (finiteCommand) {
|
|
349
|
+
process.once('SIGINT', abort);
|
|
350
|
+
process.once('SIGTERM', abort);
|
|
351
|
+
}
|
|
352
|
+
const result = await execute(parsed, { signal: controller.signal });
|
|
340
353
|
if (options.json) line(process.stdout, JSON.stringify(result));
|
|
341
354
|
else writeResult(parsed.command, result);
|
|
342
355
|
return ['blocked', 'failed', 'invalid'].includes(result?.status) ? 2 : 0;
|
|
@@ -345,5 +358,8 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
345
358
|
if (options.json) line(process.stderr, JSON.stringify(diagnostic));
|
|
346
359
|
else line(process.stderr, `${diagnostic.code}: ${diagnostic.message}`);
|
|
347
360
|
return 1;
|
|
361
|
+
} finally {
|
|
362
|
+
process.removeListener('SIGINT', abort);
|
|
363
|
+
process.removeListener('SIGTERM', abort);
|
|
348
364
|
}
|
|
349
365
|
}
|
package/src/index/check.js
CHANGED
|
@@ -7,6 +7,10 @@ import { inspectProgram } from './program.js';
|
|
|
7
7
|
import { inspectVerification } from './project-state.js';
|
|
8
8
|
import { missingStackResources } from './stack-preflight.js';
|
|
9
9
|
import { withStackEnvironmentDefaults } from './stack-environment-defaults.js';
|
|
10
|
+
import { inspectProjectEnvironment } from './environment-files.js';
|
|
11
|
+
import { inspectProjectLaunch } from './launch.js';
|
|
12
|
+
import { inspectProjectDeployment } from './deployment.js';
|
|
13
|
+
import { inspectWorkspaceSetupForStack } from './workspace-setup.js';
|
|
10
14
|
|
|
11
15
|
function invalidResult(area, error) {
|
|
12
16
|
return {
|
|
@@ -15,6 +19,10 @@ function invalidResult(area, error) {
|
|
|
15
19
|
stack: area === 'stack' ? 'invalid' : 'unknown',
|
|
16
20
|
skills: area === 'skills' ? 'invalid' : 'unknown',
|
|
17
21
|
program: 'unknown',
|
|
22
|
+
workspaceSetup: 'unknown',
|
|
23
|
+
environment: 'unknown',
|
|
24
|
+
launch: 'unknown',
|
|
25
|
+
deployment: 'unknown',
|
|
18
26
|
resources: 'unknown',
|
|
19
27
|
verification: 'unknown',
|
|
20
28
|
programFiles: [],
|
|
@@ -61,10 +69,18 @@ export async function checkProject({ environment = process.env, projectRoot } =
|
|
|
61
69
|
resources: stack.resources,
|
|
62
70
|
});
|
|
63
71
|
const verification = await inspectVerification({ projectRoot: root, stack });
|
|
72
|
+
const [workspaceSetup, projectEnvironment, launch, deployment] = await Promise.all([
|
|
73
|
+
inspectWorkspaceSetupForStack({ projectRoot: root, stack }),
|
|
74
|
+
inspectProjectEnvironment({ environment, projectRoot: root }),
|
|
75
|
+
inspectProjectLaunch({ environment, projectRoot: root }),
|
|
76
|
+
inspectProjectDeployment({ projectRoot: root }),
|
|
77
|
+
]);
|
|
64
78
|
const diagnostics = [
|
|
65
79
|
...(program.diagnostic ? [program.diagnostic] : []),
|
|
66
80
|
...skills.diagnostics,
|
|
67
81
|
...missingResources,
|
|
82
|
+
...workspaceSetup.diagnostics.filter(({ code }) => code !== 'STACK_WORKSPACE_SETUP_WAITING'),
|
|
83
|
+
...deployment.diagnostics,
|
|
68
84
|
...(verification.status === 'invalid' ? [{
|
|
69
85
|
code: 'VERIFICATION_EVIDENCE_INVALID',
|
|
70
86
|
message: 'Saved verification evidence is malformed.',
|
|
@@ -79,6 +95,8 @@ export async function checkProject({ environment = process.env, projectRoot } =
|
|
|
79
95
|
if (missingResources.length > 0) {
|
|
80
96
|
guidance.push(`${missingResources.map(({ message }) => message).join(' ')} Prompt generation remains available.`);
|
|
81
97
|
}
|
|
98
|
+
if (workspaceSetup.status === 'blocked') guidance.push('Repair the project Workspace setup declaration.');
|
|
99
|
+
if (deployment.status === 'blocked') guidance.push('Repair the project Deployment declaration.');
|
|
82
100
|
if (['missing', 'stale'].includes(verification.status)) guidance.push('Run genesis verify to refresh concrete evidence.');
|
|
83
101
|
if (verification.status === 'unconfigured') guidance.push('Add project verification commands to genesis/stack.md.');
|
|
84
102
|
guidance.push('Use genesis prompt --task review for a semantic, evidence-based comparison.');
|
|
@@ -86,6 +104,8 @@ export async function checkProject({ environment = process.env, projectRoot } =
|
|
|
86
104
|
const needsAttention = program.status === 'missing'
|
|
87
105
|
|| skills.status === 'missing'
|
|
88
106
|
|| missingResources.length > 0
|
|
107
|
+
|| workspaceSetup.status === 'blocked'
|
|
108
|
+
|| deployment.status === 'blocked'
|
|
89
109
|
|| ['missing', 'stale', 'unconfigured'].includes(verification.status);
|
|
90
110
|
|
|
91
111
|
return {
|
|
@@ -96,6 +116,10 @@ export async function checkProject({ environment = process.env, projectRoot } =
|
|
|
96
116
|
stack: 'valid',
|
|
97
117
|
skills: skills.status,
|
|
98
118
|
program: program.status,
|
|
119
|
+
workspaceSetup: workspaceSetup.status,
|
|
120
|
+
environment: projectEnvironment.status,
|
|
121
|
+
launch: launch.status,
|
|
122
|
+
deployment: deployment.status,
|
|
99
123
|
resources: missingResources.length > 0 ? 'missing' : 'inputs-present',
|
|
100
124
|
verification: verification.status,
|
|
101
125
|
programFiles: program.files,
|
package/src/index/deployment.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { gitContext } from './git.js';
|
|
2
2
|
import { readStack } from './stack.js';
|
|
3
3
|
import { sha256, stableJson, uniqueSorted } from './utils.js';
|
|
4
|
+
import { GENESIS_CONTRACTS } from './contracts.js';
|
|
4
5
|
|
|
5
6
|
/** Read the Stack's production recipe without provisioning or publishing anything. */
|
|
6
7
|
export async function inspectProjectDeployment({ projectRoot } = {}) {
|
|
@@ -18,6 +19,7 @@ export async function inspectProjectDeployment({ projectRoot } = {}) {
|
|
|
18
19
|
steps: stack.deployment.steps,
|
|
19
20
|
};
|
|
20
21
|
return {
|
|
22
|
+
contract: GENESIS_CONTRACTS.deployment,
|
|
21
23
|
status,
|
|
22
24
|
stackHash: stack.identityHash,
|
|
23
25
|
recipeHash: status === 'ready' ? sha256(stableJson(recipe)) : '',
|
|
@@ -2,6 +2,7 @@ import { gitContext } from './git.js';
|
|
|
2
2
|
import { readStack } from './stack.js';
|
|
3
3
|
import { withStackEnvironmentDefaults } from './stack-environment-defaults.js';
|
|
4
4
|
import { missingStackResources } from './stack-preflight.js';
|
|
5
|
+
import { GENESIS_CONTRACTS } from './contracts.js';
|
|
5
6
|
|
|
6
7
|
/** Inspect Stack environment requirements without returning supplied host values. */
|
|
7
8
|
export async function inspectProjectEnvironment({ environment = process.env, projectRoot } = {}) {
|
|
@@ -15,6 +16,7 @@ export async function inspectProjectEnvironment({ environment = process.env, pro
|
|
|
15
16
|
|| stack.environmentFiles.length > 0
|
|
16
17
|
|| stack.resources.length > 0;
|
|
17
18
|
return {
|
|
19
|
+
contract: GENESIS_CONTRACTS.environment,
|
|
18
20
|
status: !configured ? 'unconfigured' : diagnostics.length > 0 ? 'missing-inputs' : 'ready',
|
|
19
21
|
stackHash: stack.identityHash,
|
|
20
22
|
components: stack.components.map(({ id }) => id),
|
package/src/index/launch.js
CHANGED
|
@@ -3,6 +3,7 @@ import { missingStackResources } from './stack-preflight.js';
|
|
|
3
3
|
import { withStackEnvironmentDefaults } from './stack-environment-defaults.js';
|
|
4
4
|
import { readStack } from './stack.js';
|
|
5
5
|
import { uniqueSorted } from './utils.js';
|
|
6
|
+
import { GENESIS_CONTRACTS } from './contracts.js';
|
|
6
7
|
|
|
7
8
|
/** Read the Stack's launch declaration without choosing or starting a runtime. */
|
|
8
9
|
export async function inspectProjectLaunch({
|
|
@@ -27,6 +28,7 @@ export async function inspectProjectLaunch({
|
|
|
27
28
|
if (targets.length === 0) status = 'unconfigured';
|
|
28
29
|
else if (diagnostics.length > 0) status = 'blocked';
|
|
29
30
|
return {
|
|
31
|
+
contract: GENESIS_CONTRACTS.launch,
|
|
30
32
|
status,
|
|
31
33
|
stackHash: stack.identityHash,
|
|
32
34
|
components: stack.components.map(({ id }) => id),
|
package/src/index/process.js
CHANGED
|
@@ -1,18 +1,120 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
2
|
|
|
3
3
|
import { GenesisError } from './errors.js';
|
|
4
4
|
|
|
5
5
|
const MAX_DIAGNOSTIC_OUTPUT = 16_384;
|
|
6
|
+
const DEFAULT_TERMINATION_GRACE_MS = 1_000;
|
|
6
7
|
|
|
7
|
-
function
|
|
8
|
+
function processTerminationError(code, message) {
|
|
9
|
+
const error = new Error(message);
|
|
10
|
+
error.code = code;
|
|
11
|
+
return error;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function signalProcessTree(child, signal) {
|
|
15
|
+
if (!Number.isSafeInteger(child?.pid) || child.pid < 1) return;
|
|
16
|
+
try {
|
|
17
|
+
if (process.platform === 'win32') child.kill(signal);
|
|
18
|
+
else process.kill(-child.pid, signal);
|
|
19
|
+
} catch (error) {
|
|
20
|
+
if (error?.code !== 'ESRCH') throw error;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function executeFile(command, args, {
|
|
25
|
+
maxBuffer,
|
|
26
|
+
signal,
|
|
27
|
+
terminationGraceMs = DEFAULT_TERMINATION_GRACE_MS,
|
|
28
|
+
timeoutMs,
|
|
29
|
+
...options
|
|
30
|
+
}) {
|
|
8
31
|
return new Promise((resolve, reject) => {
|
|
9
|
-
const
|
|
10
|
-
|
|
32
|
+
const stdoutChunks = [];
|
|
33
|
+
const stderrChunks = [];
|
|
34
|
+
let stdoutBytes = 0;
|
|
35
|
+
let stderrBytes = 0;
|
|
36
|
+
let forcedError = null;
|
|
37
|
+
let killTimer = null;
|
|
38
|
+
let spawnError = null;
|
|
39
|
+
let timeoutTimer = null;
|
|
40
|
+
let settled = false;
|
|
41
|
+
const cleanup = () => {
|
|
42
|
+
if (timeoutTimer) clearTimeout(timeoutTimer);
|
|
43
|
+
if (killTimer) clearTimeout(killTimer);
|
|
44
|
+
signal?.removeEventListener('abort', abort);
|
|
45
|
+
};
|
|
46
|
+
const child = spawn(command, args, {
|
|
47
|
+
...options,
|
|
48
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
49
|
+
});
|
|
50
|
+
const terminate = (error) => {
|
|
51
|
+
if (settled || forcedError) return;
|
|
52
|
+
forcedError = error;
|
|
53
|
+
signalProcessTree(child, 'SIGTERM');
|
|
54
|
+
killTimer = setTimeout(() => {
|
|
55
|
+
if (!settled) signalProcessTree(child, 'SIGKILL');
|
|
56
|
+
}, terminationGraceMs);
|
|
57
|
+
killTimer.unref?.();
|
|
58
|
+
};
|
|
59
|
+
const collect = (chunks, chunk, stream) => {
|
|
60
|
+
chunks.push(chunk);
|
|
61
|
+
if (stream === 'stdout') stdoutBytes += chunk.length;
|
|
62
|
+
else stderrBytes += chunk.length;
|
|
63
|
+
if (Number.isFinite(maxBuffer) && Math.max(stdoutBytes, stderrBytes) > maxBuffer) {
|
|
64
|
+
terminate(processTerminationError(
|
|
65
|
+
'GENESIS_PROCESS_OUTPUT_LIMIT',
|
|
66
|
+
`${command} exceeded its diagnostic output limit.`,
|
|
67
|
+
));
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
child.stdout.on('data', (chunk) => collect(stdoutChunks, chunk, 'stdout'));
|
|
71
|
+
child.stderr.on('data', (chunk) => collect(stderrChunks, chunk, 'stderr'));
|
|
72
|
+
child.once('error', (error) => {
|
|
73
|
+
spawnError = error;
|
|
74
|
+
});
|
|
75
|
+
child.once('close', (status, processSignal) => {
|
|
76
|
+
settled = true;
|
|
77
|
+
cleanup();
|
|
78
|
+
const stdout = Buffer.concat(stdoutChunks);
|
|
79
|
+
const stderr = Buffer.concat(stderrChunks);
|
|
80
|
+
if (forcedError) {
|
|
81
|
+
forcedError.stdout = stdout;
|
|
82
|
+
forcedError.stderr = stderr;
|
|
83
|
+
forcedError.signal = processSignal || null;
|
|
84
|
+
reject(forcedError);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (spawnError) {
|
|
88
|
+
spawnError.stdout = stdout;
|
|
89
|
+
spawnError.stderr = stderr;
|
|
90
|
+
spawnError.signal = processSignal || null;
|
|
91
|
+
reject(spawnError);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (status !== 0) {
|
|
95
|
+
const error = new Error(`${command} exited with status ${status ?? 'unknown'}.`);
|
|
96
|
+
error.code = status;
|
|
97
|
+
error.signal = processSignal || null;
|
|
98
|
+
error.stdout = stdout;
|
|
99
|
+
error.stderr = stderr;
|
|
11
100
|
reject(error);
|
|
12
101
|
return;
|
|
13
102
|
}
|
|
14
103
|
resolve({ stdout, stderr });
|
|
15
104
|
});
|
|
105
|
+
const abort = () => terminate(processTerminationError(
|
|
106
|
+
'GENESIS_PROCESS_ABORTED',
|
|
107
|
+
`${command} was cancelled.`,
|
|
108
|
+
));
|
|
109
|
+
if (signal?.aborted) abort();
|
|
110
|
+
else signal?.addEventListener('abort', abort, { once: true });
|
|
111
|
+
if (Number.isFinite(timeoutMs) && timeoutMs > 0) {
|
|
112
|
+
timeoutTimer = setTimeout(() => terminate(processTerminationError(
|
|
113
|
+
'GENESIS_PROCESS_TIMEOUT',
|
|
114
|
+
`${command} timed out after ${timeoutMs} ms.`,
|
|
115
|
+
)), timeoutMs);
|
|
116
|
+
timeoutTimer.unref?.();
|
|
117
|
+
}
|
|
16
118
|
child.stdin?.end();
|
|
17
119
|
});
|
|
18
120
|
}
|
|
@@ -39,21 +141,33 @@ export async function runProcess(command, args, {
|
|
|
39
141
|
env = process.env,
|
|
40
142
|
maxBytes = 32 * 1024 * 1024,
|
|
41
143
|
code = 'PROCESS_EXEC_FAILED',
|
|
144
|
+
signal,
|
|
145
|
+
terminationGraceMs,
|
|
146
|
+
timeoutMs,
|
|
42
147
|
} = {}) {
|
|
43
148
|
try {
|
|
44
149
|
const { stdout, stderr } = await executeFile(command, args, {
|
|
45
150
|
cwd,
|
|
151
|
+
detached: process.platform !== 'win32',
|
|
46
152
|
env,
|
|
47
153
|
encoding: 'buffer',
|
|
48
154
|
maxBuffer: maxBytes,
|
|
49
155
|
shell: false,
|
|
156
|
+
signal,
|
|
157
|
+
terminationGraceMs,
|
|
158
|
+
timeoutMs,
|
|
50
159
|
windowsHide: true,
|
|
51
160
|
});
|
|
52
161
|
return { status: 0, signal: null, stdout, stderr };
|
|
53
162
|
} catch (error) {
|
|
54
163
|
const stdout = Buffer.isBuffer(error.stdout) ? error.stdout : Buffer.from(error.stdout || '');
|
|
55
164
|
const stderr = Buffer.isBuffer(error.stderr) ? error.stderr : Buffer.from(error.stderr || '');
|
|
56
|
-
|
|
165
|
+
const diagnosticCode = error.code === 'GENESIS_PROCESS_ABORTED'
|
|
166
|
+
? 'PROCESS_ABORTED'
|
|
167
|
+
: error.code === 'GENESIS_PROCESS_TIMEOUT'
|
|
168
|
+
? 'PROCESS_TIMEOUT'
|
|
169
|
+
: code;
|
|
170
|
+
throw new GenesisError(diagnosticCode, `${command} failed: ${error.message}`, {
|
|
57
171
|
command,
|
|
58
172
|
args,
|
|
59
173
|
status: typeof error.code === 'number' ? error.code : null,
|
|
@@ -6,6 +6,9 @@ import { withStackEnvironmentDefaults } from './stack-environment-defaults.js';
|
|
|
6
6
|
import { missingStackResources } from './stack-preflight.js';
|
|
7
7
|
import { readStack } from './stack.js';
|
|
8
8
|
import { inspectWorkspaceSetupForStack } from './workspace-setup.js';
|
|
9
|
+
import { GENESIS_CONTRACTS } from './contracts.js';
|
|
10
|
+
|
|
11
|
+
const DEFAULT_FINITE_COMMAND_TIMEOUT_MS = 30 * 60 * 1_000;
|
|
9
12
|
|
|
10
13
|
async function emit(onEvent, event) {
|
|
11
14
|
try { await onEvent?.(event); } catch { /* Progress observers do not control verification. */ }
|
|
@@ -16,12 +19,15 @@ export async function verifyProject({
|
|
|
16
19
|
onEvent,
|
|
17
20
|
processRunner = runProcess,
|
|
18
21
|
projectRoot,
|
|
22
|
+
signal,
|
|
23
|
+
timeoutMs = DEFAULT_FINITE_COMMAND_TIMEOUT_MS,
|
|
19
24
|
} = {}) {
|
|
20
25
|
const root = (await gitContext(projectRoot)).repositoryRoot;
|
|
21
26
|
const stack = await readStack(root);
|
|
22
27
|
const workspaceSetup = await inspectWorkspaceSetupForStack({ projectRoot: root, stack });
|
|
23
28
|
if (workspaceSetup.status === 'blocked') {
|
|
24
29
|
return {
|
|
30
|
+
contract: GENESIS_CONTRACTS.verification,
|
|
25
31
|
status: 'blocked',
|
|
26
32
|
summary: workspaceSetup.diagnostics.map(({ message }) => message).join(' '),
|
|
27
33
|
commands: [],
|
|
@@ -33,6 +39,7 @@ export async function verifyProject({
|
|
|
33
39
|
);
|
|
34
40
|
if (waiting.length > 0) {
|
|
35
41
|
return {
|
|
42
|
+
contract: GENESIS_CONTRACTS.verification,
|
|
36
43
|
status: 'unconfigured',
|
|
37
44
|
summary: waiting.map(({ message }) => message).join(' '),
|
|
38
45
|
commands: [],
|
|
@@ -49,6 +56,7 @@ export async function verifyProject({
|
|
|
49
56
|
});
|
|
50
57
|
if (missing.length > 0) {
|
|
51
58
|
return {
|
|
59
|
+
contract: GENESIS_CONTRACTS.verification,
|
|
52
60
|
status: 'blocked',
|
|
53
61
|
summary: missing.map(({ message }) => message).join(' '),
|
|
54
62
|
commands: [],
|
|
@@ -57,6 +65,7 @@ export async function verifyProject({
|
|
|
57
65
|
}
|
|
58
66
|
if (stack.commands.length === 0) {
|
|
59
67
|
return {
|
|
68
|
+
contract: GENESIS_CONTRACTS.verification,
|
|
60
69
|
status: 'unconfigured',
|
|
61
70
|
summary: 'The selected Stack declares no verification commands.',
|
|
62
71
|
commands: [],
|
|
@@ -79,6 +88,8 @@ export async function verifyProject({
|
|
|
79
88
|
env: resolvedEnvironment,
|
|
80
89
|
maxBytes: 32 * 1024 * 1024,
|
|
81
90
|
code: 'VERIFICATION_FAILED',
|
|
91
|
+
signal,
|
|
92
|
+
timeoutMs,
|
|
82
93
|
});
|
|
83
94
|
await emit(onEvent, {
|
|
84
95
|
type: 'genesis.verification',
|
|
@@ -90,6 +101,7 @@ export async function verifyProject({
|
|
|
90
101
|
}
|
|
91
102
|
const evidence = await writeVerification({ projectRoot: root, stack });
|
|
92
103
|
return {
|
|
104
|
+
contract: GENESIS_CONTRACTS.verification,
|
|
93
105
|
status: 'passed',
|
|
94
106
|
summary: `Passed ${commands.length} declared verification command${commands.length === 1 ? '' : 's'}.`,
|
|
95
107
|
commands,
|
|
@@ -98,6 +110,7 @@ export async function verifyProject({
|
|
|
98
110
|
};
|
|
99
111
|
} catch (error) {
|
|
100
112
|
return {
|
|
113
|
+
contract: GENESIS_CONTRACTS.verification,
|
|
101
114
|
status: 'failed',
|
|
102
115
|
summary: error.message,
|
|
103
116
|
commands: [],
|
|
@@ -7,6 +7,9 @@ import { runProcess } from './process.js';
|
|
|
7
7
|
import { readStack } from './stack.js';
|
|
8
8
|
import { withStackEnvironmentDefaults } from './stack-environment-defaults.js';
|
|
9
9
|
import { sha256, stableJson, uniqueSorted } from './utils.js';
|
|
10
|
+
import { GENESIS_CONTRACTS } from './contracts.js';
|
|
11
|
+
|
|
12
|
+
const DEFAULT_FINITE_COMMAND_TIMEOUT_MS = 30 * 60 * 1_000;
|
|
10
13
|
|
|
11
14
|
async function emit(onEvent, event) {
|
|
12
15
|
try { await onEvent?.(event); } catch { /* Progress observers do not control preparation. */ }
|
|
@@ -54,6 +57,7 @@ export async function inspectWorkspaceSetupForStack({ projectRoot: root, stack }
|
|
|
54
57
|
? sha256(stableJson({ version: 1, steps: applicableSteps }))
|
|
55
58
|
: '';
|
|
56
59
|
return {
|
|
60
|
+
contract: GENESIS_CONTRACTS.workspaceSetup,
|
|
57
61
|
status,
|
|
58
62
|
stackHash: stack.identityHash,
|
|
59
63
|
recipeHash,
|
|
@@ -82,6 +86,8 @@ export async function prepareProjectWorkspace({
|
|
|
82
86
|
onEvent,
|
|
83
87
|
processRunner = runProcess,
|
|
84
88
|
projectRoot,
|
|
89
|
+
signal,
|
|
90
|
+
timeoutMs = DEFAULT_FINITE_COMMAND_TIMEOUT_MS,
|
|
85
91
|
} = {}) {
|
|
86
92
|
const root = (await gitContext(projectRoot)).repositoryRoot;
|
|
87
93
|
const stack = await readStack(root);
|
|
@@ -114,6 +120,8 @@ export async function prepareProjectWorkspace({
|
|
|
114
120
|
env: resolvedEnvironment,
|
|
115
121
|
maxBytes: 32 * 1024 * 1024,
|
|
116
122
|
code: 'WORKSPACE_PREPARATION_FAILED',
|
|
123
|
+
signal,
|
|
124
|
+
timeoutMs,
|
|
117
125
|
});
|
|
118
126
|
commands.push({
|
|
119
127
|
label: step.label,
|
package/src/index.js
CHANGED
|
@@ -17,6 +17,8 @@ import {
|
|
|
17
17
|
prepareProjectWorkspace,
|
|
18
18
|
} from './index/workspace-setup.js';
|
|
19
19
|
|
|
20
|
+
export { GENESIS_CONTRACTS } from './index/contracts.js';
|
|
21
|
+
|
|
20
22
|
function withIndexResult(result, index) {
|
|
21
23
|
const changedFiles = [...new Set([...result.changedFiles, ...index.changedFiles])].sort();
|
|
22
24
|
const refreshedOnly = result.status === 'unchanged' && index.changedFiles.length > 0;
|