genesis-compiler 1.2.8 → 1.2.11

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
@@ -510,7 +510,7 @@ executing or provisioning it.
510
510
 
511
511
  Normalized operational results identify their stable public contract in the
512
512
  `contract` field: `genesis.workspace-setup.v1`, `genesis.environment.v1`,
513
- `genesis.launch.v1`, `genesis.deployment.v1`, or
513
+ `genesis.launch.v1`, `genesis.deployment.v2`, or
514
514
  `genesis.verification.v1`. Hosts validate that identity instead of
515
515
  feature-detecting individual fields.
516
516
 
@@ -257,6 +257,7 @@ complete form is:
257
257
 
258
258
  - Workdir: `.`
259
259
  - Runtimes: `nodejs`
260
+ - Recreate on restore: `node_modules`
260
261
  - Ready when: `GET` `/api/health` returns `200`
261
262
  - Prepare `Install production dependencies`: `npm` `install` `--omit=dev`
262
263
  - Build `Build`: `npm` `run` `build`
@@ -270,6 +271,19 @@ Prepare, Build, and Migrate steps are optional and retain their declared order.
270
271
  There is exactly one final Serve step and one exact HTTP readiness predicate
271
272
  whose successful status is from 200 through 399. Runtime ids remain abstract.
272
273
 
274
+ `Recreate on restore` lists project-relative paths that a host may remove when
275
+ retaining an inactive release. Every listed path must be completely recreated
276
+ by the Deployment Prepare steps, which a host runs after restoring the release
277
+ and before Serve. Declaring these paths without a Prepare step is invalid.
278
+ An entry without `/` matches that path segment at every depth, so
279
+ `node_modules` covers root and nested workspace dependency trees. `*` stays
280
+ within one segment and `**` may cross path separators. A matched directory
281
+ includes its complete subtree.
282
+ This is application and Stack knowledge: for example, JSKIT declares
283
+ `node_modules`, while compiled output such as `dist` remains part of the
284
+ artifact because Prepare does not rebuild it. Genesis normalizes the paths but
285
+ does not remove, archive, restore, or reconstruct anything itself.
286
+
273
287
  A project `## Deployment` section replaces every component recipe as one unit,
274
288
  including with `- Nothing.`. Without a project section, exactly one selected
275
289
  component recipe may apply; several are blocked as ambiguous and never merged.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genesis-compiler",
3
- "version": "1.2.8",
3
+ "version": "1.2.11",
4
4
  "type": "module",
5
5
  "description": "An agent-independent prompt, multi-language code-index, cleanup, and verification companion with optional Codex hooks.",
6
6
  "repository": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genesis",
3
- "version": "1.2.8",
3
+ "version": "1.2.11",
4
4
  "description": "Makes Codex aware of optional Genesis adoption for existing projects.",
