genesis-compiler 1.2.12 → 1.2.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/prompts/adopt.txt CHANGED
@@ -28,20 +28,11 @@ relevant existing-application migration reference. Apply all SELECTED STACK
28
28
  ADOPTION GUIDANCE. Component defaults describe current foundations and are
29
29
  hypotheses until they match this source. They do not upgrade an older project.
30
30
 
31
- Build one evidence-backed import inventory covering, where applicable:
32
-
33
- - package manager, install/build commands, runtimes, workdirs, and generated
34
- prerequisites;
35
- - environment files, public defaults, required resources, and alternative
36
- environment names, without reading or returning secret values;
37
- - database engine, migration ownership and ledger, preparation command,
38
- idempotent seed behavior, and fresh versus existing-database safety;
39
- - every long-running target's exact argv, host/port placeholders, URL path,
40
- readiness predicate, and shutdown ownership;
41
- - application-owned Preview identity helper, protocol, supported selectors,
42
- environment names, runtime, and timeout when authenticated preview exists;
43
- - concrete verification commands, browser-test assumptions, and deployment
44
- preparation/build/migration/serve/readiness declarations.
31
+ Build one evidence-backed import inventory covering the selected technologies,
32
+ public environment/resource declarations, concrete Verification commands, and
33
+ every consumer-owned Stack section supported by direct source evidence. Use the
34
+ selected technology skill and the section's declared schema for any
35
+ consumer-specific detail; do not invent a generic operational grammar.
45
36
 
46
37
  Translate observed contracts into exact project-owned Stack sections whenever
47
38
  the selected components' defaults differ. A project section replaces the
@@ -51,18 +42,18 @@ Genesis; never add a permanent legacy reader or shim. Do not delete an old
51
42
  manifest until all of its live facts have been accounted for.
52
43
 
53
44
  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.
45
+ `## Environment defaults`, `## Environment files`, `## Verification`, and any
46
+ evidence-backed consumer-owned opaque sections. Treat these as one inventory:
47
+ preserve every observed contract without asking Genesis to infer or validate a
48
+ consumer's private semantics.
58
49
 
59
50
  Create the non-technical Blueprint and useful subsystem-oriented Program from
60
51
  current source and tests. Do not cite archive internals or Git object storage as
61
52
  implementation source; Program sources must be current Git-visible files.
62
53
 
63
- Run `genesis check` only for structural validation. Missing environment values,
64
- workspace markers, commands, helpers, seeds, or readiness evidence are honest
65
- import findings, not permission to invent or execute them. Finish with a clear
66
- inventory of what was imported, what remains unconfigured or blocked, and what
67
- would require a separately approved implementation port. Never claim the
54
+ Run `genesis check` only for structural validation. Missing environment inputs,
55
+ Verification commands, or evidence required by a consumer-owned contract are
56
+ honest import findings, not permission to invent or execute them. Finish with a
57
+ clear inventory of what was imported, what remains unconfigured or blocked, and
58
+ what would require a separately approved implementation port. Never claim the
68
59
  project is ready merely because Stack components were selected.
package/prompts/start.txt CHANGED
@@ -14,10 +14,9 @@ If the product direction becomes clear while Stack is still empty, do not end
14
14
  the turn after only writing or summarizing the Blueprint: present the relevant
15
15
  Stack choices in that same turn and ask the user to select one explicitly.
16
16
  This opening turn owns product direction and Stack selection, not final
17
- verification. Do not run `genesis verify` before implementation has created
18
- the selected technology's workspace substrate. If structural validation is
19
- useful, run `genesis check`; treat missing workspace markers as unconfigured,
20
- not as an implementation failure.
17
+ verification. Do not run `genesis verify` before implementation exists. If
18
+ structural validation is useful, run `genesis check`; consumer-owned operations
19
+ remain governed by their own tools and must not be inferred here.
21
20
 
22
21
  When `projectKind` is `existing`, ask what the user wants to understand or
23
22
  change. Use the current Blueprint, Stack, Program, and code index when present.
@@ -25,14 +24,13 @@ If the existing implementation is not yet explained, recommend the Genesis
25
24
  description/adoption path instead of pretending its intent is known.
26
25
  When an existing implementation has no selected Stack yet, inspect its actual
27
26
  package scripts, build files, setup commands, server entrypoint, and readiness
