genesis-compiler 1.2.3 → 1.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/prompts/work.txt CHANGED
@@ -10,7 +10,7 @@ and reuse an existing function when it already owns the behavior.
10
10
  This is the implementation turn. Do not edit the Blueprint, Program, or
11
11
  `.genesis/`; the separate reconciliation turn owns explanatory changes. Keep an
12
12
  exact Stack `## Launch` declaration aligned when the implementation establishes
13
- or intentionally changes how the project is started or how Vibe64 may request
13
+ or intentionally changes how the project is started or how a preview host may request
14
14
  an application preview identity. Declare only command paths, protocol, supported
15
15
  identity types, environment variable names, runtimes, and timeout policy; never
16
16
  put an environment value or secret in Stack. Never invent missing external-resource
@@ -21,6 +21,14 @@ codebase, tests, Git review, or the coding agent.
21
21
  6. Load applicable technology skills from `.agents/skills/` or from the
22
22
  agent's own installed skill catalog.
23
23
 
24
+ For an existing application's first Stack selection, inspect its real setup,
25
+ build, and launch commands before relying on component defaults. A selected
26
+ component describes its current foundation; it does not silently port older
27
+ source. When the existing commands differ, keep the implementation unchanged
28
+ and declare exact project-owned `## Workspace setup` and `## Launch` overrides.
29
+ Do not claim the inherited component recipe is usable until it matches the
30
+ source.
31
+
24
32
  Program is concise, fallible explanation. Its Sources and optional
25
33
  Implementation maps aid navigation but never substitute for reading code,
26
34
  tests, and runtime behavior.
@@ -36,7 +44,7 @@ Neither is authority or proof; both may be regenerated with `genesis index`.
36
44
  - Do not edit the Blueprint, Program, or `.genesis/` during implementation; the
37
45
  separate reconciliation turn owns explanatory updates. Keep an exact Stack
38
46
  `## Launch` declaration aligned when the implementation establishes or
39
- intentionally changes how the project is started or how Vibe64 may request
47
+ intentionally changes how the project is started or how a preview host may request
40
48
  an application preview identity. Record only capability metadata and
41
49
  environment variable names there, never environment values or secrets.
42
50
  - Follow established project and technology seams instead of creating parallel
