genesis-compiler 1.5.2 → 1.6.0

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
@@ -28,7 +28,8 @@ genesis/
28
28
  engineering.md selected engineering profile and project-specific requirements
29
29
  stack.md selected components, resources, verification, and project-owned operation contracts
30
30
  stack/ optional per-component Description, Guidance, Adoption, Post-change, and Deslop customization
31
- program/ concise explanations grouped into conceptual subsystems
31
+ subsystems.md authored responsibilities, Program membership, and data ownership/use
32
+ program/ concise public-operation explanations
32
33
  .agents/skills/
33
34
  genesis-* Genesis workflow skills
34
35
  <technology>/ authoritative skills installed by selected Stack pieces
@@ -977,3 +978,60 @@ project operations, and records exact verification evidence. Code, tests, Git
977
978
  review, and the user's chosen agent remain responsible for implementation. It
978
979
  does not claim universal semantic convergence, exhaustive coverage, or
979
980
  whole-product correctness.
981
+
982
+ ## Authored subsystems (version zero)
983
+
984
+ `genesis/subsystems.md` is the single authored map connecting architectural
985
+ responsibilities to Program operations and data. Program directories are only
986
+ navigation; Genesis never infers subsystem membership from them. Blueprint
987
+ remains non-technical product intent. Source, migrations and runtime behavior
988
+ remain evidence; the map does not define application behavior or database DDL.
989
+
990
+ ```markdown
991
+ # Subsystems
992
+
993
+ ## `forms` Forms
994
+
995
+ Owns reusable form authoring, rendering, and answer validation.
996
+
997
+ ### Program
998
+ - `genesis/program/forms/render.md`
999
+
1000
+ ### Data owned
1001
+ - Table `database` `default` `form_definitions`
1002
+
1003
+ ### Data used
1004
+ - Nothing.
1005
+ ```
1006
+
1007
+ Each subsystem has a unique lowercase hyphenated id, title, responsibility
1008
+ prose, and exactly the three shown sections. Each Program module is assigned
1009
+ exactly once, by its complete project-relative path. Each table has at most one
1010
+ owner. Data used names a table owned by another declared subsystem. Empty
1011
+ sections explicitly say `- Nothing.`; an empty initialized map contains
1012
+ `# Subsystems` followed by `- Nothing.`. Data-only and code-only subsystems are
1013
+ valid. No subsystem hierarchy is inferred from paths.
1014
+
1015
+ Table references contain three distinct values: declared Stack resource **id**,
1016
+ schema, and table. `default` selects that resource's default schema; other schema
1017
+ names are literal. Never put host database names or environment values here.
1018
+ Genesis validates declarations and resource references without opening a database.
1019
+ Hosts resolve table references against their inspected schema and show missing
1020
+ references and unassigned tables explicitly; the map is not a complete schema
1021
+ inventory. Table/column descriptions and relationships remain database metadata.
1022
+
1023
+ `inspectSubsystems({ projectRoot })` and `genesis inspect subsystems --json`
1024
+ return `genesis.subsystems.v0`, normalized entries, status and a deterministic
1025
+ identity. Program inspection additionally validates membership and referenced
1026
+ modules. Context includes the relevant subsystem through its Program Sources;
1027
+ `context .` includes all subsystems. Program City uses authored titles and table
1028
+ associations, including data-only districts, and changes its hash when the map
1029
+ changes. Opening context stays concise; detailed associations load on demand.
1030
+
1031
+ Adoption, Program refresh, work prompts, session guidance and installed workflow
1032
+ skills maintain the map alongside Program in the same turn. Review checks it
1033
+ read-only; Blueprint-only work leaves it alone. Ordinary private helper edits
1034
+ need no map change. Existing projects do not need a bulk update: missing or empty maps preserve
1035
+ Program access and instruct the agent to author the map from source before its
1036
+ next implementation task. Until then, operations have no declared subsystem;
1037
+ there is no folder-based compatibility reader or automatic ownership inference. Initialization creates an empty map without replacing one.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genesis-compiler",
3
- "version": "1.5.2",
3
+ "version": "1.6.0",
4
4
  "type": "module",
5
5
  "description": "An agent-independent prompt, multi-language code-index, cleanup, and verification companion with project agent guidance.",