28
- route before recording the user's technology choice. Component Workspace setup
29
- and Launch declarations describe that component's current foundation; selecting
30
- the component does not port an older implementation to it. If the existing
31
- commands differ, add exact project-owned `## Workspace setup` and `## Launch`
32
- sections that replace the component defaults and match the code that exists.
33
- Never report the inherited setup or Launch as usable merely because the
34
- component was selected, and never add compatibility implementation without the
35
- user asking for a port.
27
+ route before recording the user's technology choice. Consumer-owned sections
28
+ declared by a component describe that component's current operational
29
+ foundation; selecting the component does not port an older implementation to
30
+ it. If the existing facts differ, add exact project-owned replacements using
31
+ the consumer's current schema and the code that exists. Never report an
32
+ inherited operation as usable merely because the component was selected, and
33
+ never add compatibility implementation without the user asking for a port.
36
34
 
37
35
  When `projectKind` is `existing-uninitialized`, ask what the user wants to
38
36
  understand or change and strongly recommend preparing the existing project for
package/prompts/work.txt CHANGED
@@ -8,14 +8,13 @@ Before adding a helper or public operation, query `genesis index <name-or-path>`
8
8
  and reuse an existing function when it already owns the behavior.
9
9
 
10
10
  This is the implementation turn. Do not edit the Blueprint, Program, or
11
- `.genesis/`; the separate reconciliation turn owns explanatory changes. Keep an
12
- exact Stack `## Launch` declaration aligned when the implementation establishes
13
- or intentionally changes how the project is started or how a preview host may request
14
- an application preview identity. Declare only command paths, protocol, supported
15
- identity types, environment variable names, runtimes, and timeout policy; never
16
- put an environment value or secret in Stack. Never invent missing external-resource
17
- values. Preserve useful work and report a genuinely missing resource instead of
18
- constructing a parallel fallback.
11
+ `.genesis/`; the separate reconciliation turn owns explanatory changes. Keep
12
+ applicable consumer-owned Stack sections aligned when the implementation changes
13
+ facts governed by those contracts. Follow the selected technology skill and the
14
+ section's own schema; Genesis only transports the section and does not interpret
15
+ or execute it. Never put an environment value or secret in Stack. Never invent
16
+ missing external-resource values. Preserve useful work and report a genuinely
17
+ missing resource instead of constructing a parallel fallback.
19
18
 
20
19
  Run focused checks when useful. Summarize files changed, checks actually run,
21
20
  and anything still requiring attention.
@@ -24,5 +23,5 @@ If no Stack components are selected, continue the project-opening
24
23
  conversation instead of guessing a technology. Use `genesis stack list` for
25
24
  the current catalog, present relevant choices, wait for an explicit decision,
26
25
  and add the selection with `genesis stack add <piece...>` before relying on
27
- technology-specific setup or launch behavior. Do not end after only updating
26
+ technology-specific operations. Do not end after only updating
28
27
  or summarizing the Blueprint while the Stack remains empty.
package/src/cli.js CHANGED
@@ -9,13 +9,10 @@ import {
9
9
  getContext,
10
10
  indexCodebase,
11
11
  initialize,
12
- inspectDeployment,
13
12
  inspectEnvironment,
14
- inspectLaunch,
15
- inspectWorkspaceSetup,
13
+ inspectStackSection,
16
14
  installCodex,
17
15
  listStackPieces,
18
- prepareWorkspace,
19
16
  verify,
20
17
  } from './index.js';
