genesis-compiler 1.2.5 → 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 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 execute setup, start a process, probe a server, materialize environment
205
- files, or deploy.
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.5",
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": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genesis",
3
- "version": "1.2.5",
3
+ "version": "1.2.6",
4
4
  "description": "Makes Codex aware of optional Genesis adoption for existing projects.",
5
5
  "author": {
6
6
  "name": "Mobily Enterprises"
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 result = await execute(parsed);
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
  }
@@ -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,
@@ -0,0 +1,7 @@
1
+ export const GENESIS_CONTRACTS = Object.freeze({
2
+ deployment: 'genesis.deployment.v1',
3
+ environment: 'genesis.environment.v1',
4
+ launch: 'genesis.launch.v1',
5
+ verification: 'genesis.verification.v1',
6
+ workspaceSetup: 'genesis.workspace-setup.v1',
7
+ });
@@ -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),
@@ -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),
@@ -1,18 +1,120 @@
1
- import { execFile } from 'node:child_process';
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 executeFile(command, args, options) {
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 child = execFile(command, args, options, (error, stdout, stderr) => {
10
- if (error) {
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
- throw new GenesisError(code, `${command} failed: ${error.message}`, {
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;