genesis-compiler 1.2.1 → 1.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,7 +13,7 @@ interaction.
13
13
  ```text
14
14
  genesis/
15
15
  blueprint.md non-technical product intent
16
- stack.md selected technology guidance, checks, setup, and launch data
16
+ stack.md selected technology guidance, checks, setup, launch, and deployment data
17
17
  stack/ optional per-component Description, Guidance, and Deslop customization
18
18
  program/ concise explanations grouped into conceptual subsystems
19
19
  .agents/skills/
@@ -116,8 +116,13 @@ Genesis never overwrites an unmanaged skill and preserves locally modified
116
116
  managed skills. Run `genesis init` after manually editing `genesis/stack.md`,
117
117
  or use `genesis stack add`, to synchronize selected skills.
118
118
 
119
- Hosts that create dependency-empty workspaces can call the public
120
- `inspectWorkspaceSetup()` API. A concrete Stack piece may declare one exact,
119
+ Run `genesis prepare` to execute the selected Stack's exact workspace recipe
120
+ directly with the caller's environment and installed toolchain. It executes
121
+ argument arrays without a shell, honors declared project-relative working
122
+ directories and conditional paths, stops on the first failure, and never
123
+ returns environment values. Hosts that need their own runtime, permission, or
124
+ process policy can instead call the public `inspectWorkspaceSetup()` API and
125
+ execute the same normalized recipe themselves. A concrete Stack piece may declare one exact,
121
126
  ordered `## Workspace setup` recipe with labels, argument arrays expressed as
122
127
  backticked tokens, abstract runtimes, and optional project-relative working
123
128
  directories and readiness paths. A recipe whose declared project marker does
@@ -127,29 +132,56 @@ piece waits for `package.json` and then declares `npm install`; generic
127
132
  `genesis/stack.md` replaces all component defaults, including with
128
133
  `- Nothing.`. Without that override, exactly one component recipe is used;
129
134
  multiple component recipes are reported as ambiguous and never merged.
130
- Genesis does not run the recipe. Stack resources such as database variables do
131
- not gate dependency installation.
135
+ The standalone command does not provision declared resources: callers provide
136
+ their own database, credentials, compilers, and other infrastructure. Stack
137
+ resources such as database variables do not gate dependency installation.
132
138
 
133
139
  Hosts that manage project environments can call `inspectEnvironment()`.
140
+ Concrete Stack integrations may declare public, non-secret Environment
141
+ defaults such as the database driver selected by that integration. Explicit
142
+ host or user values take precedence. The built-in JSKIT MySQL and PostgreSQL
143
+ pieces respectively declare `DB_CLIENT=mysql2` and `DB_CLIENT=pg`, so users
144
+ provide connection values rather than restating a deterministic technology
145
+ choice.
134
146
  An optional readable `## Environment files` section declares safe
135
147
  project-relative dotenv projections such as `.env`. A project declaration
136
148
  replaces component defaults, including with `- Nothing.`; otherwise component
137
- paths compose. Genesis reports resource declarations, value-free missing-input
138
- diagnostics, and paths. It never returns values, writes files, stores secrets,
139
- or decides which resolved values a host should materialize.
149
+ paths compose. Genesis reports public defaults, resource declarations,
150
+ value-free missing-input diagnostics, and paths. It never returns supplied
151
+ environment values, writes files, stores secrets, or decides which resolved
152
+ host values should be materialized.
140
153
 
141
154
  Hosts that provide previews can call the public `inspectLaunch()` API. An
142
155
  optional readable `## Launch` section in `genesis/stack.md` declares labeled
143
156
  targets, exact backticked argument tokens, relative working directories,
144
157
  abstract runtime requirements, `{host}`/`{port}` substitutions, and an optional
145
158
  application-owned Preview identity block. Concrete components may provide a
146
- default: `jskit` declares build followed by `npm start`; generic `nodejs` and
147
- `php` do not guess. Genesis starts nothing itself. It returns preview identity
159
+ default: `jskit` declares its app-owned `npm run develop` command; generic
160
+ `nodejs` and `php` do not guess. Genesis starts nothing itself. It returns preview identity
148
161
  command metadata and environment variable names, never their values. The host