21
18
  import {
@@ -35,8 +32,8 @@ const USAGE = `Usage:
35
32
  genesis stack add <piece...>
36
33
  genesis context <path...>
37
34
  genesis index [function-or-path...]
38
- genesis inspect <workspace|environment|launch|deployment>
39
- genesis prepare
35
+ genesis inspect environment
36
+ genesis inspect section <name>
40
37
  genesis prompt [request...]
41
38
  genesis prompt --task <start|adopt|work|deslop|program|blueprint|describe|review> [request...]
42
39
  genesis verify
@@ -54,7 +51,7 @@ prompt to the agent you already use. Review all edits through the ordinary Git
54
51
  diff, then run genesis verify for the Stack's concrete checks.
55
52
  `;
56
53
 
57
- const COMMANDS = new Set(['adopt', 'check', 'codex', 'context', 'hook', 'index', 'init', 'inspect', 'prepare', 'prompt', 'stack', 'verify']);
54
+ const COMMANDS = new Set(['adopt', 'check', 'codex', 'context', 'hook', 'index', 'init', 'inspect', 'prompt', 'stack', 'verify']);
58
55
 
59
56
  function parseCommand(argv) {
60
57
  if (argv.length === 0 || argv.includes('--help') || argv.includes('-h') || argv[0] === 'help') {
@@ -105,10 +102,12 @@ function parseCommand(argv) {
105
102
  } else if (command === 'context' && operands.length === 0) {
106
103
  fail('CONTEXT_PATH_REQUIRED', 'Command context requires at least one project path.');
107
104
  } else if (command === 'inspect') {
108
- if (operands.length !== 1 || !['workspace', 'environment', 'launch', 'deployment'].includes(operands[0])) {
105
+ const ordinaryInspection = operands.length === 1 && operands[0] === 'environment';
106
+ const sectionInspection = operands.length >= 2 && operands[0] === 'section';
107
+ if (!ordinaryInspection && !sectionInspection) {
109
108
  fail(
110
109
  'CLI_INSPECT_TARGET_REQUIRED',
111
- 'Command inspect requires exactly one of: workspace, environment, launch, deployment.',
110
+ 'Command inspect requires environment or section <name>.',
112
111
  );
113
112
  }
114
113
  } else if (command === 'hook' && (operands.length !== 1 || !['begin', 'discover', 'end', 'session', 'stop'].includes(operands[0]))) {
@@ -134,10 +133,8 @@ function writeCheck(result) {
134
133
  line(process.stdout, `Stack: ${result.stack}`);
135
134
  line(process.stdout, `Agent Skills: ${result.skills}`);
136
135
  line(process.stdout, `Program: ${result.program}`);
137
- line(process.stdout, `Workspace setup: ${result.workspaceSetup}`);
138
136
  line(process.stdout, `Environment: ${result.environment}`);
139
- line(process.stdout, `Launch: ${result.launch}`);
140
- line(process.stdout, `Deployment: ${result.deployment}`);
137
+ line(process.stdout, `Stack extensions: ${result.extensions}`);
141
138
  line(process.stdout, `Resource inputs: ${result.resources}`);
142
139
  line(process.stdout, `Verification: ${result.verification}`);
143
140
  namedItems('Program files', result.programFiles);
@@ -152,21 +149,14 @@ function writeInspection(result) {
152
149
  line(process.stdout, `${title}: ${result.status}`);
153
150
  namedItems('Components', result.components);
154
151
  namedItems('Runtimes', result.runtimeRequirements);
155
- if (result.inspection === 'workspace') {
156
- namedItems('Preparation steps', result.steps.map(({ label, argv, workdir }) => (
157
- `${label} in ${workdir}: ${argv.join(' ')}`
158
- )));
159
- } else if (result.inspection === 'environment') {
152
+ if (result.inspection === 'environment') {
160
153
  namedItems('Public defaults', result.environmentDefaults.map(({ name, value }) => `${name}=${value}`));
161
154
  namedItems('Environment files', result.files.map(({ format, path: filePath }) => `${format}: ${filePath}`));
162
- } else if (result.inspection === 'launch') {
163
- namedItems('Targets', result.targets.map(({ id, label, urlPath, steps }) => (
164
- `${id} (${label}) ${urlPath}: ${steps.map(({ argv }) => argv.join(' ')).join(' -> ')}`
165
- )));
166
- } else {
167
- namedItems('Deployment steps', result.steps.map(({ label, argv }) => `${label}: ${argv.join(' ')}`));
155
+ } else if (result.inspection === 'section') {
156
+ if (result.source) line(process.stdout, `Source: ${result.source}`);
157
+ if (result.content) process.stdout.write(`${result.content}\n`);
168
158
  }
169
- namedItems('Resources', result.resources.map(({ component, resource }) => (
159
+ namedItems('Resources', result.resources?.map(({ component, resource }) => (
170
160
  `${resource.id} (${resource.kind}, ${component})`
171
161
  )));
172
162
  for (const item of result.diagnostics || []) line(process.stdout, `${item.code}: ${item.message}`);
@@ -215,19 +205,6 @@ function writeResult(command, result) {
215
205
  writeInspection(result);
216
206
  return;
217
207
  }
218
- if (command === 'prepare') {
219
- if (result.summary) line(process.stdout, result.summary);
220
- namedItems('Commands', result.commands?.map(({ label, argv }) => `${label}: ${argv.join(' ')}`));
221
- for (const item of result.diagnostics || []) {
222
- line(process.stdout, `${item.code}: ${item.message}`);
223
- for (const field of ['stdout', 'stderr']) {
224
- const output = item.details?.[field]?.trim();
225
- if (output) line(process.stdout, output);
226
- }
227
- }
228
- line(process.stdout, `prepare: ${result.status}`);
229
- return;
230
- }
231
208
  if (command === 'hook') {
232
209
  if (['discover', 'session'].includes(result.kind) && result.output) line(process.stdout, result.output);
233
210
  else if (result.kind === 'stop') line(process.stdout, JSON.stringify(result.output));
@@ -243,7 +220,7 @@ function writeResult(command, result) {
243
220
  }
244
221
  if (result.summary) line(process.stdout, result.summary);
245
222
  namedItems('Changed files', result.changedFiles);
246
- namedItems('Commands', result.commands?.map(({ label, argv }) => `${label}: ${argv.join(' ')}`));
223
+ namedItems('Verification commands', result.commands?.map(({ label, argv }) => `${label}: ${argv.join(' ')}`));
247
224
  for (const item of result.diagnostics || []) {
248
225
  line(process.stdout, `${item.code}: ${item.message}`);
249
226
  for (const field of ['stdout', 'stderr']) {
@@ -294,27 +271,22 @@ async function execute({ command, operands, options }, { signal } = {}) {
294
271
  if (command === 'context') return getContext({ paths: operands, projectRoot, stackPackages });
295
272
  if (command === 'index') return indexCodebase({ projectRoot, queries: operands, stackPackages });
296
273
  if (command === 'inspect') {
297
- const inspections = {
298
- workspace: inspectWorkspaceSetup,
299
- environment: inspectEnvironment,
300
- launch: inspectLaunch,
301
- deployment: inspectDeployment,
302
- };
274
+ if (operands[0] === 'section') {
275
+ return {
276
+ inspection: 'section',
277
+ ...await inspectStackSection({
278
+ name: operands.slice(1).join(' '),
279
+ projectRoot,
280
+ stackPackages,
281
+ }),
282
+ };
283
+ }
284
+ const inspections = { environment: inspectEnvironment };
303
285
  return {
304
286
  inspection: operands[0],
305
287
  ...await inspections[operands[0]]({ projectRoot, stackPackages }),
306
288
  };
307
289
  }
308
- if (command === 'prepare') {
309
- return prepareWorkspace({
310
- projectRoot,
311
- signal,
312
- stackPackages,
313
- onEvent: (event) => {
314
- if (!options.json) line(process.stderr, event.message);
315
- },
316
- });
317
- }
318
290
  if (command === 'hook') {
319
291
  if (operands[0] === 'discover') {
320
292
  return { kind: 'discover', ...await codexAdoptionRecommendation({ projectRoot }) };
@@ -356,7 +328,7 @@ export async function runCli(argv = process.argv.slice(2)) {
356
328
  return 0;
357
329
  }
358
330
  options = parsed.options;
359
- const finiteCommand = ['prepare', 'verify'].includes(parsed.command);
331
+ const finiteCommand = parsed.command === 'verify';
360
332
  if (finiteCommand) {
361
333
  process.once('SIGINT', abort);
362
334
  process.once('SIGTERM', abort);
@@ -8,9 +8,6 @@ import { inspectVerification } from './project-state.js';
8
8
  import { missingStackResources } from './stack-preflight.js';
9
9
  import { withStackEnvironmentDefaults } from './stack-environment-defaults.js';
10
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';
14
11
 
15
12
  function invalidResult(area, error) {
16
13
  return {
@@ -19,10 +16,8 @@ function invalidResult(area, error) {
19
16
  stack: area === 'stack' ? 'invalid' : 'unknown',
20
17
  skills: area === 'skills' ? 'invalid' : 'unknown',
21
18
  program: 'unknown',
22
- workspaceSetup: 'unknown',
23
19
  environment: 'unknown',
24
- launch: 'unknown',
25
- deployment: 'unknown',
20
+ extensions: 'unknown',
26
21
  resources: 'unknown',
27
22
  verification: 'unknown',
28
23
  programFiles: [],
@@ -73,18 +68,17 @@ export async function checkProject({
73
68
  resources: stack.resources,
74
69
  });
75
70
  const verification = await inspectVerification({ projectRoot: root, stack });
76
- const [workspaceSetup, projectEnvironment, launch, deployment] = await Promise.all([
77
- inspectWorkspaceSetupForStack({ projectRoot: root, stack }),
78
- inspectProjectEnvironment({ environment, projectRoot: root, stackPackages }),
79
- inspectProjectLaunch({ environment, projectRoot: root, stackPackages }),
80
- inspectProjectDeployment({ projectRoot: root, stackPackages }),
81
- ]);
71
+ const projectEnvironment = await inspectProjectEnvironment({
72
+ environment,
73
+ projectRoot: root,
74
+ stackPackages,
75
+ });
76
+ const extensionDiagnostics = stack.extensions.flatMap(({ diagnostics }) => diagnostics);
82
77
  const diagnostics = [
83
78
  ...(program.diagnostic ? [program.diagnostic] : []),
84
79
  ...skills.diagnostics,
85
80
  ...missingResources,
86
- ...workspaceSetup.diagnostics.filter(({ code }) => code !== 'STACK_WORKSPACE_SETUP_WAITING'),
87
- ...deployment.diagnostics,
81
+ ...extensionDiagnostics,
88
82
  ...(verification.status === 'invalid' ? [{
89
83
  code: 'VERIFICATION_EVIDENCE_INVALID',
90
84
  message: 'Saved verification evidence is malformed.',
@@ -99,8 +93,7 @@ export async function checkProject({
99
93
  if (missingResources.length > 0) {
100
94
  guidance.push(`${missingResources.map(({ message }) => message).join(' ')} Prompt generation remains available.`);
101
95
  }
102
- if (workspaceSetup.status === 'blocked') guidance.push('Repair the project Workspace setup declaration.');
103
- if (deployment.status === 'blocked') guidance.push('Repair the project Deployment declaration.');
96
+ if (extensionDiagnostics.length > 0) guidance.push('Resolve the competing consumer-owned Stack sections.');
104
97
  if (['missing', 'stale'].includes(verification.status)) guidance.push('Run genesis verify to refresh concrete evidence.');
105
98
  if (verification.status === 'unconfigured') guidance.push('Add project verification commands to genesis/stack.md.');
106
99
  guidance.push('Use genesis prompt --task review for a semantic, evidence-based comparison.');
@@ -108,8 +101,7 @@ export async function checkProject({
108
101
  const needsAttention = program.status === 'missing'
109
102
  || skills.status === 'missing'
110
103
  || missingResources.length > 0
111
- || workspaceSetup.status === 'blocked'
112
- || deployment.status === 'blocked'
104
+ || extensionDiagnostics.length > 0
113
105
  || ['missing', 'stale', 'unconfigured'].includes(verification.status);
114
106
 
115
107
  return {
@@ -120,10 +112,10 @@ export async function checkProject({
120
112
  stack: 'valid',
121
113
  skills: skills.status,
122
114
  program: program.status,
123
- workspaceSetup: workspaceSetup.status,
124
115
  environment: projectEnvironment.status,
125
- launch: launch.status,
126
- deployment: deployment.status,
116
+ extensions: extensionDiagnostics.length > 0
117
+ ? 'blocked'
118
+ : stack.extensions.length > 0 ? 'ready' : 'unconfigured',
127
119
  resources: missingResources.length > 0 ? 'missing' : 'inputs-present',
128
120
  verification: verification.status,
129
121
  programFiles: program.files,
@@ -115,9 +115,9 @@ export async function contextForProjectPaths({ paths, projectRoot, stackPackages
115
115
  '',
116
116
  '## Verification',
117
117
  '',
118
- ...(stack.commands.length === 0
118
+ ...(stack.verificationCommands.length === 0
119
119
  ? ['No verification commands are configured.']
120
- : stack.commands.map(({ label, argv }) => `- ${label}: ${argv.map((value) => `\`${value}\``).join(' ')}`)),
120
+ : stack.verificationCommands.map(({ label, argv }) => `- ${label}: ${argv.map((value) => `\`${value}\``).join(' ')}`)),
121
121
  '',
122
122
  ].join('\n');
123
123
  return {
@@ -125,7 +125,7 @@ export async function contextForProjectPaths({ paths, projectRoot, stackPackages
125
125
  paths: targets,
126
126
  modules: moduleSources.map(({ source: _source, ...module }) => module),
127
127
  components: stack.components.map(({ id }) => id),
128
- verificationCommands: stack.commands.map(({ label, argv }) => ({ label, argv })),
128
+ verificationCommands: stack.verificationCommands.map(({ label, argv }) => ({ label, argv })),
129
129
  warnings: [
130
130
  ...(program.diagnostic ? [program.diagnostic] : []),
131
131
  ...projectSkills.diagnostics,
@@ -1,8 +1,6 @@
1
1
  export const GENESIS_CONTRACTS = Object.freeze({
2
- deployment: 'genesis.deployment.v2',
3
2
  derivedArtifacts: 'genesis.derived-artifacts.v1',
4
3
  environment: 'genesis.environment.v1',
5
- launch: 'genesis.launch.v1',
4
+ stackSection: 'genesis.stack-section.v1',
6
5
  verification: 'genesis.verification.v1',
7
- workspaceSetup: 'genesis.workspace-setup.v1',
8
6
  });
@@ -9,7 +9,7 @@ const SCHEMA_VERSION = 1;
9
9
  const HASH = /^sha256:[0-9a-f]{64}$/u;
10
10
 
11
11
  function commandRecords(stack) {
12
- return stack.commands.map(({ label, argv }) => ({ label, argv }));
12
+ return stack.verificationCommands.map(({ label, argv }) => ({ label, argv }));
13
13
  }
14
14
 
15
15
  async function codebaseHash(projectRoot) {
@@ -52,7 +52,7 @@ async function readEvidence(projectRoot) {
52
52
  }
53
53
 
54
54
  export async function inspectVerification({ projectRoot, stack }) {
55
- if (stack.commands.length === 0) return { status: 'unconfigured', evidence: null };
55
+ if (stack.verificationCommands.length === 0) return { status: 'unconfigured', evidence: null };
56
56
  const saved = await readEvidence(projectRoot);
57
57
  if (saved.status !== 'recorded') return saved;
58
58
  const expected = {
@@ -390,7 +390,7 @@ export async function generateProjectPrompt({
390
390
  resourceInputs: missing.length > 0
391
391
  ? { status: 'missing', diagnostics: missing }
392
392
  : { status: 'present' },
393
- verificationCommands: stack.commands.map(({ label, argv }) => ({ label, argv })),
393
+ verificationCommands: stack.verificationCommands.map(({ label, argv }) => ({ label, argv })),
394
394
  agentSkills: { status: projectSkills.status },
395
395
  codeIndex: codeIndexContext(index),
396
396
  };
@@ -152,8 +152,7 @@ export async function listStackCatalogPieces(options = {}) {
152
152
  cityRegions: piece.cityRegions,
153
153
  indexers: piece.indexers,
154
154
  environmentDefaults: piece.environmentDefaults,
155
- launchTargets: piece.launchTargets.map(({ id }) => id),
156
- workspaceSetup: piece.workspaceSetupSteps,
155
+ extensions: piece.extensions.map(({ name }) => name),
157
156
  skill: piece.skill === null ? null : piece.skill.path,
158
157
  stackPackage: piece.stackPackage,
159
158
  resources: piece.resources.map(({ id }) => id),
@@ -1,16 +1,29 @@
1
1
  import { GenesisError } from './errors.js';
2
- import { parseStackCommandLines } from './stack-command.js';
3
2
  import { parseStackCityPresentationLines } from './stack-city-presentation.js';
4
- import { parseStackDeploymentLines } from './stack-deployment.js';
5
3
  import { parseStackEnvironmentDefaultLines } from './stack-environment-defaults.js';
6
4
  import { parseStackEnvironmentFileLines } from './stack-environment-files.js';
7
- import { parseStackLaunchLines } from './stack-launch.js';
8
- import { parseStackWorkspaceSetupLines } from './stack-workspace-setup.js';
5
+ import { opaqueStackSections } from './stack-section.js';
6
+ import { parseStackVerificationLines } from './stack-verification.js';
9
7
  import { normalizeSource } from './utils.js';
10
8
 
11
9
  const STACK_PIECE_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
12
10
  const STACK_CUSTOMIZATION_TITLE = /^# Stack customization: ([a-z0-9]+(?:-[a-z0-9]+)*)\s*$/u;
13
11
  const STACK_CUSTOMIZATION_FIELDS = new Set(['Description', 'Guidance', 'Adoption', 'Deslop']);
12
+ const STACK_PIECE_SECTIONS = new Set([
13
+ 'Adoption',
14
+ 'City regions',
15
+ 'Conflicts',
16
+ 'Description',
17
+ 'Deslop',
18
+ 'Environment defaults',
19
+ 'Environment files',
20
+ 'Guidance',
21
+ 'Indexers',
22
+ 'Requires',
23
+ 'Resources',
24
+ 'Skill',
25
+ 'Verification',
26
+ ]);
14
27
 
15
28
  export function normalizeStackPieceId(value, {
16
29
  code = 'STACK_PIECE_INVALID',
@@ -214,7 +227,8 @@ function resources(all, piecePath) {
214
227
 
215
228
  /**
216
229
  * Stack pieces are guidance, not a second programming language. Genesis reads
217
- * only the few sections it uses and deliberately ignores explanatory sections.
230
+ * only its own small grammar and preserves every other section as opaque data
231
+ * for the consumer that named it.
218
232
  */
219
233
  export function parseStackPieceSource(value, {
220
234
  expectedId,
@@ -231,7 +245,10 @@ export function parseStackPieceSource(value, {
231
245
  const adoption = textSection(all, 'Adoption');
232
246
  const deslop = textSection(all, 'Deslop');
233
247
  if (!description) invalid(piecePath, 'Stack piece needs Description.');
234
- const commands = parseStackCommandLines(all.get('Commands') || [], { path: piecePath });
248
+ const verificationCommands = parseStackVerificationLines(
249
+ all.get('Verification') || [],
250
+ { path: piecePath },
251
+ );
235
252
  const cityPresentation = parseStackCityPresentationLines(
236
253
  all.has('City regions') ? all.get('City regions') : undefined,
237
254
  { path: piecePath },
@@ -244,17 +261,6 @@ export function parseStackPieceSource(value, {
244
261
  all.has('Environment files') ? all.get('Environment files') : undefined,
245
262
  { path: piecePath },
246
263
  );
247
- const launch = parseStackLaunchLines(all.has('Launch') ? all.get('Launch') : undefined, {
248
- path: piecePath,
249
- });
250
- const deployment = parseStackDeploymentLines(
251
- all.has('Deployment') ? all.get('Deployment') : undefined,
252
- { path: piecePath },
253
- );
254
- const workspaceSetup = parseStackWorkspaceSetupLines(
255
- all.has('Workspace setup') ? all.get('Workspace setup') : undefined,
256
- { path: piecePath },
257
- );
258
264
  return {
259
265
  id,
260
266
  description,
@@ -265,14 +271,12 @@ export function parseStackPieceSource(value, {
265
271
  indexers: idList(all, 'Indexers', piecePath),
266
272
  skill: skill(all, piecePath),
267
273
  resources: resources(all, piecePath),
268
- commands: commands.map(({ line: _line, ...command }) => command),
274
+ verificationCommands: verificationCommands.map(({ line: _line, ...command }) => command),
269
275
  cityExclusions: cityPresentation.exclusions,
270
276
  cityRegions: cityPresentation.regions,
271
277
  environmentDefaults,
272
278
  environmentFiles: environmentFiles || [],
273
- launchTargets: launch?.targets || [],
274
- deployment,
275
- workspaceSetupSteps: workspaceSetup || [],
279
+ extensions: opaqueStackSections(all, STACK_PIECE_SECTIONS),
276
280
  deslop,
277
281
  };
278
282
  }
@@ -0,0 +1,33 @@
1
+ import { GENESIS_CONTRACTS } from './contracts.js';
2
+ import { gitContext } from './git.js';
3
+ import { readStack } from './stack.js';
4
+ import { normalizeStackSectionName } from './stack-section.js';
5
+ import { sha256, stableJson } from './utils.js';
6
+
7
+ /** Inspect one consumer-owned Stack section without interpreting its contents. */
8
+ export async function inspectProjectStackSection({
9
+ name,
10
+ projectRoot,
11
+ stackPackages = [],
12
+ } = {}) {
13
+ const sectionName = normalizeStackSectionName(name);
14
+ const root = (await gitContext(projectRoot)).repositoryRoot;
15
+ const stack = await readStack(root, { stackPackages });
16
+ const section = stack.extensions.find((candidate) => candidate.name === sectionName);
17
+ const diagnostics = [...(section?.diagnostics || [])];
18
+ const status = diagnostics.length > 0 ? 'blocked' : section ? 'ready' : 'unconfigured';
19
+ const lines = section ? [...section.lines] : [];
20
+ return {
21
+ contract: GENESIS_CONTRACTS.stackSection,
22
+ status,
23
+ stackHash: stack.identityHash,
24
+ sectionHash: status === 'ready' ? sha256(stableJson({ name: sectionName, lines })) : '',
25
+ components: stack.components.map(({ id }) => id),
26
+ name: sectionName,
27
+ source: section?.source || null,
28
+ content: lines.join('\n'),
29
+ lines,
30
+ diagnostics,
31
+ };
32
+ }
33
+
@@ -0,0 +1,82 @@
1
+ import { GenesisError } from './errors.js';
2
+
3
+ export function trimStackSectionLines(lines = []) {
4
+ const result = [...lines];
5
+ while (result[0]?.trim() === '') result.shift();
6
+ while (result.at(-1)?.trim() === '') result.pop();
7
+ return result;
8
+ }
9
+
10
+ export function normalizeStackSectionName(value, {
11
+ code = 'STACK_SECTION_INVALID',
12
+ field = 'section',
13
+ } = {}) {
14
+ const name = String(value ?? '').trim();
15
+ if (!name || /[\0\r\n]/u.test(name) || /^#/u.test(name)) {
16
+ throw new GenesisError(code, 'Stack section names must be non-empty single lines.', {
17
+ [field]: value,
18
+ });
19
+ }
20
+ return name;
21
+ }
22
+
23
+ export function opaqueStackSections(sections, knownNames) {
24
+ return [...sections.entries()]
25
+ .filter(([name]) => !knownNames.has(name))
26
+ .map(([name, lines]) => ({
27
+ name: normalizeStackSectionName(name),
28
+ lines: trimStackSectionLines(lines),
29
+ }));
30
+ }
31
+
32
+ /**
33
+ * Compose consumer-owned Stack sections without interpreting their contents.
34
+ * A project declaration replaces component defaults; otherwise exactly one
35
+ * selected component may provide a section with a given name.
36
+ */
37
+ export function composeStackSections(components = [], projectSections = []) {
38
+ const projectByName = new Map(projectSections.map((section) => [section.name, section]));
39
+ const componentByName = new Map();
40
+ for (const component of components) {
41
+ for (const section of component.extensions || []) {
42
+ const declarations = componentByName.get(section.name) || [];
43
+ declarations.push({
44
+ ...section,
45
+ source: `component:${component.id}`,
46
+ });
47
+ componentByName.set(section.name, declarations);
48
+ }
49
+ }
50
+ const names = [...new Set([...projectByName.keys(), ...componentByName.keys()])].sort();
51
+ return names.map((name) => {
52
+ const project = projectByName.get(name);
53
+ if (project) {
54
+ return {
55
+ name,
56
+ lines: [...project.lines],
57
+ source: 'project',
58
+ diagnostics: [],
59
+ };
60
+ }
61
+ const declarations = componentByName.get(name) || [];
62
+ if (declarations.length === 1) {
63
+ return {
64
+ name,
65
+ lines: [...declarations[0].lines],
66
+ source: declarations[0].source,
67
+ diagnostics: [],
68
+ };
69
+ }
70
+ const sources = declarations.map(({ source }) => source).sort();
71
+ return {
72
+ name,
73
+ lines: [],
74
+ source: null,
75
+ diagnostics: [{
76
+ code: 'STACK_SECTION_AMBIGUOUS',
77
+ message: `Selected Stack components provide competing \`${name}\` sections: ${sources.join(', ')}. Add one project \`## ${name}\` section to select the exact declaration.`,
78
+ details: { name, sources },
79
+ }],
80
+ };
81
+ });
82
+ }