package/src/cli.js CHANGED
@@ -9,6 +9,10 @@ import {
9
9
  getContext,
10
10
  indexCodebase,
11
11
  initialize,
12
+ inspectDeployment,
13
+ inspectEnvironment,
14
+ inspectLaunch,
15
+ inspectWorkspaceSetup,
12
16
  installCodex,
13
17
  listStackPieces,
14
18
  prepareWorkspace,
@@ -31,9 +35,10 @@ const USAGE = `Usage:
31
35
  genesis stack add <piece...>
32
36
  genesis context <path...>
33
37
  genesis index [function-or-path...]
38
+ genesis inspect <workspace|environment|launch|deployment>
34
39
  genesis prepare
35
40
  genesis prompt [request...]
36
- genesis prompt --task <start|work|deslop|program|blueprint|describe|review> [request...]
41
+ genesis prompt --task <start|adopt|work|deslop|program|blueprint|describe|review> [request...]
37
42
  genesis verify
38
43
  genesis check
39
44
 
@@ -48,7 +53,7 @@ prompt to the agent you already use. Review all edits through the ordinary Git
48
53
  diff, then run genesis verify for the Stack's concrete checks.
49
54
  `;
50
55
 
51
- const COMMANDS = new Set(['adopt', 'check', 'codex', 'context', 'hook', 'index', 'init', 'prepare', 'prompt', 'stack', 'verify']);
56
+ const COMMANDS = new Set(['adopt', 'check', 'codex', 'context', 'hook', 'index', 'init', 'inspect', 'prepare', 'prompt', 'stack', 'verify']);
52
57
 
53
58
  function parseCommand(argv) {
54
59
  if (argv.length === 0 || argv.includes('--help') || argv.includes('-h') || argv[0] === 'help') {
@@ -96,9 +101,16 @@ function parseCommand(argv) {
96
101
  }
97
102
  } else if (command === 'context' && operands.length === 0) {
98
103
  fail('CONTEXT_PATH_REQUIRED', 'Command context requires at least one project path.');
104
+ } else if (command === 'inspect') {
105
+ if (operands.length !== 1 || !['workspace', 'environment', 'launch', 'deployment'].includes(operands[0])) {
106
+ fail(
107
+ 'CLI_INSPECT_TARGET_REQUIRED',
108
+ 'Command inspect requires exactly one of: workspace, environment, launch, deployment.',
109
+ );
110
+ }
99
111
  } else if (command === 'hook' && (operands.length !== 1 || !['begin', 'discover', 'end', 'session', 'stop'].includes(operands[0]))) {
100
112
  fail('CLI_HOOK_ACTION_REQUIRED', 'Command hook requires exactly one of: discover, session, begin, stop, end.');
101
- } else if (!['adopt', 'context', 'hook', 'index', 'prompt'].includes(command) && operands.length > 0) {
113
+ } else if (!['adopt', 'context', 'hook', 'index', 'inspect', 'prompt'].includes(command) && operands.length > 0) {
102
114
  fail('CLI_EXTRA_ARGUMENT', `Command ${command} accepts no arguments.`);
103
115
  }
104
116
  return { command, operands, options };
@@ -128,6 +140,31 @@ function writeCheck(result) {
128
140
  line(process.stdout, `Check: ${result.status}`);
129
141
  }
130
142
 
143
+ function writeInspection(result) {
144
+ const title = result.inspection[0].toUpperCase() + result.inspection.slice(1);
145
+ line(process.stdout, `${title}: ${result.status}`);
146
+ namedItems('Components', result.components);
147
+ namedItems('Runtimes', result.runtimeRequirements);
148
+ if (result.inspection === 'workspace') {
149
+ namedItems('Preparation steps', result.steps.map(({ label, argv, workdir }) => (
150
+ `${label} in ${workdir}: ${argv.join(' ')}`
151
+ )));
152
+ } else if (result.inspection === 'environment') {
153
+ namedItems('Public defaults', result.environmentDefaults.map(({ name, value }) => `${name}=${value}`));
154
+ namedItems('Environment files', result.files.map(({ format, path: filePath }) => `${format}: ${filePath}`));
155
+ } else if (result.inspection === 'launch') {
156
+ namedItems('Targets', result.targets.map(({ id, label, urlPath, steps }) => (
157
+ `${id} (${label}) ${urlPath}: ${steps.map(({ argv }) => argv.join(' ')).join(' -> ')}`
158
+ )));
159
+ } else {
160
+ namedItems('Deployment steps', result.steps.map(({ label, argv }) => `${label}: ${argv.join(' ')}`));
161
+ }
162
+ namedItems('Resources', result.resources.map(({ component, resource }) => (
163
+ `${resource.id} (${resource.kind}, ${component})`
164
+ )));
165
+ for (const item of result.diagnostics || []) line(process.stdout, `${item.code}: ${item.message}`);
166
+ }
167
+
131
168
  function writeResult(command, result) {
132
169
  if (command === 'prompt') {
133
170
  process.stdout.write(result.prompt.endsWith('\n') ? result.prompt : `${result.prompt}\n`);
@@ -167,6 +204,10 @@ function writeResult(command, result) {
167
204
  line(process.stdout, `index: ${result.status}`);
168
205
  return;
169
206
  }
207
+ if (command === 'inspect') {
208
+ writeInspection(result);
209
+ return;
210
+ }
170
211
  if (command === 'prepare') {
171
212
  if (result.summary) line(process.stdout, result.summary);
172
213
  namedItems('Commands', result.commands?.map(({ label, argv }) => `${label}: ${argv.join(' ')}`));
@@ -241,6 +282,15 @@ async function execute({ command, operands, options }) {
241
282
  }
242
283
  if (command === 'context') return getContext({ paths: operands, projectRoot });
243
284
  if (command === 'index') return indexCodebase({ projectRoot, queries: operands });
285
+ if (command === 'inspect') {
286
+ const inspections = {
287
+ workspace: inspectWorkspaceSetup,
288
+ environment: inspectEnvironment,
289
+ launch: inspectLaunch,
290
+ deployment: inspectDeployment,
291
+ };
292
+ return { inspection: operands[0], ...await inspections[operands[0]]({ projectRoot }) };
293
+ }
244
294
  if (command === 'prepare') {
245
295
  return prepareWorkspace({
246
296
  projectRoot,
@@ -3,6 +3,7 @@ import { readFile } from 'node:fs/promises';
3
3
  const root = new URL('../../', import.meta.url);
4
4
  const assets = {
5
5
  start: new URL('prompts/start.txt', root),
6
+ adopt: new URL('prompts/adopt.txt', root),
6
7
  work: new URL('prompts/work.txt', root),
7
8
  describe: new URL('prompts/describe.txt', root),
8
9
  reconcile: new URL('prompts/reconcile.txt', root),
@@ -156,8 +156,9 @@ export async function codexAdoptionRecommendation({ projectRoot = process.cwd()
156
156
  output: [
157
157
  'IMPORTANT GENESIS RECOMMENDATION:',
158
158
  'This existing project has no Genesis explanatory layer.',
159
- 'In your first response, strongly recommend that the user run `genesis adopt` before substantial work.',
160
- '`genesis adopt` preserves the implementation and returns the prompt for creating its initial Blueprint and Program.',
159
+ 'In your first response, strongly recommend preparing this existing project for guided editing before substantial work.',
160
+ 'Explain in product language that preparation preserves the implementation while importing its current operational and product context. Do not require the user to know Genesis terminology or type a command.',
161
+ 'If the user approves, run `genesis adopt` yourself and follow the returned adoption prompt.',
161
162
  'Do not run it without the user\'s approval. If they agree, run it and follow the returned prompt in this conversation.',
162
163
  'If the user declines, continue normally and do not repeat the recommendation in this session.',
163
164
  ].join('\n'),
@@ -14,9 +14,10 @@ import { stableJson } from './utils.js';
14
14
  import { gitVisibleFileStates } from './project-files.js';
15
15
  import { isProjectContentPath } from './paths.js';
16
16
 
17
- const TASKS = new Set(['start', 'work', 'deslop', 'program', 'blueprint', 'describe', 'review']);
17
+ const TASKS = new Set(['start', 'adopt', 'work', 'deslop', 'program', 'blueprint', 'describe', 'review']);
18
18
  const DEFAULT_REQUEST = {
19
19
  start: 'Start a conversation about this project.',
20
+ adopt: 'Import the existing project into a truthful Genesis project contract.',
20
21
  work: 'Implement the product intent expressed by the current Blueprint.',
21
22
  deslop: 'Simplify the current Git-visible work without making unrelated changes.',
22
23
  program: 'Refresh the complete useful Program for the code that exists now.',
@@ -97,6 +98,7 @@ function renderPrompt({
97
98
  skills = '',
98
99
  guidance = '',
99
100
  cleanup = '',
101
+ adoption = '',
100
102
  }) {
101
103
  return [
102
104
  instructions.trim(),
@@ -111,12 +113,58 @@ function renderPrompt({
111
113
  stableJson(context).trimEnd(),
112
114
  '```',
113
115
  ...(guidance ? ['', 'SELECTED STACK GUIDANCE', '', guidance] : []),
116
+ ...(adoption ? ['', 'SELECTED STACK ADOPTION GUIDANCE', '', adoption] : []),
114
117
  ...(skills ? ['', 'AVAILABLE AGENT SKILLS', '', skills] : []),
115
118
  ...(cleanup ? ['', 'SELECTED STACK CLEANUP GUIDANCE', '', cleanup] : []),
116
119
  '',
117
120
  ].join('\n');
118
121
  }
119
122
 
123
+ async function generateAdoptionPrompt({ environment, instructions, program, request, root }) {
124
+ const blueprint = await readBlueprint(root, { required: true });
125
+ const [stack, index, catalog] = await Promise.all([
126
+ readStack(root),
127
+ buildProjectIndex({ projectRoot: root, write: false }),
128
+ listBuiltinStackPieces(),
129
+ ]);
130
+ const projectSkills = await inspectProjectSkills({ projectRoot: root, stack });
131
+ const missing = missingStackResources({
132
+ environment: withStackEnvironmentDefaults(environment, stack.environmentDefaults),
133
+ resources: stack.resources,
134
+ });
135
+ return {
136
+ status: 'ready',
137
+ task: 'adopt',
138
+ prompt: renderPrompt({
139
+ instructions,
140
+ request,
141
+ context: {
142
+ task: 'adopt',
143
+ projectRoot: root,
144
+ projectKind: 'existing',
145
+ blueprint: { path: blueprint.path, source: blueprint.source },
146
+ stack: stackPromptContext(stack),
147
+ availableStackPieces: stackCatalogContext(catalog),
148
+ program: programContext(program),
149
+ resourceInputs: missing.length > 0
150
+ ? { status: 'missing', diagnostics: missing }
151
+ : { status: 'present' },
152
+ codeIndex: codeIndexContext(index),
153
+ },
154
+ guidance: stack.guidance,
155
+ adoption: stack.adoption,
156
+ skills: renderAgentSkillCatalog(projectSkills.skills),
157
+ }),
158
+ warnings: [
159
+ ...(program.diagnostic ? [program.diagnostic] : []),
160
+ ...missing,
161
+ ...projectSkills.diagnostics,
162
+ ...index.diagnostics,
163
+ ],
164
+ verificationCommands: [],
165
+ };
166
+ }
167
+
120
168
  async function generateExplanationPrompt({ instructions, program, request, root, task }) {
121
169
  const blueprint = await readBlueprint(root, {
122
170
  required: true,
@@ -268,6 +316,15 @@ export async function generateProjectPrompt({
268
316
  root,
269
317
  });
270
318
  }
319
+ if (task === 'adopt') {
320
+ return generateAdoptionPrompt({
321
+ environment,
322
+ instructions,
323
+ program,
324
+ request: userRequest,
325
+ root,
326
+ });
327
+ }
271
328
  let stack;
272
329
  if (task === 'work') {
273
330
  try {
@@ -61,6 +61,7 @@ export async function listBuiltinStackPieces() {
61
61
  id: piece.id,
62
62
  description: piece.description,
63
63
  guidance: piece.guidance,
64
+ adoption: piece.adoption,
64
65
  requires: piece.requires,
65
66
  conflicts: piece.conflicts,
66
67
  cityExclusions: piece.cityExclusions,
@@ -14,6 +14,8 @@ export function parseStackEnvironmentDefaultLines(lines, {
14
14
  path: defaultsPath = 'stack environment defaults',
15
15
  } = {}) {
16
16
  if (lines === undefined) return [];
17
+ const present = lines.map((line) => line.trim()).filter(Boolean);
18
+ if (present.length === 1 && present[0] === '- Nothing.') return [];
17
19
  const defaults = [];
18
20
  for (let index = 0; index < lines.length; index += 1) {
19
21
  const source = lines[index].trim();
@@ -7,9 +7,8 @@ import {
7
7
 
8
8
  const ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
9
9
  const PLACEHOLDER = /\{([a-z][a-z0-9-]*)\}/gu;
10
- const PREVIEW_IDENTITY_COMMAND_PATH = /^\.vibe64\/bin\/[A-Za-z0-9][A-Za-z0-9._-]*$/u;
11
10
  const PREVIEW_IDENTITY_ENVIRONMENT_NAME = /^[A-Z_][A-Z0-9_]*$/u;
12
- const PREVIEW_IDENTITY_PROTOCOL = 'vibe64.preview-identity.command.v1';
11
+ const PREVIEW_IDENTITY_PROTOCOL = 'genesis.preview-identity.command.v1';
13
12
  const PREVIEW_IDENTITY_RESERVED_ENVIRONMENT_NAMES = new Set([
14
13
  'HOME',
15
14
  'LOGNAME',
@@ -17,8 +16,6 @@ const PREVIEW_IDENTITY_RESERVED_ENVIRONMENT_NAMES = new Set([
17
16
  'PATH',
18
17
  'TMPDIR',
19
18
  'USER',
20
- 'VIBE64_PREVIEW_IDENTITY_ENABLED',
21
- 'VIBE64_PREVIEW_IDENTITY_SECRET',
22
19
  ]);
23
20
  const PREVIEW_IDENTITY_TYPES = new Set(['email', 'login', 'user-id']);
24
21
  const PREVIEW_FIELDS = [
@@ -291,12 +288,14 @@ function normalizePreviewIdentity(draft, launchPath, targetId) {
291
288
  if (
292
289
  command.length > 64
293
290
  || command.some((entry) => !entry.trim() || entry.length > 4096 || /[\r\n]/u.test(entry))
294
- || !PREVIEW_IDENTITY_COMMAND_PATH.test(command[0])
291
+ || !command[0].includes('/')
292
+ || command[0].includes('\\')
293
+ || command[0].split('/').some((part) => !part || ['.', '..'].includes(part))
295
294
  || command.some((entry) => /\{(?:host|port)\}/u.test(entry))
296
295
  ) {
297
296
  invalid(
298
297
  launchPath,
299
- 'Preview identity command must use an app-owned executable under .vibe64/bin and contain at most 64 literal arguments.',
298
+ 'Preview identity command must use a committed app-owned project-relative executable and contain at most 64 literal arguments.',
300
299
  draft.line,
301
300
  );
302
301
  }
@@ -10,7 +10,7 @@ import { normalizeSource } from './utils.js';
10
10
 
11
11
  const STACK_PIECE_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
12
12
  const STACK_CUSTOMIZATION_TITLE = /^# Stack customization: ([a-z0-9]+(?:-[a-z0-9]+)*)\s*$/u;
13
- const STACK_CUSTOMIZATION_FIELDS = new Set(['Description', 'Guidance', 'Deslop']);
13
+ const STACK_CUSTOMIZATION_FIELDS = new Set(['Description', 'Guidance', 'Adoption', 'Deslop']);
14
14
 
15
15
  export function normalizeStackPieceId(value, {
16
16
  code = 'STACK_PIECE_INVALID',
@@ -93,12 +93,12 @@ function customizationSections(source, piecePath) {
93
93
  if (/^#{1,3}\s+/u.test(line)) invalid(piecePath, `Unknown customization heading: ${line.trim()}.`);
94
94
  if (field) content.push(line);
95
95
  else if (line.trim()) {
96
- invalid(piecePath, 'Customization content must be below Description, Guidance, or Deslop.');
96
+ invalid(piecePath, 'Customization content must be below Description, Guidance, Adoption, or Deslop.');
97
97
  }
98
98
  }
99
99
  finishField();
100
100
  if (Object.values(result).every((values) => Object.keys(values).length === 0)) {
101
- invalid(piecePath, 'Stack customization must add or override Description, Guidance, or Deslop.');
101
+ invalid(piecePath, 'Stack customization must add or override Description, Guidance, Adoption, or Deslop.');
102
102
  }
103
103
  return result;
104
104
  }
@@ -151,18 +151,22 @@ function skill(all, piecePath) {
151
151
  };
152
152
  }
153
153
 
154
- function resources(all, piecePath) {
155
- const source = textSection(all, 'Resources');
154
+ export function parseStackResourceLines(lines, {
155
+ path: resourcePath = 'stack resources',
156
+ } = {}) {
157
+ if (lines === undefined) return [];
158
+ const source = lines.join('\n').trim();
156
159
  if (!source) return [];
160
+ if (source === '- Nothing.') return [];
157
161
  const blocks = [...source.matchAll(/```json genesis-resource\s*\n([\s\S]*?)\n```/gu)];
158
162
  const outside = source.replace(/```json genesis-resource\s*\n[\s\S]*?\n```/gu, '').trim();
159
163
  if (outside || blocks.length === 0) {
160
- invalid(piecePath, '## Resources may contain only `json genesis-resource` fenced objects.');
164
+ invalid(resourcePath, '## Resources may contain only `json genesis-resource` fenced objects.');
161
165
  }
162
166
  const parsed = blocks.map((match) => {
163
167
  let resource;
164
168
  try { resource = JSON.parse(match[1]); } catch (error) {
165
- invalid(piecePath, `A Stack resource is invalid JSON: ${error.message}`);
169
+ invalid(resourcePath, `A Stack resource is invalid JSON: ${error.message}`);
166
170
  }
167
171
  if (
168
172
  !resource
@@ -173,7 +177,7 @@ function resources(all, piecePath) {
173
177
  || !Array.isArray(resource.environmentAlternatives)
174
178
  || resource.environmentAlternatives.length === 0
175
179
  ) {
176
- invalid(piecePath, 'A Stack resource needs an id, kind, and at least one environment alternative.');
180
+ invalid(resourcePath, 'A Stack resource needs an id, kind, and at least one environment alternative.');
177
181
  }
178
182
  const environmentAlternatives = resource.environmentAlternatives.map((alternative) => {
179
183
  const required = alternative?.required;
@@ -182,7 +186,7 @@ function resources(all, piecePath) {
182
186
  !Array.isArray(required)
183
187
  || !Array.isArray(allowEmpty)
184
188
  ) {
185
- invalid(piecePath, `Stack resource ${resource.id} has an invalid environment alternative.`);
189
+ invalid(resourcePath, `Stack resource ${resource.id} has an invalid environment alternative.`);
186
190
  }
187
191
  const names = [...required, ...allowEmpty];
188
192
  if (
@@ -191,17 +195,23 @@ function resources(all, piecePath) {
191
195
  || allowEmpty.some((name) => !required.includes(name))
192
196
  || new Set(required).size !== required.length
193
197
  || new Set(allowEmpty).size !== allowEmpty.length
194
- ) invalid(piecePath, `Stack resource ${resource.id} has an invalid environment alternative.`);
198
+ ) invalid(resourcePath, `Stack resource ${resource.id} has an invalid environment alternative.`);
195
199
  return { required: [...required], allowEmpty: [...allowEmpty] };
196
200
  });
197
201
  return { id: resource.id, kind: resource.kind, environmentAlternatives };
198
202
  });
199
203
  if (new Set(parsed.map(({ id }) => id)).size !== parsed.length) {
200
- invalid(piecePath, '## Resources contains a duplicate resource id.');
204
+ invalid(resourcePath, '## Resources contains a duplicate resource id.');
201
205
  }
202
206
  return parsed;
203
207
  }
204
208
 
209
+ function resources(all, piecePath) {
210
+ return parseStackResourceLines(all.has('Resources') ? all.get('Resources') : undefined, {
211
+ path: piecePath,
212
+ });
213
+ }
214
+
205
215
  /**
206
216
  * Stack pieces are guidance, not a second programming language. Genesis reads
207
217
  * only the few sections it uses and deliberately ignores explanatory sections.
@@ -218,6 +228,7 @@ export function parseStackPieceSource(value, {
218
228
  const all = sections(source);
219
229
  const description = textSection(all, 'Description');
220
230
  const guidance = textSection(all, 'Guidance');
231
+ const adoption = textSection(all, 'Adoption');
221
232
  const deslop = textSection(all, 'Deslop');
222
233
  if (!description) invalid(piecePath, 'Stack piece needs Description.');
223
234
  const commands = parseStackCommandLines(all.get('Commands') || [], { path: piecePath });
@@ -248,6 +259,7 @@ export function parseStackPieceSource(value, {
248
259
  id,
249
260
  description,
250
261
  guidance,
262
+ adoption,
251
263
  requires: idList(all, 'Requires', piecePath, { required: true }),
252
264
  conflicts: idList(all, 'Conflicts', piecePath),
253
265
  indexers: idList(all, 'Indexers', piecePath),
@@ -299,6 +311,11 @@ export function applyStackPieceCustomization(piece, customization) {
299
311
  customization.override.guidance,
300
312
  customization.add.guidance,
301
313
  ),
314
+ adoption: customizedText(
315
+ piece.adoption,
316
+ customization.override.adoption,
317
+ customization.add.adoption,
318
+ ),
302
319
  deslop: customizedText(piece.deslop, customization.override.deslop, customization.add.deslop),
303
320
  };
304
321
  }
@@ -10,6 +10,7 @@ import { composeStackDeployment, parseStackDeploymentLines } from './stack-deplo
10
10
  import { resolveStackPieces } from './stack-composition.js';
11
11
  import {
12
12
  composeStackEnvironmentDefaults,
13
+ parseStackEnvironmentDefaultLines,
13
14
  } from './stack-environment-defaults.js';
14
15
  import {
15
16
  composeStackEnvironmentFiles,
@@ -24,6 +25,7 @@ import {
24
25
  applyStackPieceCustomization,
25
26
  normalizeStackPieceId,
26
27
  parseStackPieceCustomizationSource,
28
+ parseStackResourceLines,
27
29
  } from './stack-piece.js';
28
30
  import { STACK_PATH } from './paths.js';
29
31
  import { sha256, stableJson, writeFileAtomic } from './utils.js';
@@ -32,6 +34,8 @@ const COMPONENT_LINE = /^- `([a-z0-9]+(?:-[a-z0-9]+)*)`$/u;
32
34
  export const EMPTY_STACK_SOURCE = '# Stack\n\n## Components\n';
33
35
  const STACK_SECTIONS = new Set([
34
36
  'Components',
37
+ 'Resources',
38
+ 'Environment defaults',
35
39
  'Environment files',
36
40
  'Workspace setup',
37
41
  'Commands',
@@ -104,8 +108,10 @@ function renderStack({
104
108
  commandLines = [],
105
109
  componentIds: componentIdsValue,
106
110
  deploymentLines = null,
111
+ environmentDefaultLines = null,
107
112
  environmentFileLines = null,
108
113
  launchLines = null,
114
+ resourceLines = null,
109
115
  workspaceSetupLines = null,
110
116
  }) {
111
117
  return [
@@ -113,6 +119,12 @@ function renderStack({
113
119
  '',
114
120
  '## Components',
115
121
  ...componentIdsValue.map((id) => `- \`${id}\``),
122
+ ...(resourceLines === null
123
+ ? []
124
+ : ['', '## Resources', '', ...withoutOuterBlankLines(resourceLines)]),
125
+ ...(environmentDefaultLines === null
126
+ ? []
127
+ : ['', '## Environment defaults', '', ...withoutOuterBlankLines(environmentDefaultLines)]),
116
128
  ...(environmentFileLines === null
117
129
  ? []
118
130
  : ['', '## Environment files', '', ...withoutOuterBlankLines(environmentFileLines)]),
@@ -153,6 +165,15 @@ export async function addStackPieces({ pieces, projectRoot }) {
153
165
  )).map(({ id }) => id);
154
166
  const commandLines = parseStackCommandLines(sections.get('Commands') || [], { path: STACK_PATH })
155
167
  .map(({ label, argv }) => `- Verify \`${label}\`: ${argv.map((value) => `\`${value}\``).join(' ')}`);
168
+ const resourceLines = sections.has('Resources') ? sections.get('Resources') : null;
169
+ parseStackResourceLines(resourceLines === null ? undefined : resourceLines, { path: STACK_PATH });
170
+ const environmentDefaultLines = sections.has('Environment defaults')
171
+ ? sections.get('Environment defaults')
172
+ : null;
173
+ parseStackEnvironmentDefaultLines(
174
+ environmentDefaultLines === null ? undefined : environmentDefaultLines,
175
+ { path: STACK_PATH },
176
+ );
156
177
  const environmentFileLines = sections.has('Environment files')
157
178
  ? sections.get('Environment files')
158
179
  : null;
@@ -178,8 +199,10 @@ export async function addStackPieces({ pieces, projectRoot }) {
178
199
  commandLines,
179
200
  componentIds: selected,
180
201
  deploymentLines,
202
+ environmentDefaultLines,
181
203
  environmentFileLines,
182
204
  launchLines,
205
+ resourceLines,
183
206
  workspaceSetupLines,
184
207
  });
185
208
  const stackChanged = rendered !== source;
@@ -266,8 +289,19 @@ export async function readStack(projectRoot) {
266
289
  const componentCommands = components.flatMap((piece) => piece.commands);
267
290
  const commands = distinctCommands(projectCommands.length > 0 ? projectCommands : componentCommands);
268
291
  const cityPresentation = composeStackCityPresentation(components);
269
- const resources = resourceDeclarations(components);
270
- const environmentDefaults = composeStackEnvironmentDefaults(components);
292
+ const projectResources = sections.has('Resources')
293
+ ? parseStackResourceLines(sections.get('Resources'), { path: STACK_PATH })
294
+ : null;
295
+ const resources = projectResources === null
296
+ ? resourceDeclarations(components)
297
+ : projectResources.map((resource) => ({ component: 'project', resource }));
298
+ const projectEnvironmentDefaults = parseStackEnvironmentDefaultLines(
299
+ sections.has('Environment defaults') ? sections.get('Environment defaults') : undefined,
300
+ { path: STACK_PATH },
301
+ );
302
+ const environmentDefaults = sections.has('Environment defaults')
303
+ ? projectEnvironmentDefaults.map((entry) => ({ ...entry, sources: ['project'] }))
304
+ : composeStackEnvironmentDefaults(components);
271
305
  const projectEnvironmentFiles = parseStackEnvironmentFileLines(
272
306
  sections.has('Environment files') ? sections.get('Environment files') : undefined,
273
307
  { path: STACK_PATH },
@@ -313,6 +347,7 @@ export async function readStack(projectRoot) {
313
347
  workspaceSetup,
314
348
  resources,
315
349
  guidance: composedProse(components, 'guidance'),
350
+ adoption: composedProse(components, 'adoption'),
316
351
  deslop: composedProse(components, 'deslop'),
317
352
  };
318
353
  }
@@ -323,12 +358,14 @@ export function stackPromptContext(stack) {
323
358
  id: piece.id,
324
359
  description: piece.description,
325
360
  ...(piece.guidance ? { guidance: piece.guidance } : {}),
361
+ ...(piece.adoption ? { adoption: piece.adoption } : {}),
326
362
  requires: piece.requires,
327
363
  })),
328
364
  cityExclusions: stack.cityExclusions,
329
365
  cityRegions: stack.cityRegions,
330
366
  verifyCommands: stack.commands.map(({ label, argv }) => ({ label, argv })),
331
367
  environmentDefaults: stack.environmentDefaults,
368
+ resources: stack.resources,
332
369
  environmentFiles: stack.environmentFiles,
333
370
  launchTargets: stack.launchTargets,
334
371
  deployment: stack.deployment,
package/src/index.js CHANGED
@@ -44,7 +44,7 @@ export async function adoptProject({ projectRoot = process.cwd(), request = '' }
44
44
  const description = await generateProjectPrompt({
45
45
  projectRoot,
46
46
  request,
47
- task: 'describe',
47
+ task: 'adopt',
48
48
  });
49
49
  return {
50
50
  status: 'ready',
@@ -54,7 +54,7 @@ export async function adoptProject({ projectRoot = process.cwd(), request = '' }
54
54
  changedFiles: initialized.changedFiles,
55
55
  prompt: description.prompt,
56
56
  warnings: description.warnings,
57
- guidance: 'Follow the generated description prompt now. Genesis does not start another agent.',
57
+ guidance: 'Follow the generated adoption prompt now. Genesis does not start another agent.',
58
58
  };
59
59
  }
60
60
 
@@ -12,6 +12,18 @@ C and C++ translation-unit, header, callable, ownership, build, and test convent
12
12
 
13
13
  - `cpp`
14
14
 
15
+ ## Adoption
16
+
17
+ - Identify the actual build system and presets, compiler and language level,
18
+ package/runtime dependencies, generated-source steps, and supported build
19
+ configurations without substituting a preferred CMake or Meson layout.
20
+ - Record exact configure/build/test commands and the executable argv, workdir,
21
+ host/port handling, HTTP readiness predicate, and signal/shutdown contract for
22
+ every web-service target.
23
+ - Preserve public headers, ABI and platform assumptions. Treat databases,
24
+ queues, TLS material, and external service credentials as explicit Resources,
25
+ not implicit properties of the native binary.
26
+
15
27
  ## Deslop
16
28
 
17
29
  - Preserve public header boundaries, ABI expectations, build configuration,
@@ -35,6 +35,16 @@ JSKIT MySQL runtime, live-schema CRUD generation, and persistence conventions.
35
35
  }
36
36
  ```
37
37
 
38
+ ## Adoption
39
+
40
+ - Confirm that the application's effective MySQL environment alternatives and
41
+ driver match this Resource declaration; add a project Resource contract when
42
+ the existing application legitimately differs.
43
+ - Verify from source that the database preparation path discovers every
44
+ package-owned migration, preserves the existing ledger, orders cross-package
45
+ constraints safely, and invokes an app-owned idempotent seed. Do not execute
46
+ it during adoption.
47
+
38
48
  ## Deslop
39
49
 
40
50
  - Use the installed JSKIT database runtime and generated resource/repository
@@ -35,6 +35,16 @@ JSKIT PostgreSQL runtime, live-schema CRUD generation, and persistence conventio
35
35
  }
36
36
  ```
37
37
 
38
+ ## Adoption
39
+
40
+ - Confirm that the application's effective PostgreSQL environment alternatives
41
+ and driver match this Resource declaration; add a project Resource contract
42
+ when the existing application legitimately differs.
43
+ - Verify from source that the database preparation path discovers every
44
+ package-owned migration, preserves the existing ledger, orders cross-package
45
+ constraints safely, and invokes an app-owned idempotent seed. Do not execute
46
+ it during adoption.
47
+
38
48
  ## Deslop
39
49
 
40
50
  - Use the installed JSKIT database runtime and generated resource/repository
@@ -27,6 +27,29 @@ application-owned composition.
27
27
  user-command failures through JSKIT's shared non-layout-shifting action
28
28
  feedback/snackbar path.
29
29
 
30
+ ## Adoption
31
+
32
+ - Read the installed JSKIT skill's existing-application migration guidance
33
+ completely before editing Stack or assessing readiness.
34
+ - Inventory the coordinated `@jskit-ai/*` package graph, application-owned
35
+ composition, package-owned migrations, explicit repositories/features/named
36
+ actions, and current client resources without running a broad package update.
37
+ - Compare the real package scripts with JSKIT's current Workspace setup,
38
+ Launch, verification, and Deployment declarations. Older source needs complete
39
+ project-owned overrides until a separately approved port supplies the current
40
+ commands.
41
+ - Inspect existing setup and database preparation for both migration discovery
42
+ and an app-owned idempotent seed. Never infer `db:prepare` from the selected
43
+ component when the script or preparation file does not exist.
44
+ - When authenticated Preview exists, preserve the app-owned Preview identity
45
+ command, selectors, protocol, environment names, runtime, and timeout in the
46
+ project's Launch declaration. A host-saved identity name/value is not a
47
+ substitute for this application contract.
48
+ - Treat generator descriptors, receipts, sync commands, legacy service
49
+ locators, and retired tool manifests as migration evidence only. Translate
50
+ live facts into current source/Stack and report remaining port work; do not
51
+ add compatibility readers or claim adoption performed the port.
52
+
30
53
  ## Workspace setup
31
54
 
32
55
  - Prepare `Install dependencies` with `nodejs` when `package.json` exists: `npm` `install`