149
- continues to own pinned runtimes, ports, processes, readiness, identity-command
150
- execution and secrets, browser binaries, Git, and credentials. See
162
+ continues to own pinned runtimes, ports, processes, readiness probing and
163
+ timeouts, identity-command execution and secrets, browser binaries, Git, and
164
+ credentials. Genesis owns only the exact HTTP readiness predicate declared by
165
+ the selected Stack. See
151
166
  [`docs/stack-components.md`](docs/stack-components.md) for the v1 contract.
152
167
 
168
+ Hosts that publish applications can call the separate public
169
+ `inspectDeployment()` API. An optional readable `## Deployment` section
170
+ declares an ordered production recipe: project-relative workdir, abstract
171
+ runtimes, exact Prepare/Build/Migrate/Serve argv, and one HTTP readiness
172
+ predicate. A concrete component may provide the recipe; the built-in `jskit`
173
+ piece declares production dependency installation, build, database preparation,
174
+ `npm start`, and `/api/health`. Generic language pieces do not guess. A project
175
+ section replaces component defaults wholesale, and `- Nothing.` explicitly
176
+ disables publishing.
177
+
178
+ Genesis only validates and reports this recipe and the Stack's abstract resource
179
+ requirements. It does not provision a database, resolve credentials, copy a
180
+ release, run commands, start a service, reserve a port, route traffic, issue TLS,
181
+ or roll back. A deployment host owns all of those operational responsibilities,
182
+ including the mapping from `mysql` or `postgresql` resources to its managed
183
+ services.
184
+
153
185
  At session start, Codex receives only a short explanation of how Genesis is
154
186
  organized. After it locates the source involved in a request, it can load the
155
187
  specific explanatory and technology context with:
@@ -332,8 +364,11 @@ Run the Stack's declared checks explicitly:
332
364
  genesis verify