6
6
  "repository": {
package/prompts/adopt.txt CHANGED
@@ -60,3 +60,18 @@ execute them. Finish with a clear inventory of what was imported, what remains
60
60
  unconfigured or blocked, and what would require a separately approved
61
61
  implementation port. Never claim the project is ready merely because Stack
62
62
  components were selected.
63
+
64
+ Maintain `genesis/subsystems.md` in the same implementation turn when a change
65
+ adds, removes, or changes a subsystem responsibility, Program membership, or
66
+ data ownership/use. Read the map before changing related code. Reuse existing
67
+ subsystems unless a distinct responsibility is evidenced. Declare each Program
68
+ module exactly once; folders do not determine membership. Keep table references
69
+ grounded in schema/migrations; Genesis does not inspect databases. Private
70
+ helper changes need no map edit when these associations remain unchanged.
71
+
72
+ If `genesis/subsystems.md` is missing, or is empty in an existing explained
73
+ application, create it from the actual source, schema, and existing Program
74
+ before implementation. This is ordinary explanatory adoption, not a blocker or
75
+ a reason to ask permission again. Preserve the existing Program and source;
76
+ inspect the relevant responsibilities and complete the smallest truthful map.
77
+ Initialization can create the empty file, but only the agent authors its meaning.
@@ -12,6 +12,21 @@ If the codebase is too large for one careful pass, use subagents as you judge
12
12
  appropriate, reconcile their findings, and remain responsible for one coherent
13
13
  result. Identify anything not inspected or genuinely uncertain.
14
14
 
15
- Edit only `genesis/blueprint.md` and explanatory Markdown below
15
+ Edit only `genesis/blueprint.md`, `genesis/subsystems.md`, and explanatory Markdown below
16
16
  `genesis/program/`. Summarize every explanatory file created, updated, or
17
17
  removed.
18
+
19
+ Maintain `genesis/subsystems.md` in the same implementation turn when a change
20
+ adds, removes, or changes a subsystem responsibility, Program membership, or
21
+ data ownership/use. Read the map before changing related code. Reuse existing
22
+ subsystems unless a distinct responsibility is evidenced. Declare each Program
23
+ module exactly once; folders do not determine membership. Keep table references
24
+ grounded in schema/migrations; Genesis does not inspect databases. Private
25
+ helper changes need no map edit when these associations remain unchanged.
26
+
27
+ If `genesis/subsystems.md` is missing, or is empty in an existing explained
28
+ application, create it from the actual source, schema, and existing Program
29
+ before implementation. This is ordinary explanatory adoption, not a blocker or
30
+ a reason to ask permission again. Preserve the existing Program and source;
31
+ inspect the relevant responsibilities and complete the smallest truthful map.
32
+ Initialization can create the empty file, but only the agent authors its meaning.
@@ -4,9 +4,24 @@ in complete Program-refresh mode.
4
4
  Explain the code that currently exists using the smallest useful
5
5
  subsystem-oriented Program. Read implementation, tests, Blueprint, and current
6
6
  Program material. Use Machine City as a navigation aid, then verify relevant
7
- source directly. Edit only explanatory Markdown below `genesis/program/`.
7
+ source directly. Edit only `genesis/subsystems.md` and explanatory Markdown below `genesis/program/`.
8
8
  Delete stale or duplicate modules and do not edit code, tests, configuration,
9
9
  Blueprint, Stack, or `.genesis/`.
10
10
 
11
11
  When finished, summarize Program files created, updated, or removed and report
12
12
  genuine ambiguity.
13
+
14
+ Maintain `genesis/subsystems.md` in the same implementation turn when a change
15
+ adds, removes, or changes a subsystem responsibility, Program membership, or
16
+ data ownership/use. Read the map before changing related code. Reuse existing
17
+ subsystems unless a distinct responsibility is evidenced. Declare each Program
18
+ module exactly once; folders do not determine membership. Keep table references
19
+ grounded in schema/migrations; Genesis does not inspect databases. Private
20
+ helper changes need no map edit when these associations remain unchanged.
21
+
22
+ If `genesis/subsystems.md` is missing, or is empty in an existing explained
23
+ application, create it from the actual source, schema, and existing Program
24
+ before implementation. This is ordinary explanatory adoption, not a blocker or
25
+ a reason to ask permission again. Preserve the existing Program and source;
26
+ inspect the relevant responsibilities and complete the smallest truthful map.
27
+ Initialization can create the empty file, but only the agent authors its meaning.
@@ -10,3 +10,5 @@ evidence from inference and state questions that require human intent.
10
10
 
11
11
  Do not edit any file. Return a concise report ordered by impact with exact file
12
12
  references, the evidence inspected, and the smallest sensible correction.
13
+
14
+ Review `genesis/subsystems.md` responsibilities, Program membership, and data ownership/use against source and schema. Report missing, stale, or conflicting associations without editing them.
package/prompts/work.txt CHANGED
@@ -38,3 +38,18 @@ explicit decision, and add the selection with the Genesis
38
38
  `stack add <piece...>` operation before relying on technology-specific
39
39
  operations. Do not end after only updating
40
40
  or summarizing the Blueprint while the Stack remains empty.
41
+
42
+ Maintain `genesis/subsystems.md` in the same implementation turn when a change
43
+ adds, removes, or changes a subsystem responsibility, Program membership, or
44
+ data ownership/use. Read the map before changing related code. Reuse existing
45
+ subsystems unless a distinct responsibility is evidenced. Declare each Program
46
+ module exactly once; folders do not determine membership. Keep table references
47
+ grounded in schema/migrations; Genesis does not inspect databases. Private
48
+ helper changes need no map edit when these associations remain unchanged.
49
+
50
+ If `genesis/subsystems.md` is missing, or is empty in an existing explained
51
+ application, create it from the actual source, schema, and existing Program
52
+ before implementation. This is ordinary explanatory adoption, not a blocker or
53
+ a reason to ask permission again. Preserve the existing Program and source;
54
+ inspect the relevant responsibilities and complete the smallest truthful map.
55
+ Initialization can create the empty file, but only the agent authors its meaning.
@@ -21,7 +21,8 @@ private design choice, or ambiguity into product intent.
21
21
 
22
22
  ## Program
23
23
 
24
- Organize Program beneath conceptual subsystem directories:
24
+ Organize Program in readable directories; membership is declared only in
25
+ `genesis/subsystems.md`, independently of directory names:
25
26
 
26
27
  ```text
27
28
  genesis/program/billing/invoices.md
@@ -64,3 +65,58 @@ The caller determines whether this is initial description, complete Program
64
65
  refresh, focused post-change reconciliation, Blueprint-only work, or read-only
65
66
  review. Respect the caller's edit boundary. Report ambiguity rather than
66
67
  inventing intent.
68
+
69
+ ## Subsystems
70
+
71
+ Maintain `genesis/subsystems.md` alongside Program in description and refresh
72
+ work. Blueprint-only work does not change the map; review remains read-only.
73
+ The map is the authored association authority, but responsibilities and table
74
+ references remain fallible explanations of application source and schema.
75
+
76
+ Use this exact version-zero grammar:
77
+
78
+ ```markdown
79
+ # Subsystems
80
+
81
+ ## `forms` Forms
82
+
83
+ Owns reusable form rendering, validation, and submission capture.
84
+
85
+ ### Program
86
+ - `genesis/program/forms/render.md`
87
+
88
+ ### Data owned
89
+ - Table `database` `default` `form_definitions`
90
+
91
+ ### Data used
92
+ - Nothing.
93
+ ```
94
+
95
+ Use one stable lowercase hyphenated id and a human title per subsystem. Each
96
+ entry needs responsibility prose and exactly the three sections shown. Empty
97
+ sections say `- Nothing.`; a new empty map contains only `# Subsystems` and
98
+ `- Nothing.`. Every Program module belongs to exactly one subsystem. No folder
99
+ inference, globs, duplicated operations, or duplicated table owners.
100
+
101
+ A table identity is three separate exact backticked values: the declared Stack
102
+ resource id, schema, and table. `default` means the resource's default schema;
103
+ use an explicit schema where applicable. Never record a host database name,
104
+ credential, SQL expression, or environment value. Data used references a table
105
+ owned by another declared subsystem. A subsystem may own data without Program
106
+ operations, or operations without data. Read the real schema/migrations before
107
+ assigning ownership; report uncertainty instead of inventing associations.
108
+
109
+ Maintain `genesis/subsystems.md` in the same implementation turn when a change
110
+ adds, removes, or changes a subsystem responsibility, Program membership, or
111
+ data ownership/use. Read the map before changing related code. Reuse existing
112
+ subsystems unless a distinct responsibility is evidenced. Declare each Program
113
+ module exactly once; folders do not determine membership. Keep table references
114
+ grounded in schema/migrations; Genesis does not inspect databases. Private
115
+ helper changes need no map edit when these associations remain unchanged.
116
+
117
+ If `genesis/subsystems.md` is missing, or is empty in an existing explained
118
+ application, create it from the actual source, schema, and existing Program
119
+ before implementation. This is ordinary explanatory adoption, not a blocker or
120
+ a reason to ask permission again. Preserve the existing Program and source;
121
+ inspect the relevant responsibilities and complete the smallest truthful map.
122
+ Initialization can create the empty file, but only the agent authors its meaning.
@@ -152,3 +152,20 @@ required inputs and resources, declared project operations, and focused evidence
152
152
  with what actually exists. Report files changed, checks actually run,
153
153
  anything not proven, and anything still requiring attention. Never claim that
154
154
  an unrun check passed or that passing checks prove the whole product.
155
+
156
+ ## Subsystem maintenance
157
+
158
+ Maintain `genesis/subsystems.md` in the same implementation turn when a change
159
+ adds, removes, or changes a subsystem responsibility, Program membership, or
160
+ data ownership/use. Read the map before changing related code. Reuse existing
161
+ subsystems unless a distinct responsibility is evidenced. Declare each Program
162
+ module exactly once; folders do not determine membership. Keep table references
163
+ grounded in schema/migrations; Genesis does not inspect databases. Private
164
+ helper changes need no map edit when these associations remain unchanged.
165
+
166
+ If `genesis/subsystems.md` is missing, or is empty in an existing explained
167
+ application, create it from the actual source, schema, and existing Program
168
+ before implementation. This is ordinary explanatory adoption, not a blocker or
169
+ a reason to ask permission again. Preserve the existing Program and source;
170
+ inspect the relevant responsibilities and complete the smallest truthful map.
171
+ Initialization can create the empty file, but only the agent authors its meaning.
package/src/cli.js CHANGED
@@ -14,6 +14,7 @@ import {
14
14
  inspectEngineering,
15
15
  inspectEnvironment,
16
16
  inspectStackSection,
17
+ inspectSubsystems,
17
18
  inspectProject,
18
19
  inspectSkills,
19
20
  installCodex,
@@ -62,6 +63,7 @@ const USAGE = `Usage:
62
63
  genesis context <path...>
63
64
  genesis index [function-or-path...]
64
65
  genesis migrate
66
+ genesis inspect subsystems
65
67
  genesis inspect environment
66
68
  genesis inspect project
67
69
  genesis inspect skills
@@ -182,12 +184,12 @@ function parseCommand(argv) {
182
184
  } else if (command === 'context' && operands.length === 0) {
183
185
  fail('CONTEXT_PATH_REQUIRED', 'Command context requires at least one project path.');
184
186
  } else if (command === 'inspect') {
185
- const ordinaryInspection = operands.length === 1 && ['environment', 'project', 'skills'].includes(operands[0]);
187
+ const ordinaryInspection = operands.length === 1 && ['environment', 'project', 'skills', 'subsystems'].includes(operands[0]);
186
188
  const sectionInspection = operands.length >= 2 && operands[0] === 'section';
187
189
  if (!ordinaryInspection && !sectionInspection) {
188
190
  fail(
189
191
  'CLI_INSPECT_TARGET_REQUIRED',
190
- 'Command inspect requires project, environment, skills, or section <name>.',
192
+ 'Command inspect requires project, environment, skills, subsystems, or section <name>.',
191
193
  );
192
194
  }
193
195
  } else if (command === 'hook' && (operands.length !== 1 || !['discover', 'session', 'turn'].includes(operands[0]))) {
@@ -272,7 +274,9 @@ function writeInspection(result) {
272
274
  line(process.stdout, `${title}: ${result.status}`);
273
275
  namedItems('Components', result.components);
274
276
  namedItems('Runtimes', result.runtimeRequirements);
275
- if (result.inspection === 'environment') {
277
+ if (result.inspection === 'subsystems') {
278
+ namedItems('Subsystems', result.subsystems.map(({ id, title }) => `${id}: ${title}`));
279
+ } else if (result.inspection === 'environment') {
276
280
  namedItems('Public defaults', result.environmentDefaults.map(({ name, value }) => `${name}=${value}`));
277
281
  namedItems('Environment files', result.files.map(({ format, path: filePath }) => `${format}: ${filePath}`));
278
282
  } else if (result.inspection === 'section') {
@@ -511,7 +515,7 @@ async function execute({ command, operands, options }, { signal } = {}) {
511
515
  }),
512
516
  };
513
517
  }
514
- const inspections = { environment: inspectEnvironment, project: inspectProject, skills: inspectSkills };
518
+ const inspections = { subsystems: inspectSubsystems, environment: inspectEnvironment, project: inspectProject, skills: inspectSkills };
515
519
  return {
516
520
  inspection: operands[0],
517
521
  ...await inspections[operands[0]]({ projectRoot, stackPackages }),
@@ -43,7 +43,7 @@ function subsystemId(value) {
43
43
  }
44
44
 
45
45
  function operationId(module) {
46
- return `operation:${module.subsystem}/${module.name}`;
46
+ return `operation:${module.path}`;
47
47
  }
48
48
 
49
49
  function parentDirectory(value) {
@@ -70,18 +70,10 @@ function directoryRecords(paths) {
70
70
  }
71
71
 
72
72
  function subsystemRecords(subsystems) {
73
- const paths = new Set();
74
- for (const subsystem of subsystems) {
75
- const segments = subsystem.split('/');
76
- for (let count = 1; count <= segments.length; count += 1) {
77
- paths.add(segments.slice(0, count).join('/'));
78
- }
79
- }
80
- return [...paths].sort().map((subsystem) => ({
81
- id: subsystemId(subsystem),
82
- path: subsystem,
83
- title: path.posix.basename(subsystem).replace(/-/gu, ' '),
84
- parentId: subsystem.includes('/') ? subsystemId(parentDirectory(subsystem)) : null,
73
+ return subsystems.map((entry) => ({
74
+ id: subsystemId(entry.id), path: entry.id, title: entry.title,
75
+ description: entry.description, parentId: null,
76
+ dataOwned: entry.dataOwned, dataUsed: entry.dataUsed,
85
77
  }));
86
78
  }
87
79
 
@@ -195,7 +187,7 @@ async function programCity(projectRoot) {
195
187
  implementationMap: module.implementationMap,
196
188
  path: module.path,
197
189
  subsystem: module.subsystem,
198
- districtId: subsystemId(module.subsystem),
190
+ districtId: module.subsystem === null ? null : subsystemId(module.subsystem),
199
191
  sources: module.sources,
200
192
  sourceFileIds: module.sources.map(fileId),
201
193
  }));
@@ -203,9 +195,9 @@ async function programCity(projectRoot) {
203
195
  schema: 'genesis.program-city.v1',
204
196
  schemaVersion: 1,
205
197
  status: program.status,
206
- programHash: sha256(stableJson(operations)),
198
+ programHash: sha256(stableJson({ operations, subsystems: program.subsystemMap })),
207
199
  diagnostics: [],
208
- districts: subsystemRecords(program.subsystems),
200
+ districts: subsystemRecords(program.subsystemMap.subsystems),
209
201
  buildings: operations,
210
202
  links: operations.flatMap((operation) => operation.sourceFileIds.map((targetId) => ({
211
203
  kind: 'implemented-by',
@@ -111,6 +111,11 @@ export async function contextForProjectPaths({ paths, projectRoot, stackPackages
111
111
  '',
112
112
  'Use Program as a concise, fallible explanation. Code, tests, and runtime behavior remain the evidence.',
113
113
  '',
114
+ '## Relevant subsystems',
115
+ '',
116
+ ...(['missing', 'empty'].includes(program.subsystemMap?.status) ? ['The subsystem map needs authoring. Before implementation, create genesis/subsystems.md from source, schema, and existing Program; preserve existing explanations and do not infer ownership from folders.'] : []),
117
+ ...((program.subsystemMap?.subsystems || []).filter((entry) => targets.includes('') || modules.some((module) => module.subsystem === entry.id)).map((entry) => JSON.stringify(entry, null, 2))),
118
+ '',
114
119
  '## Relevant Program',
115
120
  '',
116
121
  ...programDetails,
@@ -147,6 +152,7 @@ export async function contextForProjectPaths({ paths, projectRoot, stackPackages
147
152
  status: 'ready',
148
153
  paths: targets,
149
154
  modules: moduleSources.map(({ source: _source, ...module }) => module),
155
+ subsystems: (program.subsystemMap?.subsystems || []).filter((entry) => targets.includes('') || modules.some((module) => module.subsystem === entry.id)),
150
156
  components: stack.components.map(({ id }) => id),
151
157
  engineeringProfile: engineering.profile.id,
152
158
  verificationCommands: stack.verificationCommands.map(({ label, argv }) => ({ label, argv })),
@@ -1,4 +1,5 @@
1
1
  export const GENESIS_CONTRACTS = Object.freeze({
2
+ subsystems: 'genesis.subsystems.v0',
2
3
  projectInspection: 'genesis.project-inspection.v1',
3
4
  templates: 'genesis.templates.v1',
4
5
  templateApplication: 'genesis.template-application.v1',
package/src/index/init.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { SUBSYSTEMS_PATH, EMPTY_SUBSYSTEMS_SOURCE } from './subsystems.js';
1
2
  import { mkdir, writeFile } from 'node:fs/promises';
2
3
  import path from 'node:path';
3
4
 
@@ -40,6 +41,7 @@ export async function initializeProject({ projectRoot, stackPackages = [] } = {}
40
41
  const projectFormat = await inspectProjectFormatAtRoot(root);
41
42
  requireCurrentProjectFormat(projectFormat, { allowUninitialized: true });
42
43
  const created = (await Promise.all([
44
+ createIfMissing(root, SUBSYSTEMS_PATH, EMPTY_SUBSYSTEMS_SOURCE),
43
45
  createIfMissing(root, BLUEPRINT_PATH, BLUEPRINT_SKELETON_SOURCE),
44
46
  createIfMissing(root, COLLABORATION_PATH, COLLABORATION_SKELETON_SOURCE),
45
47
  createIfMissing(root, ENGINEERING_PATH, ENGINEERING_SKELETON_SOURCE),
@@ -1,6 +1,7 @@
1
1
  import { readFile, readdir, stat } from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
 
4
+ import { inspectSubsystems } from './subsystems.js';
4
5
  import { GenesisError } from './errors.js';
5
6
  import { isProjectContentPath, PROGRAM_ROOT } from './paths.js';
6
7
  import { normalizeRelative } from './utils.js';
@@ -55,16 +56,15 @@ function moduleIdentity(programPath) {
55
56
  const relative = programPath.slice(`${PROGRAM_ROOT}/`.length, -3);
56
57
  const normalized = normalizeRelative(relative);
57
58
  const segments = normalized.split('/');
58
- if (segments.length < 2 || segments.some((segment) => !CONCEPT_NAME.test(segment))) {
59
+ if (segments.some((segment) => !CONCEPT_NAME.test(segment))) {
59
60
  throw new GenesisError(
60
61
  'PROGRAM_INVALID',
61
- `Program module needs lowercase conceptual subsystem and operation names: ${programPath}.`,
62
+ `Program module needs lowercase path names: ${programPath}.`,
62
63
  { path: programPath },
63
64
  );
64
65
  }
65
66
  return {
66
67
  name: segments.at(-1),
67
- subsystem: segments.slice(0, -1).join('/'),
68
68
  };
69
69
  }
70
70
 
@@ -136,8 +136,13 @@ function moduleContents(source, programPath) {
136
136
 
137
137
  export async function inspectProgram(projectRoot) {
138
138
  const { files, found } = await markdownFiles(projectRoot);
139
+ const subsystemMap = await inspectSubsystems({ projectRoot });
140
+ const assignments = new Map(subsystemMap.subsystems.flatMap((entry) => entry.program.map((file) => [file, entry.id])));
141
+ for (const file of assignments.keys()) {
142
+ if (!files.includes(file)) throw new GenesisError('SUBSYSTEMS_PROGRAM_MISSING', `Subsystem cites missing Program module: ${file}.`);
143
+ }
139
144
  if (!found || files.length === 0) {
140
- return { status: 'missing', files: [], modules: [], subsystems: [] };
145
+ return { status: subsystemMap.status === 'valid' ? 'valid' : 'missing', files: [], modules: [], subsystems: subsystemMap.subsystems.map(({ id }) => id), subsystemMap };
141
146
  }
142
147
 
143
148
  const modules = [];
@@ -150,6 +155,7 @@ export async function inspectProgram(projectRoot) {
150
155
  }
151
156
  const identity = moduleIdentity(programPath);
152
157
  const contents = moduleContents(source, programPath);
158
+ if (subsystemMap.status === 'valid' && !assignments.has(programPath)) throw new GenesisError('SUBSYSTEMS_PROGRAM_UNASSIGNED', `Program module has no declared subsystem: ${programPath}.`);
153
159
  const { sources } = contents;
154
160
  for (const sourcePath of sources) {
155
161
  if (!sourcePath || !isProjectContentPath(sourcePath) || !await sourceExists(projectRoot, sourcePath)) {
@@ -164,7 +170,7 @@ export async function inspectProgram(projectRoot) {
164
170
  path: programPath,
165
171
  name: identity.name,
166
172
  sources,
167
- subsystem: identity.subsystem,
173
+ subsystem: assignments.get(programPath) ?? null,
168
174
  title: contents.title,
169
175
  description: contents.description,
170
176
  publicContract: contents.publicContract,
@@ -176,6 +182,7 @@ export async function inspectProgram(projectRoot) {
176
182
  status: 'valid',
177
183
  files,
178
184
  modules,
179
- subsystems: [...new Set(modules.map(({ subsystem }) => subsystem))].sort(),
185
+ subsystems: subsystemMap.subsystems.map(({ id }) => id),
186
+ subsystemMap,
180
187
  };
181
188
  }
@@ -16,6 +16,7 @@ import { readStack } from './stack.js';
16
16
  export const GENESIS_BOOTSTRAP_PATHS = Object.freeze([
17
17
  'genesis/version',
18
18
  'genesis/blueprint.md',
19
+ 'genesis/subsystems.md',
19
20
  'genesis/stack.md',
20
21
  'genesis/collaboration.md',
21
22
  'genesis/engineering.md',
@@ -74,6 +74,7 @@ function programContext(program) {
74
74
  return {
75
75
  status: program.status,
76
76
  files: program.files,
77
+ subsystemMap: program.subsystemMap,
77
78
  subsystems: program.subsystems,
78
79
  modules: program.modules.map(({ path, name, sources, subsystem }) => ({
79
80
  path,
@@ -90,6 +91,7 @@ function startProgramContext(program) {
90
91
  status: program.status,
91
92
  subsystems: program.subsystems,
92
93
  moduleCount: program.modules.length,
94
+ subsystemMapStatus: program.subsystemMap?.status,
93
95
  ...(program.diagnostic ? { diagnostic: program.diagnostic } : {}),
94
96
  };
95
97
  }
@@ -149,6 +149,8 @@ export async function projectSessionContext({
149
149
  ...skills.diagnostics.map(({ message }) => `- Agent Skill maintenance: ${message}`),
150
150
  ...(skills.diagnostics.length > 0 ? ['- Skill inspection is read-only. Synchronization is an explicit source change; follow the current task authorization and host write boundary before running it.'] : []),
151
151
  '- After locating source, run `genesis context <path...>`; before adding a helper or public operation, run `genesis index <name-or-path...>` and reuse an existing owner.',
152
+ '- If genesis/subsystems.md is missing or empty in an existing explained application, author it from source, schema and existing Program before implementation. This is ordinary explanatory adoption, not a startup blocker; do not run tests or application commands merely to create the map.',
153
+ '- Read genesis/subsystems.md for declared subsystem responsibilities, Program membership, and data ownership/use. Maintain it in the same implementation turn when those facts change; reuse existing boundaries, verify source/schema, and never infer membership from folders.',
152
154
  '- Program is fallible explanation; code, tests, and runtime behavior remain evidence.',
153
155
  '- Keep Blueprint and affected Program explanations aligned with intentional observable product behavior in the same implementation turn. Private restructuring may need only source citations or no explanatory change.',
154
156
  '- Before reporting completion, compare the requested observable behavior, required inputs and resources, declared project operations, and focused evidence with what actually exists. State anything not proven.',
@@ -0,0 +1,106 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { gitContext } from './git.js';
4
+ import { readStack } from './stack.js';
5
+ import { GenesisError } from './errors.js';
6
+ import { normalizeRelative, sha256, stableJson } from './utils.js';
7
+
8
+ export const SUBSYSTEMS_PATH = 'genesis/subsystems.md';
9
+ export const EMPTY_SUBSYSTEMS_SOURCE = '# Subsystems\n\n- Nothing.\n';
10
+
11
+ // Table identities are declarations, never database queries or SQL identifiers.
12
+ export async function inspectSubsystems({ projectRoot = process.cwd() } = {}) {
13
+ projectRoot = (await gitContext(projectRoot)).repositoryRoot;
14
+ let source;
15
+ try { source = await readFile(path.join(projectRoot, SUBSYSTEMS_PATH), 'utf8'); }
16
+ catch (error) {
17
+ if (error.code !== 'ENOENT') throw error;
18
+ return { contract: 'genesis.subsystems.v0', status: 'missing', path: SUBSYSTEMS_PATH, subsystems: [] };
19
+ }
20
+ const invalid = (message) => { throw new GenesisError('SUBSYSTEMS_INVALID', `${SUBSYSTEMS_PATH}: ${message}`); };
21
+ const lines = source.replace(/\r\n?/gu, '\n').trim().split('\n');
22
+ if (lines.shift() !== '# Subsystems') invalid('Expected # Subsystems.');
23
+ const subsystems = [];
24
+ let current;
25
+ let section;
26
+ const sections = new Set();
27
+ const finish = () => {
28
+ if (!current) return;
29
+ if (!current.description.trim() || sections.size !== 3) invalid(`Subsystem ${current.id} needs a responsibility and Program, Data owned, Data used sections.`);
30
+ current.description = current.description.trim();
31
+ subsystems.push(current);
32
+ };
33
+ const empty = lines.join('\n').trim() === '- Nothing.';
34
+ if (!empty && !lines.some((line) => line.trim())) invalid('An empty map must say - Nothing.');
35
+ if (!empty) for (const line of lines) {
36
+ if (!line.trim()) continue;
37
+ const heading = line.match(/^## `([a-z0-9]+(?:-[a-z0-9]+)*)` (\S.*)$/u);
38
+ if (heading) {
39
+ finish();
40
+ current = { id: heading[1], title: heading[2], description: '', program: [], dataOwned: [], dataUsed: [] };
41
+ section = null;
42
+ sections.clear();
43
+ continue;
44
+ }
45
+ if (!current) invalid('Expected a subsystem heading or - Nothing.');
46
+ const sectionHeading = line.match(/^### (Program|Data owned|Data used)$/u);
47
+ if (sectionHeading) {
48
+ section = { Program: 'program', 'Data owned': 'dataOwned', 'Data used': 'dataUsed' }[sectionHeading[1]];
49
+ if (sections.has(section)) invalid(`Duplicate ${sectionHeading[1]} section.`);
50
+ sections.add(section);
51
+ continue;
52
+ }
53
+ if (line.startsWith('#')) invalid(`Unexpected heading: ${line}`);
54
+ if (!section) { current.description += `${line}\n`; continue; }
55
+ if (line === '- Nothing.') {
56
+ if (current[section].length || current[`${section}Empty`]) invalid('Nothing must be the only section entry.');
57
+ current[`${section}Empty`] = true;
58
+ continue;
59
+ }
60
+ if (current[`${section}Empty`]) invalid('Nothing must be the only section entry.');
61
+ if (section === 'program') {
62
+ const match = line.match(/^- `([^`]+)`$/u);
63
+ if (!match || !/^genesis\/program\/[a-z0-9-]+(?:\/[a-z0-9-]+)*\.md$/u.test(match[1]) || normalizeRelative(match[1]) !== match[1]) invalid(`Invalid Program reference: ${line}`);
64
+ current.program.push(match[1]);
65
+ } else {
66
+ const match = line.match(/^- Table `([^`]+)` `([^`]+)` `([^`]+)`$/u);
67
+ if (!match) invalid(`Expected Table resource, schema, and table: ${line}`);
68
+ current[section].push({ resource: match[1], schema: match[2], table: match[3] });
69
+ }
70
+ }
71
+ finish();
72
+ const ids = new Set();
73
+ const programs = new Set();
74
+ const owners = new Map();
75
+ for (const subsystem of subsystems) {
76
+ if (ids.has(subsystem.id)) invalid(`Duplicate subsystem ${subsystem.id}.`);
77
+ ids.add(subsystem.id);
78
+ for (const key of ['program', 'dataOwned', 'dataUsed']) {
79
+ if (!subsystem[key].length && !subsystem[`${key}Empty`]) invalid(`${subsystem.id}: empty sections must say - Nothing.`);
80
+ delete subsystem[`${key}Empty`];
81
+ const entries = subsystem[key].map((entry) => JSON.stringify(entry));
82
+ if (new Set(entries).size !== entries.length) invalid(`${subsystem.id}: duplicate ${key} entry.`);
83
+ }
84
+ for (const file of subsystem.program) {
85
+ if (programs.has(file)) invalid(`Program operation has multiple owners: ${file}.`);
86
+ programs.add(file);
87
+ }
88
+ for (const table of subsystem.dataOwned) {
89
+ const key = JSON.stringify(table);
90
+ if (owners.has(key)) invalid(`Table has multiple owners: ${key}.`);
91
+ owners.set(key, subsystem.id);
92
+ }
93
+ }
94
+ const tables = subsystems.flatMap((entry) => [...entry.dataOwned, ...entry.dataUsed]);
95
+ if (tables.length) {
96
+ const stack = await readStack(projectRoot);
97
+ const resources = new Set(stack.resources.map(({ resource }) => resource.id));
98
+ for (const table of tables) if (!resources.has(table.resource)) invalid(`Undeclared Stack resource: ${table.resource}.`);
99
+ }
100
+ for (const subsystem of subsystems) for (const table of subsystem.dataUsed) {
101
+ const owner = owners.get(JSON.stringify(table));
102
+ if (!owner || owner === subsystem.id) invalid(`${subsystem.id}: Data used must reference a table owned by another declared subsystem.`);
103
+ }
104
+ return { contract: 'genesis.subsystems.v0', status: subsystems.length ? 'valid' : 'empty', path: SUBSYSTEMS_PATH,
105
+ identity: sha256(stableJson(subsystems)), subsystems };
106
+ }
package/src/index.js CHANGED
@@ -223,3 +223,5 @@ export async function syncSkills(options) {
223
223
  guidance: 'Review changed skills in the ordinary Git diff. An assistant that already loaded them needs its supported context refresh before using the new guidance.',
224
224
  };
225
225
  }
226
+
227
+ export { inspectSubsystems } from './index/subsystems.js';