5
5
  "author": {
6
6
  "name": "Mobily Enterprises"
@@ -15,6 +15,19 @@ import { sha256, stableJson, writeFileAtomic } from './utils.js';
15
15
  export const MACHINE_CITY_PATH = '.genesis/machine-city.json';
16
16
  export const PROGRAM_CITY_PATH = '.genesis/program-city.json';
17
17
 
18
+ export const GENESIS_DERIVED_ARTIFACTS = Object.freeze([
19
+ Object.freeze({
20
+ id: 'machine-city',
21
+ path: MACHINE_CITY_PATH,
22
+ recreator: 'index-codebase',
23
+ }),
24
+ Object.freeze({
25
+ id: 'program-city',
26
+ path: PROGRAM_CITY_PATH,
27
+ recreator: 'index-codebase',
28
+ }),
29
+ ]);
30
+
18
31
  const INDEXERS = astGrepCodeIndexers;
19
32
 
20
33
  function directoryId(value) {
@@ -250,10 +250,19 @@ async function changedProjectPaths(projectRoot, before, after) {
250
250
  const dirtyChanges = [...new Set([...previous.keys(), ...current.keys()])].filter((file) => (
251
251
  stableJson(previous.get(file)) !== stableJson(current.get(file))
252
252
  ));
253
- return [...new Set([
253
+ const candidates = [...new Set([
254
254
  ...dirtyChanges,
255
255
  ...await committedPaths(projectRoot, before.head, after.head),
256
256
  ])].sort();
257
+ const changed = await Promise.all(candidates.map(async (file) => {
258
+ if (!previous.has(file)) return file;
259
+ // Moving pre-turn file bytes into HEAD is a save operation, not new implementation.
260
+ const finalState = current.has(file)
261
+ ? current.get(file)
262
+ : await pathState(path.join(projectRoot, file));
263
+ return stableJson(previous.get(file)) === stableJson(finalState) ? null : file;
264
+ }));
265
+ return changed.filter(Boolean);
257
266
  }
258
267
 
259
268
  function changedPathLines(changedPaths) {
@@ -1,5 +1,6 @@
1
1
  export const GENESIS_CONTRACTS = Object.freeze({
2
- deployment: 'genesis.deployment.v1',
2
+ deployment: 'genesis.deployment.v2',
3
+ derivedArtifacts: 'genesis.derived-artifacts.v1',
3
4
  environment: 'genesis.environment.v1',
4
5
  launch: 'genesis.launch.v1',
5
6
  verification: 'genesis.verification.v1',
@@ -13,6 +13,7 @@ export async function inspectProjectDeployment({ projectRoot, stackPackages = []
13
13
  else if (stack.deployment.steps.length > 0) status = 'ready';
14
14
  const recipe = {
15
15
  version: stack.deployment.version,
16
+ artifact: stack.deployment.artifact,
16
17
  workdir: stack.deployment.workdir,
17
18
  runtimeRequirements: stack.deployment.runtimeRequirements,
18
19
  readiness: stack.deployment.readiness,
@@ -368,7 +368,7 @@ export async function generateProjectPrompt({
368
368
  const blueprint = await readBlueprint(root, { required: true, requireDescription: true });
369
369
  stack ||= await readStack(root, { stackPackages });
370
370
  const [index, projectSkills] = await Promise.all([
371
- buildProjectIndex({ projectRoot: root, write: false }),
371
+ buildProjectIndex({ projectRoot: root, stackPackages, write: false }),
372
372
  inspectProjectSkills({ projectRoot: root, stack }),
373
373
  ]);
374
374
  const missing = missingStackResources({
@@ -7,6 +7,7 @@ import {
7
7
 
8
8
  const RUNTIME_ID = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
9
9
  const STEP_LINE = /^- (Prepare|Build|Migrate|Serve) `([^`\r\n]+)`:[ \t]+(.+)$/u;
10
+ const RESTORE_PATHS_PREFIX = '- Recreate on restore: ';
10
11
  const STEP_ROLE_ORDER = Object.freeze({
11
12
  prepare: 0,
12
13
  build: 1,
@@ -45,6 +46,36 @@ function parseRuntimes(source, deploymentPath, line) {
45
46
  return values;
46
47
  }
47
48
 
49
+ function parseDisposablePaths(source, deploymentPath, line) {
50
+ const values = parseBacktickedArguments(source.slice(RESTORE_PATHS_PREFIX.length));
51
+ if (!values || values.length === 0) {
52
+ invalid(
53
+ deploymentPath,
54
+ 'Deployment Recreate on restore paths must be separate non-empty backticked values.',
55
+ line,
56
+ );
57
+ }
58
+ const normalized = values.map((value) => String(value || '').trim());
59
+ if (normalized.some((value) => (
60
+ !value
61
+ || value.startsWith('/')
62
+ || value.includes('\\')
63
+ || value.includes('***')
64
+ || /[?\[\]{}!]/u.test(value)
65
+ || value.split('/').some((segment) => !segment || ['.', '..'].includes(segment))
66
+ ))) {
67
+ invalid(
68
+ deploymentPath,
69
+ 'Deployment Recreate on restore paths must be canonical project-relative globs using only * and ** wildcards.',
70
+ line,
71
+ );
72
+ }
73
+ if (new Set(normalized).size !== normalized.length) {
74
+ invalid(deploymentPath, 'Deployment contains a duplicate Recreate on restore path.', line);
75
+ }
76
+ return normalized;
77
+ }
78
+
48
79
  function parseUrlPath(value, deploymentPath, line) {
49
80
  const normalized = String(value || '').trim();
50
81
  if (!normalized.startsWith('/') || normalized.startsWith('//') || /[\\?#]/u.test(normalized)) {
@@ -103,10 +134,15 @@ export function parseStackDeploymentLines(lines, {
103
134
  } = {}) {
104
135
  if (lines === undefined) return null;
105
136
  const entries = lines.map((line) => line.trim()).filter(Boolean);
106
- if (entries.length === 1 && entries[0] === '- Nothing.') return { version: 1, steps: [] };
137
+ if (entries.length === 1 && entries[0] === '- Nothing.') {
138
+ return { version: 2, artifact: { disposablePaths: [] }, steps: [] };
139
+ }
107
140
 
108
141
  const result = {
109
- version: 1,
142
+ version: 2,
143
+ artifact: {
144
+ disposablePaths: [],
145
+ },
110
146
  workdir: '.',
111
147
  runtimeRequirements: [],
112
148
  readiness: null,
@@ -133,6 +169,14 @@ export function parseStackDeploymentLines(lines, {
133
169
  result.runtimeRequirements = parseRuntimes(source, deploymentPath, line);
134
170
  continue;
135
171
  }
172
+ if (source.startsWith(RESTORE_PATHS_PREFIX)) {
173
+ if (seen.has('restore-paths')) {
174
+ invalid(deploymentPath, 'Duplicate Deployment Recreate on restore entry.', line);
175
+ }
176
+ seen.add('restore-paths');
177
+ result.artifact.disposablePaths = parseDisposablePaths(source, deploymentPath, line);
178
+ continue;
179
+ }
136
180
  if (source.startsWith('- Ready when: ')) {
137
181
  if (seen.has('readiness')) invalid(deploymentPath, 'Duplicate Deployment Ready when.', line);
138
182
  seen.add('readiness');
@@ -164,6 +208,15 @@ export function parseStackDeploymentLines(lines, {
164
208
  if (!result.readiness) {
165
209
  invalid(deploymentPath, 'Deployment needs one Ready when entry.');
166
210
  }
211
+ if (
212
+ result.artifact.disposablePaths.length > 0
213
+ && !result.steps.some(({ role }) => role === 'prepare')
214
+ ) {
215
+ invalid(
216
+ deploymentPath,
217
+ 'Deployment may recreate paths on restore only when it declares a Prepare step.',
218
+ );
219
+ }
167
220
  return result;
168
221
  }
169
222
 
@@ -177,7 +230,8 @@ export function composeStackDeployment(components, projectDeployment) {
177
230
  }));
178
231
  if (declarations.length === 0) {
179
232
  return {
180
- version: 1,
233
+ version: 2,
234
+ artifact: { disposablePaths: [] },
181
235
  workdir: '.',
182
236
  runtimeRequirements: [],
183
237
  readiness: null,
@@ -189,7 +243,8 @@ export function composeStackDeployment(components, projectDeployment) {
189
243
  if (declarations.length === 1) return { ...declarations[0], diagnostics: [] };
190
244
  const sources = declarations.map(({ source }) => source).sort();
191
245
  return {
192
- version: 1,
246
+ version: 2,
247
+ artifact: { disposablePaths: [] },
193
248
  workdir: '.',
194
249
  runtimeRequirements: [],
195
250
  readiness: null,
package/src/index.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import path from 'node:path';
2
2
 
3
3
  import { checkProject } from './index/check.js';
4
- import { buildProjectIndex } from './index/code-index.js';
4
+ import { buildProjectIndex, GENESIS_DERIVED_ARTIFACTS } from './index/code-index.js';
5
+ import { GENESIS_CONTRACTS } from './index/contracts.js';
5
6
  import { contextForProjectPaths } from './index/context.js';
6
7
  import { generateProjectPrompt } from './index/prompt.js';
7
8
  import { initializeProject } from './index/init.js';
@@ -17,7 +18,7 @@ import {
17
18
  prepareProjectWorkspace,
18
19
  } from './index/workspace-setup.js';
19
20
 
20
- export { GENESIS_CONTRACTS } from './index/contracts.js';
21
+ export { GENESIS_CONTRACTS };
21
22
 
22
23
  function withIndexResult(result, index) {
23
24
  const changedFiles = [...new Set([...result.changedFiles, ...index.changedFiles])].sort();
@@ -131,6 +132,13 @@ export function indexCodebase({
131
132
  return buildProjectIndex({ projectRoot, queries, stackPackages, write });
132
133
  }
133
134
 
135
+ export function inspectDerivedArtifacts() {
136
+ return {
137
+ contract: GENESIS_CONTRACTS.derivedArtifacts,
138
+ artifacts: GENESIS_DERIVED_ARTIFACTS.map((artifact) => ({ ...artifact })),
139
+ };
140
+ }
141
+
134
142
  export function verify(options) {
135
143
  return verifyProject(options);
136
144
  }