333
365
  ```
334
366
 
335
- Genesis first evaluates generic Stack resource declarations, then runs each
336
- declared command without a shell. A complete successful run writes only
367
+ Genesis first waits for any technology-declared Workspace setup marker. A
368
+ missing marker is `unconfigured`, not a failed command. Once the workspace
369
+ exists, Genesis applies public Stack defaults beneath explicit environment
370
+ values, evaluates generic resource declarations, and runs each declared
371
+ command without a shell. A complete successful run writes only
337
372
  `.genesis/verification.json`, recording:
338
373
 
339
374
  - the exact Git-visible code hash;
@@ -413,10 +448,10 @@ to execute with its own pinned runtimes, caches, process isolation, and
413
448
  credentials. A configured recipe may remain unconfigured while it waits for a
414
449
  technology-declared project marker.
415
450
  `inspectEnvironment()` returns normalized Stack resource declarations,
416
- value-free missing-input diagnostics, and dotenv projection paths without
417
- returning any environment value.
451
+ public non-secret defaults, value-free missing-input diagnostics, and dotenv
452
+ projection paths without returning any supplied environment value.
418
453
  `inspectLaunch()` returns a validated, normalized Stack launch declaration for
419
- a host to execute with its own runtime, port, process, readiness, preview
454
+ a host to execute with its own runtime, port, process, readiness probe, preview
420
455
  identity, secret, and browser policy.
421
456
 
422
457
  A host such as Vibe64 can send the generated prompt to its existing agent:
@@ -24,3 +24,18 @@ semantically current, or that passing checks prove the whole product correct.
24
24
 
25
25
  Git remains the review and recovery boundary. The host running the generated
26
26
  prompt owns the agent and its interaction.
27
+
28
+ ## Filesystem ownership
29
+
30
+ Genesis does not copy package-file ownership or broad source modes into a
31
+ project. It creates ordinary project files with mode `0666`, directories and
32
+ executable skill files with mode `0777`, and lets the invoking process umask
33
+ and the destination directory's inherited ACL establish the effective mode and
34
+ group. Atomic replacements are created beside their destination so they inherit
35
+ the same filesystem policy before becoming visible. Private hook continuation
36
+ state remains explicitly `0600`.
37
+
38
+ This keeps ordinary single-user projects conventional while allowing a managed
39
+ host to require shared-group writes and no access for other users. The host is
40
+ responsible for establishing and verifying that umask, setgid directory, ACL,
41
+ and cross-identity contract before Genesis or an agent starts.
@@ -24,6 +24,17 @@ What this component contributes.
24
24
 
25
25
  - `javascript`
26
26
 
27
+ ## City regions
28
+
29
+ - Ignore `**/test/**`
30
+ - Ignore `**/tests/**`
31
+ - Ignore `**/__tests__/**`
32
+ - Ignore `**/*.test.*`
33
+ - Ignore `**/*.spec.*`
34
+ - Match `packages` as `Packages`: `packages/**`
35
+ - Match `source` as `Source`: `src/**`
36
+ - Fallback `everything-else` as `Everything else`
37
+
27
38
  ## Resources
28
39
 
29
40
  ```json genesis-resource
@@ -37,6 +48,10 @@ What this component contributes.
37
48
  }
38
49
  ```
39
50
 
51
+ ## Environment defaults
52
+
53
+ - Default `EXAMPLE_DRIVER`: `example`
54
+
40
55
  ## Guidance
41
56
 
42
57
  ### Technology-specific behavior
@@ -69,6 +84,7 @@ What this component contributes.
69
84
  - Workdir: `.`
70
85
  - Preferred port: `3000`
71
86
  - URL path: `/`
87
+ - Ready when: `GET` `/api/health` returns `200`
72
88
  - Runtimes: `nodejs`
73
89
  - Prepare `Build`: `npm` `run` `build`
74
90
  - Serve `Start`: `npm` `run` `server` `--` `--host` `{host}` `--port` `{port}`
@@ -93,7 +109,9 @@ from Genesis or from the declared npm package. Its `SKILL.md`, `references/`,
93
109
  Genesis expanding every manual into every prompt. `Resources` declares generic
94
110
  alternative environment-name sets reported by prompt generation and checked
95
111
  before verification. `allowEmpty` may name a required variable whose empty
96
- string is valid. `Guidance` is supplemental prose used by work, review, Program,
112
+ string is valid. `Environment defaults` declares public non-secret constants
113
+ owned by a concrete integration; explicit host values take precedence.
114
+ `Guidance` is supplemental prose used by work, review, Program,
97
115
  description, and cleanup prompts, as well as path-focused context. `Deslop`
98
116
  adds cleanup-only prose to explicit cleanup and the automatic Codex cleanup
99
117
  continuation. With no selected component, only the `genesis-deslop` skill
@@ -132,6 +150,16 @@ Precedence is intentionally small and deterministic:
132
150
  General Stack resources do not gate Workspace setup. In particular, missing
133
151
  database variables do not prevent an otherwise independent dependency install.
134
152
 
153
+ Each `Environment defaults` entry is one `Default` bullet with a backticked
154
+ environment name and value. Selected
155
+ component defaults compose by name; identical declarations collapse and
156
+ different values fail rather than silently choosing one. Defaults participate
157
+ in Stack identity, resource preflight, prompt context, Launch inspection, and
158
+ verification execution. The JSKIT database integrations use this contract for
159
+ their deterministic `DB_CLIENT` driver ids; generic database pieces do not.
160
+ Secrets and deployment-specific connection values remain host inputs and must
161
+ never be declared as defaults.
162
+
135
163
  `Environment files` optionally declares project-relative dotenv projections a
136
164
  host may materialize from its own resolved project environment. Genesis
137
165
  validates and returns only the format and path; it never reads environment
@@ -145,8 +173,9 @@ The declaration is useful independently of Launch and Workspace setup.
145
173
  offer a preview without guessing how a concrete technology starts. Repeat a
146
174
  ``### Target `id`: label`` block to declare several targets. Default, Workdir,
147
175
  Preferred port, URL path, and Runtimes are optional; their normalized defaults
148
- are false, `.`, null, `/`, and no runtime requirements. Every Prepare or Serve
149
- command and argument is a separate backticked value returned as an `argv`
176
+ are false, `.`, null, `/`, and no runtime requirements. Every target declares
177
+ one HTTP readiness predicate with a project path and an exact successful status
178
+ from 200 through 399. Every Prepare or Serve command and argument is a separate backticked value returned as an `argv`
150
179
  array, never a shell program. A target has exactly one final Serve step; any
151
180
  earlier steps must be Prepare steps. Workdir is canonical and project-relative,
152
181
  Preferred port is only a hint from 1024 through 65535, and URL path is an
@@ -155,7 +184,8 @@ application path rather than a URL. The only substitutions are `{host}` and
155
184
  Launch section to disable component targets.
156
185
 
157
186
  Genesis validates and reports Launch data but does not select a runtime,
158
- reserve a port, start a process, decide readiness, or control a browser.
187
+ reserve a port, start a process, probe readiness, or control a browser. The host
188
+ executes the declared predicate and owns its timeout and retry policy.
159
189
 
160
190
  The optional `#### Preview identity` block is specific to hosts that implement
161
191
  the declared `vibe64.preview-identity.command.v1` protocol. Its executable is a
@@ -176,7 +206,41 @@ the ids it supports to its own pinned runtime packs; for example, it may map
176
206
  dependencies such as `composer`, or treats an unknown id as something similar.
177
207
  Declare each independently required runtime. Generic language pieces such as
178
208
  `nodejs` and `php` provide no Launch recipe. A concrete piece such as `jskit`
179
- may declare one; Genesis does not invent missing targets.
209
+ may declare its app-owned `npm run develop` command; Genesis does not invent
210
+ missing targets or supervise framework-specific subprocesses.
211
+
212
+ `Deployment` is a separate optional readable production recipe. It must not be
213
+ inferred from Launch because preview preparation and production publication have
214
+ different safety, dependency, migration, artifact, and rollback concerns. Its
215
+ complete form is:
216
+
217
+ ```markdown
218
+ ## Deployment
219
+
220
+ - Workdir: `.`
221
+ - Runtimes: `nodejs`
222
+ - Ready when: `GET` `/api/health` returns `200`
223
+ - Prepare `Install production dependencies`: `npm` `install` `--omit=dev`
224
+ - Build `Build`: `npm` `run` `build`
225
+ - Migrate `Prepare database`: `npm` `run` `db:prepare`
226
+ - Serve `Start`: `npm` `start`
227
+ ```
228
+
229
+ Every command and argument is a separate backticked value returned as an argv
230
+ array; shell source is rejected. Workdir is canonical and project-relative.
231
+ Prepare, Build, and Migrate steps are optional and retain their declared order.
232
+ There is exactly one final Serve step and one exact HTTP readiness predicate
233
+ whose successful status is from 200 through 399. Runtime ids remain abstract.
234
+
235
+ A project `## Deployment` section replaces every component recipe as one unit,
236
+ including with `- Nothing.`. Without a project section, exactly one selected
237
+ component recipe may apply; several are blocked as ambiguous and never merged.
238
+ Genesis returns the Stack's existing resource declarations with the recipe but
239
+ does not check production values, provision services, execute commands, package
240
+ artifacts, start processes, probe health, route traffic, manage TLS, or retain
241
+ releases. Those are host responsibilities. This keeps the same project portable:
242
+ Genesis alone explains the production requirements, while a host such as Vibe64
243
+ Online can map `mysql` or `postgresql` to a pinned managed service.
180
244
 
181
245
  `Indexers` selects installed structural adapters for the derived Machine City.
182
246
  The shared ast-grep engine owns traversal, bounds, diagnostics, JSON, and
@@ -186,6 +250,25 @@ may contribute indexers, and duplicates are collapsed. Index output is
186
250
  navigation data, not proof that every dynamic callable or framework edge was
187
251
  discovered.
188
252
 
253
+ `City regions` is an optional complete presentation contract for Machine City.
254
+ Leading `Ignore` entries are canonical project-relative glob patterns using
255
+ only `*` within a path segment and `**` across segments. Genesis excludes their
256
+ matching files before it emits Machine City buildings or functions. This keeps
257
+ test/spec source out of the product City without requiring a host-side filter.
258
+ Each subsequent ordered `Match` entry declares one canonical project-relative
259
+ `path/**` prefix, stable id, and human title. Exactly one final `Fallback` entry
260
+ owns every remaining indexed path. Ignore patterns, ids, and prefixes must be
261
+ unique, prefixes must not overlap, and no more than one selected component may
262
+ declare the contract. Genesis resolves every indexed file and structural
263
+ district to a region and a campus while generating
264
+ `.genesis/machine-city.json`; a City renderer consumes those emitted facts and
265
+ must not reclassify or filter paths. Under a matched prefix, the first directory
266
+ is a campus, while files directly below the prefix belong to the prefix campus.
267
+ Under the fallback, each top-level directory is a campus and project-root files
268
+ belong to the project-root campus. The built-in JSKIT piece excludes exact
269
+ `test`, `tests`, and `__tests__` path segments plus `*.test.*` and `*.spec.*`
270
+ files, then declares `Packages`, `Source`, and `Everything else`.
271
+
189
272
  Do not create a Genesis-owned generic skill merely because a component is
190
273
  named `nodejs`, `php`, or another common technology. Official, user, and host
191
274
  skills with those names must coexist normally. Use `## Skill` only for an
@@ -230,7 +313,7 @@ Replacement component description.
230
313
  Each field is optional. `Override` replaces only that installed field; `Add`
231
314
  then follows the installed or overridden value. These files cannot alter
232
315
  dependencies, conflicts, Agent Skill source, indexers, resources, environment
233
- files, commands, Workspace setup, or Launch declarations.
316
+ defaults, environment files, commands, Workspace setup, or Launch declarations.
234
317
  `genesis stack add` preserves the files and composes their effective prose after
235
318
  dependency resolution.
236
319
 
@@ -248,8 +331,8 @@ rather than a prompt or controller special case.
248
331
  Description, Guidance, Agent Skill, and Deslop prose are instructions, not
249
332
  verification evidence. Changes to them do not stale an exact successful
250
333
  verification record; selected component ids, resources, environment files,
251
- Workspace setup, launch targets, and verification commands remain part of the
252
- Stack identity.
334
+ environment defaults, Workspace setup, launch targets, and verification
335
+ commands remain part of the Stack identity.
253
336
 
254
337
  Hosts inspect normalized environment requirements independently:
255
338
 
@@ -260,9 +343,10 @@ const projectEnvironment = await inspectEnvironment({ environment, projectRoot }
260
343
  ```
261
344
 
262
345
  The result has `status` (`ready`, `missing-inputs`, or `unconfigured`),
263
- `stackHash`, selected `components`, declared `resources`, value-free
264
- `diagnostics`, and normalized `files`. Each file contains `format`, `path`, and
265
- `source`. Environment values never cross this API boundary.
346
+ `stackHash`, selected `components`, public non-secret `environmentDefaults`,
347
+ declared `resources`, value-free `diagnostics`, and normalized `files`. Each default has
348
+ `name`, `value`, and component `sources`; each file contains `format`, `path`,
349
+ and `source`. Supplied environment values never cross this API boundary.
266
350
 
267
351
  Fresh-workspace hosts read setup from the package root:
268
352
 
@@ -283,6 +367,20 @@ blocked result.
283
367
  The optional environment object is accepted for a consistent host inspection
284
368
  boundary, but Workspace setup v1 neither reads nor returns its values.
285
369
 
370
+ Standalone users may execute the same normalized recipe with their own
371
+ environment and installed toolchain:
372
+
373
+ ```bash
374
+ genesis prepare
375
+ ```
376
+
377
+ `genesis prepare` runs each declared argv directly without a shell, in order,
378
+ and stops at the first failure. It honors project-relative workdirs and
379
+ conditional paths. It does not provision databases, compilers, credentials,
380
+ runtimes, or other resources. Managed hosts remain free to consume
381
+ `inspectWorkspaceSetup()` and enforce their own execution, identity, runtime,
382
+ and permission policies.
383
+
286
384
  Preview hosts read the normalized Launch contract independently:
287
385
 
288
386
  ```js
@@ -292,11 +390,12 @@ const launch = await inspectLaunch({ projectRoot, environment });
292
390
  ```
293
391
 
294
392
  The result has `status` (`ready`, `blocked`, or `unconfigured`), `stackHash`,
295
- selected `components`, distinct `runtimeRequirements`, declared `resources`,
393
+ selected `components`, public `environmentDefaults`, distinct
394
+ `runtimeRequirements`, declared `resources`,
296
395
  normalized `targets`, and resource `diagnostics`. Each target reports its
297
396
  `source`, `available`, and `disabledReason`, plus its normalized
298
- `previewIdentity` declaration when present. Environment values are inspected
299
- but never returned. Here, `available` means only that the Stack's declared
397
+ `previewIdentity` declaration when present. Supplied environment values are
398
+ inspected but never returned. Here, `available` means only that the Stack's declared
300
399
  resource inputs are present: the host must still reject runtime requirements it
301
400
  cannot map. `unconfigured` means there are no targets, `blocked` means declared
302
401
  resource inputs are missing, and `ready` means neither condition applies. The
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genesis-compiler",
3
- "version": "1.2.1",
3
+ "version": "1.2.3",
4
4
  "type": "module",
5
5
  "description": "An agent-independent prompt, multi-language code-index, cleanup, and verification companion with optional Codex hooks.",
6
6
  "repository": {
@@ -56,7 +56,7 @@
56
56
  "@ast-grep/lang-ruby": "^0.0.7",
57
57
  "@ast-grep/lang-rust": "^0.0.7",
58
58
  "@ast-grep/napi": "^0.45.1",
59
- "@jskit-ai/agent-docs": "0.1.131",
59
+ "@jskit-ai/agent-docs": "0.1.132",
60
60
  "yaml": "^2.9.0"
61
61
  }
62
62
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genesis",
3
- "version": "1.2.1",
3
+ "version": "1.2.3",
4
4
  "description": "Makes Codex aware of optional Genesis adoption for existing projects.",
5
5
  "author": {
6
6
  "name": "Mobily Enterprises"
package/prompts/start.txt CHANGED
@@ -13,6 +13,11 @@ the non-technical Blueprint from the user's answers before implementation.
13
13
  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
+ 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.
16
21
 
17
22
  When `projectKind` is `existing`, ask what the user wants to understand or
18
23
  change. Use the current Blueprint, Stack, Program, and code index when present.
@@ -48,6 +48,9 @@ Neither is authority or proof; both may be regenerated with `genesis index`.
48
48
 
49
49
  ## Verify and report
50
50
 
51
- Use `genesis verify` for the Stack's declared final checks. Report files
52
- changed, checks actually run, and anything still requiring attention. Never
53
- claim that an unrun check passed or that passing checks prove the whole product.
51
+ After the selected technology's workspace substrate exists, use `genesis
52
+ verify` for the Stack's declared final checks. An unconfigured result means the
53
+ declared workspace or checks do not exist yet; it is not a failing check. Report
54
+ files changed, checks actually run, and anything still requiring attention.
55
+ Never claim that an unrun check passed or that passing checks prove the whole
56
+ product.
package/src/cli.js CHANGED
@@ -11,6 +11,7 @@ import {
11
11
  initialize,
12
12
  installCodex,
13
13
  listStackPieces,
14
+ prepareWorkspace,
14
15
  verify,
15
16
  } from './index.js';
16
17
  import {
@@ -30,6 +31,7 @@ const USAGE = `Usage:
30
31
  genesis stack add <piece...>
31
32
  genesis context <path...>
32
33
  genesis index [function-or-path...]
34
+ genesis prepare
33
35
  genesis prompt [request...]
34
36
  genesis prompt --task <start|work|deslop|program|blueprint|describe|review> [request...]
35
37
  genesis verify
@@ -46,7 +48,7 @@ prompt to the agent you already use. Review all edits through the ordinary Git
46
48
  diff, then run genesis verify for the Stack's concrete checks.
47
49
  `;
48
50
 
49
- const COMMANDS = new Set(['adopt', 'check', 'codex', 'context', 'hook', 'index', 'init', 'prompt', 'stack', 'verify']);
51
+ const COMMANDS = new Set(['adopt', 'check', 'codex', 'context', 'hook', 'index', 'init', 'prepare', 'prompt', 'stack', 'verify']);
50
52
 
51
53
  function parseCommand(argv) {
52
54
  if (argv.length === 0 || argv.includes('--help') || argv.includes('-h') || argv[0] === 'help') {
@@ -165,6 +167,19 @@ function writeResult(command, result) {
165
167
  line(process.stdout, `index: ${result.status}`);
166
168
  return;
167
169
  }
170
+ if (command === 'prepare') {
171
+ if (result.summary) line(process.stdout, result.summary);
172
+ namedItems('Commands', result.commands?.map(({ label, argv }) => `${label}: ${argv.join(' ')}`));
173
+ for (const item of result.diagnostics || []) {
174
+ line(process.stdout, `${item.code}: ${item.message}`);
175
+ for (const field of ['stdout', 'stderr']) {
176
+ const output = item.details?.[field]?.trim();
177
+ if (output) line(process.stdout, output);
178
+ }
179
+ }
180
+ line(process.stdout, `prepare: ${result.status}`);
181
+ return;
182
+ }
168
183
  if (command === 'hook') {
169
184
  if (['discover', 'session'].includes(result.kind) && result.output) line(process.stdout, result.output);
170
185
  else if (result.kind === 'stop') line(process.stdout, JSON.stringify(result.output));
@@ -226,6 +241,14 @@ async function execute({ command, operands, options }) {
226
241
  }
227
242
  if (command === 'context') return getContext({ paths: operands, projectRoot });
228
243
  if (command === 'index') return indexCodebase({ projectRoot, queries: operands });
244
+ if (command === 'prepare') {
245
+ return prepareWorkspace({
246
+ projectRoot,
247
+ onEvent: (event) => {
248
+ if (!options.json) line(process.stderr, event.message);
249
+ },
250
+ });
251
+ }
229
252
  if (command === 'hook') {
230
253
  if (operands[0] === 'discover') {
231
254
  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',
@@ -6,6 +6,7 @@ import { gitContext } from './git.js';
6
6
  import { inspectProgram } from './program.js';
7
7
  import { inspectVerification } from './project-state.js';
8
8
  import { missingStackResources } from './stack-preflight.js';
9
+ import { withStackEnvironmentDefaults } from './stack-environment-defaults.js';
9
10
 
10
11
  function invalidResult(area, error) {
11
12
  return {
@@ -55,7 +56,10 @@ export async function checkProject({ environment = process.env, projectRoot } =
55
56
  } catch (error) {
56
57
  skills = { status: 'invalid', diagnostics: [asDiagnostic(error)], skills: [] };
57
58
  }
58
- const missingResources = missingStackResources({ environment, resources: stack.resources });
59
+ const missingResources = missingStackResources({
60
+ environment: withStackEnvironmentDefaults(environment, stack.environmentDefaults),
61
+ resources: stack.resources,
62
+ });
59
63
  const verification = await inspectVerification({ projectRoot: root, stack });
60
64
  const diagnostics = [
61
65
  ...(program.diagnostic ? [program.diagnostic] : []),
@@ -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
+ }