genesis-compiler 1.2.2 → 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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genesis",
3
- "version": "1.2.2",
3
+ "version": "1.2.4",
4
4
  "description": "Makes Codex aware of optional Genesis adoption for existing projects.",
5
5
  "author": {
6
6
  "name": "Mobily Enterprises"
@@ -0,0 +1,62 @@
1
+ Read and follow `.agents/skills/genesis-project/SKILL.md` and
2
+ `.agents/skills/genesis-program/SKILL.md` completely.
3
+
4
+ This is an existing-project adoption turn. Import the codebase's factual
5
+ product and operational contracts into Genesis without changing application
6
+ behavior. The user has already approved adoption; do not ask them to approve
7
+ Genesis again or require them to understand Genesis terminology.
8
+
9
+ Application source, dependencies, migrations, runtime behavior, and deployment
10
+ files are read-only in this turn. You may edit `genesis/blueprint.md`,
11
+ `genesis/stack.md`, explanatory Markdown below `genesis/program/`, and
12
+ Genesis-managed skills/indexes produced by `genesis stack add` or Genesis
13
+ inspection commands. Do not install dependencies, prepare a database, run
14
+ migrations or seeds, start a server, deploy, or add compatibility code.
15
+
16
+ If Stack has no components, inspect direct evidence such as manifests,
17
+ lockfiles, build files, entrypoints, package scripts, tests, configuration,
18
+ database and migration code, and existing project-tool metadata. Record only
19
+ technologies the implementation already uses with `genesis stack add
20
+ <piece...>`; this is factual import, not a new-technology choice. Ask a concise
21
+ product-language question only when evidence is genuinely ambiguous. Then run
22
+ `genesis prompt --task adopt "Continue the approved existing-project import."`
23
+ and follow the regenerated prompt so every selected Stack piece contributes its
24
+ Adoption requirements.
25
+
26
+ Read every selected technology skill named by the regenerated prompt and its
27
+ relevant existing-application migration reference. Apply all SELECTED STACK
28
+ ADOPTION GUIDANCE. Component defaults describe current foundations and are
29
+ hypotheses until they match this source. They do not upgrade an older project.
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.
45
+
46
+ Translate observed contracts into exact project-owned Stack sections whenever
47
+ the selected components' defaults differ. A project section replaces the
48
+ component declaration for that concern, so it must be complete. Inspect retired
49
+ or tool-specific manifests as evidence and carry every still-valid fact into
50
+ Genesis; never add a permanent legacy reader or shim. Do not delete an old
51
+ manifest until all of its live facts have been accounted for.
52
+
53
+ Create the non-technical Blueprint and useful subsystem-oriented Program from
54
+ current source and tests. Do not cite archive internals or Git object storage as
55
+ implementation source; Program sources must be current Git-visible files.
56
+
57
+ Run `genesis check` only for structural validation. Missing environment values,
58
+ workspace markers, commands, helpers, seeds, or readiness evidence are honest
59
+ import findings, not permission to invent or execute them. Finish with a clear
60
+ inventory of what was imported, what remains unconfigured or blocked, and what
61
+ would require a separately approved implementation port. Never claim the
62
+ project is ready merely because Stack components were selected.
package/prompts/start.txt CHANGED
@@ -23,12 +23,25 @@ When `projectKind` is `existing`, ask what the user wants to understand or
23
23
  change. Use the current Blueprint, Stack, Program, and code index when present.
24
24
  If the existing implementation is not yet explained, recommend the Genesis
25
25
  description/adoption path instead of pretending its intent is known.
26
+ When an existing implementation has no selected Stack yet, inspect its actual
27
+ 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.
26
36
 
27
37
  When `projectKind` is `existing-uninitialized`, ask what the user wants to
28
- understand or change and strongly recommend `genesis adopt` before substantial
29
- work. Explain that adoption preserves the implementation and prepares the
30
- Blueprint and Program prompt. Do not run it without the user's approval. If the
31
- user declines, continue normally and do not repeat the recommendation.
38
+ understand or change and strongly recommend preparing the existing project for
39
+ guided editing before substantial work. Explain in product language that this
40
+ preserves the implementation while importing its current setup, run, database,
41
+ preview, verification, and product context. Do not require the user to know
42
+ Genesis terminology or type a command. If they approve, run `genesis adopt`
43
+ yourself and follow its adoption prompt. If they decline, continue normally and
44
+ do not repeat the recommendation.
32
45
 
33
46
  If the request already contains an unambiguous product description and an
34
47
  explicit Stack choice, you may apply that choice and establish the Blueprint.
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,8 +9,13 @@ import {
9
9
  getContext,
10
10
  indexCodebase,
11
11
  initialize,
12
+ inspectDeployment,
13
+ inspectEnvironment,
14
+ inspectLaunch,
15
+ inspectWorkspaceSetup,
12
16
  installCodex,
13
17
  listStackPieces,
18
+ prepareWorkspace,
14
19
  verify,
15
20
  } from './index.js';
16
21
  import {
@@ -30,8 +35,10 @@ const USAGE = `Usage:
30
35
  genesis stack add <piece...>
31
36
  genesis context <path...>
32
37
  genesis index [function-or-path...]
38
+ genesis inspect <workspace|environment|launch|deployment>
39
+ genesis prepare
33
40
  genesis prompt [request...]
34
- genesis prompt --task <start|work|deslop|program|blueprint|describe|review> [request...]
41
+ genesis prompt --task <start|adopt|work|deslop|program|blueprint|describe|review> [request...]
35
42
  genesis verify
36
43
  genesis check
37
44
 
@@ -46,7 +53,7 @@ prompt to the agent you already use. Review all edits through the ordinary Git
46
53
  diff, then run genesis verify for the Stack's concrete checks.
47
54
  `;
48
55
 
49
- const COMMANDS = new Set(['adopt', 'check', 'codex', 'context', 'hook', 'index', 'init', 'prompt', 'stack', 'verify']);
56
+ const COMMANDS = new Set(['adopt', 'check', 'codex', 'context', 'hook', 'index', 'init', 'inspect', 'prepare', 'prompt', 'stack', 'verify']);
50
57
 
51
58
  function parseCommand(argv) {
52
59
  if (argv.length === 0 || argv.includes('--help') || argv.includes('-h') || argv[0] === 'help') {
@@ -94,9 +101,16 @@ function parseCommand(argv) {
94
101
  }
95
102
  } else if (command === 'context' && operands.length === 0) {
96
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
+ }
97
111
  } else if (command === 'hook' && (operands.length !== 1 || !['begin', 'discover', 'end', 'session', 'stop'].includes(operands[0]))) {
98
112
  fail('CLI_HOOK_ACTION_REQUIRED', 'Command hook requires exactly one of: discover, session, begin, stop, end.');
99
- } 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) {
100
114
  fail('CLI_EXTRA_ARGUMENT', `Command ${command} accepts no arguments.`);
101
115
  }
102
116
  return { command, operands, options };
@@ -126,6 +140,31 @@ function writeCheck(result) {
126
140
  line(process.stdout, `Check: ${result.status}`);
127
141
  }
128
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
+
129
168
  function writeResult(command, result) {
130
169
  if (command === 'prompt') {
131
170
  process.stdout.write(result.prompt.endsWith('\n') ? result.prompt : `${result.prompt}\n`);
@@ -165,6 +204,23 @@ function writeResult(command, result) {
165
204
  line(process.stdout, `index: ${result.status}`);
166
205
  return;
167
206
  }
207
+ if (command === 'inspect') {
208
+ writeInspection(result);
209
+ return;
210
+ }
211
+ if (command === 'prepare') {
212
+ if (result.summary) line(process.stdout, result.summary);
213
+ namedItems('Commands', result.commands?.map(({ label, argv }) => `${label}: ${argv.join(' ')}`));
214
+ for (const item of result.diagnostics || []) {
215
+ line(process.stdout, `${item.code}: ${item.message}`);
216
+ for (const field of ['stdout', 'stderr']) {
217
+ const output = item.details?.[field]?.trim();
218
+ if (output) line(process.stdout, output);
219
+ }
220
+ }
221
+ line(process.stdout, `prepare: ${result.status}`);
222
+ return;
223
+ }
168
224
  if (command === 'hook') {
169
225
  if (['discover', 'session'].includes(result.kind) && result.output) line(process.stdout, result.output);
170
226
  else if (result.kind === 'stop') line(process.stdout, JSON.stringify(result.output));
@@ -226,6 +282,23 @@ async function execute({ command, operands, options }) {
226
282
  }
227
283
  if (command === 'context') return getContext({ paths: operands, projectRoot });
228
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
+ }
294
+ if (command === 'prepare') {
295
+ return prepareWorkspace({
296
+ projectRoot,
297
+ onEvent: (event) => {
298
+ if (!options.json) line(process.stderr, event.message);
299
+ },
300
+ });
301
+ }
229
302
  if (command === 'hook') {
230
303
  if (operands[0] === 'discover') {
231
304
  return { kind: 'discover', ...await codexAdoptionRecommendation({ projectRoot }) };
@@ -1,13 +1,13 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import { createRequire } from 'node:module';
3
3
  import {
4
- cp,
5
4
  lstat,
6
5
  mkdir,
7
6
  readFile,
8
7
  readdir,
9
8
  rename,
10
9
  rm,
10
+ writeFile,
11
11
  } from 'node:fs/promises';
12
12
  import path from 'node:path';
13
13
  import { fileURLToPath } from 'node:url';
@@ -92,7 +92,7 @@ async function skillTree(directory, relative = '') {
92
92
  files.push({
93
93
  path: child,
94
94
  hash: sha256(content),
95
- mode: (info.mode & 0o111) === 0 ? 0o100644 : 0o100755,
95
+ mode: (info.mode & 0o100) === 0 ? 0o100644 : 0o100755,
96
96
  });
97
97
  }
98
98
  return files;
@@ -251,7 +251,7 @@ async function replaceSkill(source, target) {
251
251
  await mkdir(path.dirname(target), { recursive: true });
252
252
  let replaced = false;
253
253
  try {
254
- await cp(source, temporary, { recursive: true, errorOnExist: true });
254
+ await copySkillTree(source, temporary);
255
255
  try {
256
256
  await rename(target, backup);
257
257
  replaced = true;
@@ -271,6 +271,23 @@ async function replaceSkill(source, target) {
271
271
  }
272
272
  }
273
273
 
274
+ async function copySkillTree(source, target) {
275
+ const info = await lstat(source);
276
+ if (info.isSymbolicLink() || (!info.isDirectory() && !info.isFile())) {
277
+ invalidSkill(`Agent Skill contains a non-ordinary entry: ${source}.`);
278
+ }
279
+ if (info.isDirectory()) {
280
+ await mkdir(target, { mode: 0o777 });
281
+ const entries = await readdir(source, { withFileTypes: true });
282
+ for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
283
+ await copySkillTree(path.join(source, entry.name), path.join(target, entry.name));
284
+ }
285
+ return;
286
+ }
287
+ const mode = (info.mode & 0o100) === 0 ? 0o666 : 0o777;
288
+ await writeFile(target, await readFile(source), { flag: 'wx', mode });
289
+ }
290
+
274
291
  function targetFiles(skill) {
275
292
  return skill.files.map(({ path: file }) => `${SKILLS_ROOT}/${skill.name}/${file}`);
276
293
  }
@@ -304,6 +321,10 @@ export async function syncProjectSkills({ projectRoot, stack } = {}) {
304
321
  continue;
305
322
  }
306
323
  if (installed.hash !== managed.hash) {
324
+ if (installed.hash === skill.hash) {
325
+ manifest.value.skills[skill.name] = { hash: skill.hash, source: skill.source };
326
+ continue;
327
+ }
307
328
  diagnostics.push({
308
329
  code: 'AGENT_SKILL_CUSTOMIZED',
309
330
  message: `Preserved locally modified Agent Skill ${skill.name}.`,
@@ -388,7 +409,7 @@ export async function inspectProjectSkills({ projectRoot, stack } = {}) {
388
409
  message: `Project Agent Skill ${expected.name} is externally managed; Genesis preserved it.`,
389
410
  details: { name: expected.name, path: `${SKILLS_ROOT}/${expected.name}/SKILL.md` },
390
411
  });
391
- } else if (installed.hash !== managed.hash) {
412
+ } else if (installed.hash !== managed.hash && installed.hash !== expected.hash) {
392
413
  customized = true;
393
414
  diagnostics.push({
394
415
  code: 'AGENT_SKILL_CUSTOMIZED',
@@ -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),
@@ -0,0 +1,83 @@
1
+ import path from 'node:path';
2
+
3
+ function presentationRegionForPath(filePath, regions) {
4
+ return regions.find((region) => (
5
+ !region.fallback
6
+ && (filePath === region.pathPrefix || filePath.startsWith(`${region.pathPrefix}/`))
7
+ )) || regions.find(({ fallback }) => fallback) || null;
8
+ }
9
+
10
+ function parentDirectory(filePath) {
11
+ const parent = path.posix.dirname(filePath);
12
+ return parent === '.' ? '' : parent;
13
+ }
14
+
15
+ function presentationCampusPath(filePath, region, { file = false } = {}) {
16
+ const ownershipPath = file ? parentDirectory(filePath) : filePath;
17
+ if (!region || region.fallback) {
18
+ return ownershipPath.split('/').filter(Boolean)[0] || '';
19
+ }
20
+ if (ownershipPath === region.pathPrefix) return region.pathPrefix;
21
+ const relative = ownershipPath.slice(region.pathPrefix.length + 1);
22
+ const child = relative.split('/').filter(Boolean)[0];
23
+ return child ? `${region.pathPrefix}/${child}` : region.pathPrefix;
24
+ }
25
+
26
+ function campusId(regionId, campusPath) {
27
+ return `presentation-campus:${regionId}:${campusPath || '.'}`;
28
+ }
29
+
30
+ /** Resolves Stack-declared City regions and campus roots into Machine City facts. */
31
+ export function buildCityPresentation({ districts = [], files = [], regions = [] } = {}) {
32
+ if (regions.length === 0) {
33
+ return {
34
+ campuses: [],
35
+ placementForPath: () => ({ presentationCampusId: null, presentationRegionId: null }),
36
+ regions: [],
37
+ };
38
+ }
39
+ const districtsByPath = new Map(districts.map((district) => [district.path, district]));
40
+ const placements = new Map();
41
+ const mutableCampuses = new Map();
42
+ const mutableRegions = new Map(regions.map((region) => [region.id, {
43
+ ...region,
44
+ buildingCount: 0,
45
+ }]));
46
+
47
+ function resolvePlacement(filePath = '', { file = false } = {}) {
48
+ if (placements.has(filePath)) return placements.get(filePath);
49
+ const region = presentationRegionForPath(filePath, regions);
50
+ const campusPath = presentationCampusPath(filePath, region, { file });
51
+ const placement = {
52
+ presentationCampusId: campusId(region.id, campusPath),
53
+ presentationRegionId: region.id,
54
+ };
55
+ placements.set(filePath, placement);
56
+ if (!mutableCampuses.has(placement.presentationCampusId)) {
57
+ mutableCampuses.set(placement.presentationCampusId, {
58
+ id: placement.presentationCampusId,
59
+ regionId: region.id,
60
+ path: campusPath,
61
+ title: campusPath ? path.posix.basename(campusPath) : 'Project root',
62
+ districtId: districtsByPath.get(campusPath)?.id || null,
63
+ buildingCount: 0,
64
+ });
65
+ }
66
+ return placement;
67
+ }
68
+
69
+ for (const file of files) {
70
+ const placement = resolvePlacement(file.path, { file: true });
71
+ mutableRegions.get(placement.presentationRegionId).buildingCount += 1;
72
+ mutableCampuses.get(placement.presentationCampusId).buildingCount += 1;
73
+ }
74
+ for (const district of districts) resolvePlacement(district.path);
75
+
76
+ return {
77
+ campuses: [...mutableCampuses.values()]
78
+ .filter(({ buildingCount }) => buildingCount > 0)
79
+ .sort((left, right) => left.id.localeCompare(right.id)),
80
+ placementForPath: (filePath = '') => placements.get(filePath) || resolvePlacement(filePath),
81
+ regions: [...mutableRegions.values()],
82
+ };
83
+ }
@@ -3,6 +3,8 @@ import path from 'node:path';
3
3
 
4
4
  import { astGrepCodeIndexers } from './code-indexers/ast-grep.js';
5
5
  import { asDiagnostic } from './errors.js';
6
+ import { buildCityPresentation } from './city-presentation.js';
7
+ import { cityPathExcluded } from './stack-city-presentation.js';
6
8
  import { gitContext } from './git.js';
7
9
  import { isProjectContentPath } from './paths.js';
8
10
  import { inspectProgram } from './program.js';
@@ -128,8 +130,14 @@ function mergeIndexedFiles(contributions) {
128
130
  };
129
131
  }
130
132
 
131
- function machineCity({ components, contributions, diagnostics, indexers }) {
133
+ function machineCity({ cityRegions, components, contributions, diagnostics, indexers }) {
132
134
  const merged = mergeIndexedFiles(contributions);
135
+ const structuralDistricts = directoryRecords(merged.files.map(({ path: filePath }) => filePath));
136
+ const presentation = buildCityPresentation({
137
+ districts: structuralDistricts,
138
+ files: merged.files,
139
+ regions: cityRegions,
140
+ });
133
141
  const visibility = new Map(merged.functions.map((entry) => [entry.id, entry.visibility]));
134
142
  const codeHash = sha256(stableJson(merged.files.map(({ path: filePath, hash, mode }) => ({
135
143
  path: filePath,
@@ -141,13 +149,19 @@ function machineCity({ components, contributions, diagnostics, indexers }) {
141
149
  schemaVersion: 1,
142
150
  status: indexers.length === 0 ? 'unconfigured' : diagnostics.length > 0 ? 'completed-with-warning' : 'current',
143
151
  codeHash,
152
+ presentationRegions: presentation.regions,
153
+ presentationCampuses: presentation.campuses,
144
154
  stackComponents: components,
145
155
  indexers,
146
156
  diagnostics,
147
157
  functions: merged.functions,
148
- districts: directoryRecords(merged.files.map(({ path: filePath }) => filePath)),
158
+ districts: structuralDistricts.map((district) => ({
159
+ ...district,
160
+ ...presentation.placementForPath(district.path),
161
+ })),
149
162
  buildings: merged.files.map((file) => ({
150
163
  ...file,
164
+ ...presentation.placementForPath(file.path),
151
165
  districtId: directoryId(parentDirectory(file.path)),
152
166
  title: path.posix.basename(file.path),
153
167
  publicFunctionCount: file.functionIds.filter((id) => visibility.get(id) === 'public').length,
@@ -229,7 +243,13 @@ export async function buildProjectIndex({ projectRoot, queries = [], write = tru
229
243
  const stack = await readStack(root);
230
244
  const states = await gitVisibleFileStates(root, { includePath: isProjectContentPath });
231
245
  const files = [...states]
232
- .filter(([, state]) => state.exists && !state.symlink && !state.special && state.hash)
246
+ .filter(([filePath, state]) => (
247
+ state.exists
248
+ && !state.symlink
249
+ && !state.special
250
+ && state.hash
251
+ && !cityPathExcluded(filePath, stack.cityExclusions)
252
+ ))
233
253
  .map(([filePath, state]) => ({ path: filePath, hash: state.hash, mode: state.mode }));
234
254
  const indexers = [...new Set(stack.components.flatMap((component) => component.indexers || []))].sort();
235
255
  const contributions = [];
@@ -249,6 +269,7 @@ export async function buildProjectIndex({ projectRoot, queries = [], write = tru
249
269
  }
250
270
  }
251
271
  const machine = machineCity({
272
+ cityRegions: stack.cityRegions,
252
273
  components: stack.components.map(({ id }) => id),
253
274
  contributions,
254
275
  diagnostics,
@@ -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'),
package/src/index/init.js CHANGED
@@ -12,7 +12,7 @@ async function createIfMissing(projectRoot, relativePath, source) {
12
12
  const location = path.join(projectRoot, relativePath);
13
13
  await mkdir(path.dirname(location), { recursive: true });
14
14
  try {
15
- await writeFile(location, source, { flag: 'wx', mode: 0o644 });
15
+ await writeFile(location, source, { flag: 'wx', mode: 0o666 });
16
16
  return relativePath;
17
17
  } catch (error) {
18
18
  if (error?.code === 'EEXIST') return null;
@@ -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,8 +61,11 @@ 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,
67
+ cityExclusions: piece.cityExclusions,
68
+ cityRegions: piece.cityRegions,
66
69
  indexers: piece.indexers,
67
70
  environmentDefaults: piece.environmentDefaults,
68
71
  launchTargets: piece.launchTargets.map(({ id }) => id),