genesis-compiler 1.9.3 → 1.10.1

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
@@ -36,7 +36,7 @@ genesis/
36
36
  .codex/hooks.json project-local Codex lifecycle integration
37
37
  .claude/settings.json project-local Claude Code lifecycle integration
38
38
  .opencode/plugins/ project-local OpenCode lifecycle integration
39
- .genesis/
39
+ .genesis/ local, git-ignored (`genesis init` adds the ignore lines)
40
40
  machine-city.json derived detailed source and function map
41
41
  program-city.json derived simple subsystem and operation map
42
42
  ```
@@ -73,7 +73,7 @@ completely or correctly explains the implementation.
73
73
 
74
74
  For ready-made applications and the shared new/existing-project workflow, see
75
75
  [Project opening and templates](docs/templates.md). Templates belong to Genesis
76
- and work through the CLI or Vibe64's Preview pane.
76
+ and work through the CLI or any host that integrates Genesis.
77
77
 
78
78
  Genesis requires Node.js 22 or newer and Git.
79
79
 
@@ -459,7 +459,9 @@ extractor provenance. Program City is intentionally smaller: conceptual
459
459
  subsystems, Program operations, informational helper maps, and links to their
460
460
  implementing source files.
461
461
  Both are derived JSON, not receipts, authority, or correctness claims. An
462
- extractor warning does not stop ordinary work.
462
+ extractor warning does not stop ordinary work. They are rebuilt from the
463
+ working tree, so they are not committed: `genesis init` adds both paths to the
464
+ project's `.gitignore`.
463
465
 
464
466
  Local package exports whose entry files are excluded from indexing retain their
465
467
  declared import paths with unavailable symbol coverage and a diagnostic. They do
@@ -590,8 +592,7 @@ Prompt generation remains available for other agents and explicit tasks:
590
592
  genesis prompt
591
593
  ```
592
594
 
593
- Give the printed prompt to Vibe64 or another coding agent with access to the
594
- project. Review ordinary edits through Git.
595
+ Give the printed prompt to any coding agent with access to the project. Review ordinary edits through Git.
595
596
 
596
597
  An immediate improvement request can be included directly:
597
598
 
@@ -705,8 +706,8 @@ selection, ordinary commands resolve the recorded package without another
705
706
  flag, but later catalog releases never silently rewrite those contracts.
706
707
  Selecting no components is valid. `genesis-stack` currently contains C/C++,
707
708
  C#, Go, Java, Kotlin, Node.js, PHP, Python, Ruby, Rust, and Shell, plus Vue,
708
- MySQL, PostgreSQL, JSKIT, JSKIT database integrations, Laravel, and Laravel
709
- database integrations.
709
+ MySQL, PostgreSQL, SQLite, the JSKIT, Laravel, Django, Rails, and Spring Boot
710
+ frameworks with their database integrations, Express, and Next.js.
710
711
 
711
712
  ### Why `genesis/stack.md` is deliberately small
712
713
 
@@ -729,7 +730,9 @@ source remains the authority for what actually exists.
729
730
  Selection copies operation proposals once. From that point onward, the complete
730
731
  project sections in `genesis/stack.md` win: upgrading `genesis-stack` can improve
731
732
  guidance for future work but cannot silently change how this application is
732
- installed, checked, run, or deployed.
733
+ installed, checked, run, or deployed. A section a newer component release
734
+ proposes stays a pending proposal, reported by `genesis check`, until the
735
+ project adopts it with `genesis stack add` or writes its own.
733
736
 
734
737
  Components may contribute:
735
738
 
@@ -771,7 +774,7 @@ add or replace complete project sections when its real scripts differ.
771
774
 
772
775
  | Project contract | Command or probe | Purpose | Executor |
773
776
  | --- | --- | --- | --- |
774
- | `Workspace setup` | `npm install` | Install development dependencies when `package.json` exists. | The operator or host preparing a workspace, such as Vibe64. |
777
+ | `Workspace setup` | `npm install` | Install development dependencies when `package.json` exists. | The operator or host preparing a workspace. |
775
778
  | `Outputs` | `npm run develop` | Start the interactive development web application. | The operator or output host. |
776
779
  | `Outputs` readiness | `GET /api/health` → `200` | Decide when the development output is ready; preferred port is `3000`. | The output host. |
777
780
  | `Verification` | `npm run verify` | Run the application's finite declared checks. | `genesis verify` executes this one directly. |
@@ -949,7 +952,7 @@ Extra language parsers install on first use, outside the project's dependencies.
949
952
  The CLI can also prepare and verify parsers for an offline host; see
950
953
  [language parser management](docs/parsers.md).
951
954
  `migrate()` updates recognized older Genesis project-file formats and returns
952
- the resulting structural check for hosts such as Vibe64.
955
+ the resulting structural check for hosts.
953
956
  `inspectProjectFormat({ projectRoot })` returns only the format marker's `path`,
954
957
  `status`, `projectVersion`, `supportedVersion`, `action`, and diagnostic when
955
958
  invalid. It retains Git repository discovery and trust checks, but does not
@@ -1030,7 +1033,7 @@ field: `genesis.collaboration.v1`, `genesis.engineering.v1`,
1030
1033
  `genesis.verification.v1`. A consumer defines any schema embedded inside the
1031
1034
  opaque section body.
1032
1035
 
1033
- A host such as Vibe64 can send the generated prompt to its existing agent:
1036
+ A host can send the generated prompt to its existing agent:
1034
1037
 
1035
1038
  ```js
1036
1039
  const work = await generatePrompt({
@@ -1121,7 +1124,7 @@ Initialization creates an empty map without replacing one.
1121
1124
  ## Publishing this repository
1122
1125
 
1123
1126
  Genesis has no release command of its own. `genesis-compiler` and
1124
- `genesis-stack` are released by the Vibe64 Online deploy, which runs the
1127
+ `genesis-stack` are released by an external operator workflow, which runs the
1125
1128
  published JSKIT release tool against a clean clone of this repository's `main`.
1126
1129
  Genesis does not depend on that tool.
1127
1130
 
@@ -158,7 +158,7 @@ component headings. Exact Program modules, sources, project contracts, indexed
158
158
  functions, verification commands, and skills are deferred to `genesis context
159
159
  <relevant-path...>`. A new technology request goes through `genesis stack list`
160
160
  before selection. For an existing uninitialized codebase it owns
161
- the adoption recommendation as part of the same portable prompt. A host such as Vibe64 sends this prompt on the
161
+ the adoption recommendation as part of the same portable prompt. A host sends this prompt on the
162
162
  first chat turn; it does not recreate the questions or Stack catalog.
163
163
  `describe` creates or refreshes the complete Blueprint and useful Program in one
164
164
  agent task. The host owns the agent and its interaction. Genesis owns only
@@ -304,9 +304,10 @@ project sections remain authoritative; reconcile any proposed operation against
304
304
  the real source. Use the project's pinned compiler invocation when it declares
305
305
  `genesis-compiler`: `npm exec --no -- genesis stack add nodejs`.
306
306
 
307
- Before rolling out a catalog that adds operation proposals, operators should
308
- run read-only project inspection against representative existing projects with
309
- the candidate compiler and catalog. Arrange explicit reconciliation of affected
310
- projects before rollout. A format marker alone does not establish compatibility
311
- with new catalog proposals. Do not repair project contracts through opening,
312
- status reads or another lazy write path.
307
+ A catalog release that adds an operation proposal does not break existing
308
+ projects. Current-format reads leave a proposed section the project lacks
309
+ pending and outside the effective Stack; `genesis check` and read-only project
310
+ inspection report it as `STACK_SECTION_PROPOSALS_PENDING` with the adoption
311
+ command. The project adopts it explicitly by re-running `genesis stack add` for
312
+ a selected component, or authors its own section. Do not repair project
313
+ contracts through opening, status reads or another lazy write path.
package/docs/templates.md CHANGED
@@ -12,7 +12,7 @@ Initialization is a decision to use Genesis; it is not evidence that an existing
12
12
  application has been described or that an application has been created.
13
13
 
14
14
  `genesis inspect project --json` is a read-only opening inspection shared by
15
- the CLI, session guidance, and hosts such as Vibe64:
15
+ the CLI, session guidance, and hosts:
16
16
 
17
17
  The diagnostics list reports each identical failure once, even when separate
18
18
  inspectors encounter it. Distinct failures remain available for repair.
@@ -35,7 +35,8 @@ the two derived Cities, Genesis's Codex/OpenCode integration files, its managed
35
35
  skills manifest, the relative Claude discovery link, and the SKILL.md/openai.yaml
36
36
  pair for each of the three Genesis workflow skills. Whole `.agents/`, `.codex/`, or `genesis/` directories are not
37
37
  exempt. A README, `.gitignore`, application manifest, unfamiliar source file, or
38
- custom agent tool counts as existing content. Ignored files also count, except
38
+ custom agent tool counts as existing content, except a `.gitignore` containing
39
+ only the two City lines `genesis init` writes. Ignored files also count, except
39
40
  installed `node_modules` dependencies. Source recognition does not depend on a
40
41
  language detector or an AI model.
41
42
 
@@ -64,7 +65,7 @@ genesis templates apply official:jskit/public
64
65
  ```
65
66
 
66
67
  The first-party JSKIT repository is
67
- [vibe64-dev/seed-jskit](https://github.com/vibe64-dev/seed-jskit). `public` supplies
68
+ the [JSKIT seed repository](https://github.com/vibe64-dev/seed-jskit). `public` supplies
68
69
  an adaptive public app; `accounts` adds local accounts, private Home and MySQL.
69
70
  Each branch is a complete, current Genesis project with actual application
70
71
  source, dependency lockfile, setup/run contracts and focused checks.
@@ -94,12 +95,12 @@ Template Stack components/packages are combined with existing selections;
94
95
  existing complete operation sections take precedence. No dependency command,
95
96
  database command, agent, or Git commit runs during import. The result reports
96
97
  the resolved source repository, branch and commit; no provenance ledger is
97
- written to the project. Review the ordinary diff and commit or use Vibe64 Save.
98
+ written to the project. Review the ordinary diff and commit it, or save it through the host.
98
99
 
99
100
  The application declares its actual operations in `genesis/stack.md`. CLI users
100
101
  provide the declared runtimes and resources and execute those commands themselves
101
- or with their agent. Vibe64 can provide runtimes, resource bindings, workspace
102
- preparation and preview routing through its existing host facilities.
102
+ or with their agent. A host can provide runtimes, resource bindings, workspace
103
+ preparation and preview routing through its own facilities.
103
104
 
104
105
  ## Supply catalogues
105
106
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genesis-compiler",
3
- "version": "1.9.3",
3
+ "version": "1.10.1",
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": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genesis",
3
- "version": "1.9.3",
3
+ "version": "1.10.1",
4
4
  "description": "Makes Codex aware of optional Genesis adoption for existing projects.",
5
5
  "author": {
6
6
  "name": "Mobily Enterprises"
@@ -105,8 +105,8 @@ tests, and runtime behavior.
105
105
 
106
106
  `.genesis/machine-city.json` is a derived detailed file/function map.
107
107
  `.genesis/program-city.json` is a derived simpler subsystem/operation map.
108
- Neither is authority or proof; both may be regenerated with the Genesis
109
- `index` operation.
108
+ Neither is authority or proof; both are git-ignored and may be regenerated
109
+ with the Genesis `index` operation.
110
110
 
111
111
  ## Implement ordinary local work
112
112
 
@@ -1,7 +1,7 @@
1
1
  import { readBlueprint } from './blueprint.js';
2
2
  import { readCollaboration } from './collaboration.js';
3
3
  import { inspectProjectSkills } from './agent-skills.js';
4
- import { readStack } from './stack.js';
4
+ import { readStack, stackPendingDiagnostics } from './stack.js';
5
5
  import { asDiagnostic } from './errors.js';
6
6
  import { gitContext } from './git.js';
7
7
  import { inspectProgram } from './program.js';
@@ -124,7 +124,9 @@ export async function checkProject({
124
124
  projectRoot: root,
125
125
  stackPackages,
126
126
  });
127
+ const pendingStack = stackPendingDiagnostics(stack);
127
128
  const diagnostics = [
129
+ ...pendingStack,
128
130
  ...(program.diagnostic ? [program.diagnostic] : []),
129
131
  ...skills.diagnostics,
130
132
  ...missingResources,
@@ -134,7 +136,7 @@ export async function checkProject({
134
136
  }] : []),
135
137
  ];
136
138
 
137
- const guidance = [];
139
+ const guidance = pendingStack.map(({ message }) => message);
138
140
  if (program.status === 'missing') guidance.push('Generate an explanatory Program prompt with genesis prompt --task program.');
139
141
  if (engineering.status === 'defaulted') guidance.push('Run genesis init or genesis engineering set focused.v1 to record the default engineering approach.');
140
142
  if (program.status === 'invalid') guidance.push('Repair the Program with genesis prompt --task program.');
@@ -147,7 +149,8 @@ export async function checkProject({
147
149
  if (verification.status === 'unconfigured') guidance.push('Add project verification commands to genesis/stack.md.');
148
150
  guidance.push('Use genesis prompt --task review for a semantic, evidence-based comparison.');
149
151
 
150
- const needsAttention = program.status === 'missing'
152
+ const needsAttention = pendingStack.length > 0
153
+ || program.status === 'missing'
151
154
  || engineering.status === 'defaulted'
152
155
  || ['missing', 'outdated'].includes(skills.status)
153
156
  || missingResources.length > 0
@@ -31,6 +31,11 @@ export const GENESIS_DERIVED_ARTIFACTS = Object.freeze([
31
31
  }),
32
32
  ]);
33
33
 
34
+ // Both Cities are rebuilt from the working tree, so projects ignore them.
35
+ export const GENESIS_DERIVED_ARTIFACTS_GITIGNORE_LINES = Object.freeze(
36
+ GENESIS_DERIVED_ARTIFACTS.map(({ path: artifactPath }) => `/${artifactPath}`),
37
+ );
38
+
34
39
  function directoryId(value) {
35
40
  return `directory:${value || '.'}`;
36
41
  }
package/src/index/init.js CHANGED
@@ -1,10 +1,11 @@
1
1
  import { SUBSYSTEMS_PATH, EMPTY_SUBSYSTEMS_SOURCE } from './subsystems.js';
2
- import { mkdir, writeFile } from 'node:fs/promises';
2
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
3
3
  import path from 'node:path';
4
4
 
5
5
  import { syncProjectSkills } from './agent-skills.js';
6
6
  import { BLUEPRINT_SKELETON_SOURCE } from './blueprint.js';
7
7
  import { syncAgentIntegrations } from './agent-integrations.js';
8
+ import { GENESIS_DERIVED_ARTIFACTS_GITIGNORE_LINES } from './code-index.js';
8
9
  import { COLLABORATION_SKELETON_SOURCE } from './collaboration.js';
9
10
  import { ENGINEERING_SKELETON_SOURCE } from './engineering.js';
10
11
  import { gitContext } from './git.js';
@@ -35,6 +36,24 @@ async function createIfMissing(projectRoot, relativePath, source) {
35
36
  }
36
37
  }
37
38
 
39
+ /** Append the derived City ignore lines a project's .gitignore lacks. */
40
+ async function ignoreDerivedArtifacts(projectRoot) {
41
+ const location = path.join(projectRoot, '.gitignore');
42
+ let source = '';
43
+ try {
44
+ source = await readFile(location, 'utf8');
45
+ } catch (error) {
46
+ if (!['ENOENT', 'ENOTDIR'].includes(error?.code)) throw error;
47
+ }
48
+ const present = new Set(source.split(/\r?\n/u).map((line) => line.trim().replace(/^\//u, '')));
49
+ const missing = GENESIS_DERIVED_ARTIFACTS_GITIGNORE_LINES
50
+ .filter((line) => !present.has(line.replace(/^\//u, '')));
51
+ if (missing.length === 0) return null;
52
+ const separator = source && !source.endsWith('\n') ? '\n' : '';
53
+ await writeFile(location, `${source}${separator}${missing.join('\n')}\n`, { mode: 0o666 });
54
+ return '.gitignore';
55
+ }
56
+
38
57
  export async function initializeProject({
39
58
  allowUnresolvedSections = false,
40
59
  projectRoot,
@@ -49,6 +68,7 @@ export async function initializeProject({
49
68
  createIfMissing(root, COLLABORATION_PATH, COLLABORATION_SKELETON_SOURCE),
50
69
  createIfMissing(root, ENGINEERING_PATH, ENGINEERING_SKELETON_SOURCE),
51
70
  createIfMissing(root, STACK_PATH, EMPTY_STACK_SOURCE),
71
+ ignoreDerivedArtifacts(root),
52
72
  ])).filter(Boolean);
53
73
  await mkdir(path.join(root, PROGRAM_ROOT), { recursive: true });
54
74
  const stack = await readStack(root, { allowUnresolvedSections, stackPackages });
@@ -4,6 +4,7 @@ import { isDeepStrictEqual } from 'node:util';
4
4
 
5
5
  import { readBlueprint } from './blueprint.js';
6
6
  import { readCollaboration } from './collaboration.js';
7
+ import { GENESIS_DERIVED_ARTIFACTS_GITIGNORE_LINES } from './code-index.js';
7
8
  import { GENESIS_CONTRACTS } from './contracts.js';
8
9
  import { readEngineering } from './engineering.js';
9
10
  import { asDiagnostic } from './errors.js';
@@ -11,7 +12,7 @@ import { gitContext } from './git.js';
11
12
  import { inspectProjectFormatAtRoot, projectFormatDiagnostic } from './project-format.js';
12
13
  import { inspectProgram } from './program.js';
13
14
  import { runGit } from './process.js';
14
- import { readStack } from './stack.js';
15
+ import { readStack, stackPendingDiagnostics } from './stack.js';
15
16
 
16
17
  // These are initialization outputs, not whole directories that may contain user code.
17
18
  export const GENESIS_BOOTSTRAP_PATHS = Object.freeze([
@@ -34,6 +35,17 @@ export const GENESIS_BOOTSTRAP_PATHS = Object.freeze([
34
35
  ]),
35
36
  ]);
36
37
  const bootstrapPaths = new Set(GENESIS_BOOTSTRAP_PATHS);
38
+ // Genesis creates .gitignore only to ignore its Cities; any other content is the project's.
39
+ async function isBootstrapGitignore(projectRoot, paths) {
40
+ if (!paths.includes('.gitignore')) return false;
41
+ try {
42
+ return await readFile(path.join(projectRoot, '.gitignore'), 'utf8')
43
+ === `${GENESIS_DERIVED_ARTIFACTS_GITIGNORE_LINES.join('\n')}\n`;
44
+ } catch (error) {
45
+ if (['ENOENT', 'ENOTDIR', 'EISDIR'].includes(error?.code)) return false;
46
+ throw error;
47
+ }
48
+ }
37
49
 
38
50
  export async function inspectProjectContent(projectRoot) {
39
51
  // Exclude dependency paths in Git before collecting and sorting its output.
@@ -71,7 +83,9 @@ export async function inspectProjectContent(projectRoot) {
71
83
  if (!(error instanceof SyntaxError) && !['ENOENT', 'ENOTDIR'].includes(error.code)) throw error;
72
84
  }
73
85
  }
86
+ const gitignoreBootstrap = await isBootstrapGitignore(projectRoot, paths);
74
87
  const existingPaths = paths.filter((file) => !bootstrapPaths.has(file) &&
88
+ !(gitignoreBootstrap && file === '.gitignore') &&
75
89
  !opencodeBootstrap.includes(file) && !file.split('/').includes('node_modules'));
76
90
  return {
77
91
  kind: existingPaths.length ? 'existing' : paths.length ? 'bootstrap' : 'empty',
@@ -121,6 +135,9 @@ export async function inspectProject({ projectRoot, stackPackages = [] } = {}) {
121
135
  await readFile(path.join(root, 'genesis/stack.md'), 'utf8');
122
136
  stack = await readStack(root, { stackPackages });
123
137
  result.stackComponents = stack.components.map(({ id }) => id);
138
+ // Unadopted component proposals are offered, never a reason to repair.
139
+ const pending = stackPendingDiagnostics(stack);
140
+ if (pending.length) result.warnings = pending;
124
141
  } catch (error) { result.diagnostics.push(asDiagnostic(error)); }
125
142
  for (const read of [readCollaboration, readEngineering]) {
126
143
  try { await read(root); } catch (error) { result.diagnostics.push(asDiagnostic(error)); }
@@ -94,17 +94,25 @@ export function stackPieceContractNames(piece) {
94
94
  ];
95
95
  }
96
96
 
97
- function contractSections({ components, knownSectionNames, sections }) {
97
+ function contractSections({ adoptProposals, components, knownSectionNames, sections }) {
98
98
  const genesisContracts = componentGenesisContracts(components, sections);
99
99
  const materializedSections = [];
100
+ const pendingSections = [];
100
101
  const projectLines = new Map();
102
+ const propose = (name, lines) => {
103
+ if (!adoptProposals) {
104
+ pendingSections.push({ name, lines });
105
+ return;
106
+ }
107
+ projectLines.set(name, lines);
108
+ materializedSections.push(name);
109
+ };
101
110
 
102
111
  for (const name of PROJECT_CONTRACT_NAMES) {
103
112
  if (sections.has(name)) {
104
113
  projectLines.set(name, trimStackSectionLines(sections.get(name)));
105
114
  } else if (genesisContracts.has(name)) {
106
- projectLines.set(name, genesisContracts.get(name));
107
- materializedSections.push(name);
115
+ propose(name, genesisContracts.get(name));
108
116
  }
109
117
  }
110
118
 
@@ -119,8 +127,9 @@ function contractSections({ components, knownSectionNames, sections }) {
119
127
  unresolvedSections.push({ name: extension.name, diagnostic });
120
128
  continue;
121
129
  }
122
- projectLines.set(extension.name, trimStackSectionLines(extension.lines));
123
- if (!sections.has(extension.name)) materializedSections.push(extension.name);
130
+ const lines = trimStackSectionLines(extension.lines);
131
+ if (sections.has(extension.name)) projectLines.set(extension.name, lines);
132
+ else propose(extension.name, lines);
124
133
  }
125
134
 
126
135
  return {
@@ -130,6 +139,7 @@ function contractSections({ components, knownSectionNames, sections }) {
130
139
  .filter(([name]) => !PROJECT_CONTRACT_NAMES.includes(name))
131
140
  .map(([name, lines]) => ({ name, lines })),
132
141
  materializedSections,
142
+ pendingSections,
133
143
  projectContracts: [...projectLines].map(([name, lines]) => ({ name, lines })),
134
144
  resourceLines: projectLines.get('Resources') ?? null,
135
145
  unresolvedSections,
@@ -149,34 +159,40 @@ function unresolvedSectionsError(unresolvedSections) {
149
159
  );
150
160
  }
151
161
 
162
+ /** Adopt every selected proposal missing from the project (add and migration). */
152
163
  export function materializeStackProjectContracts(options) {
153
- return contractSections(options);
164
+ return contractSections({ ...options, adoptProposals: true });
154
165
  }
155
166
 
156
167
  /**
157
- * Require every selected proposal to be project-owned. A competing proposal
158
- * is a project decision; only the add operation that reports it may continue
159
- * without it.
168
+ * Read only project-owned contracts. A proposal the project has not adopted,
169
+ * such as a section a newer component release introduced, stays pending and
170
+ * outside the effective Stack. A competing proposal is a project decision; only
171
+ * the operations that report it may continue without it.
160
172
  */
161
- export function requireMaterializedStackProjectContracts({
173
+ export function projectOwnedStackContracts({
162
174
  allowUnresolvedSections = false,
163
175
  ...options
164
176
  }) {
165
- const result = contractSections(options);
166
- if (result.materializedSections.length > 0) {
167
- const headings = result.materializedSections.map((name) => `\`## ${name}\``);
168
- throw new GenesisError(
169
- 'STACK_PROJECT_CONTRACTS_INCOMPLETE',
170
- `Selected Stack components require project-owned contracts missing from genesis/stack.md: ${headings.join(', ')}. Run \`genesis migrate\` for an older project or re-run the confirmed \`genesis stack add\` operation for a current project.`,
171
- { sections: result.materializedSections },
172
- );
173
- }
177
+ const result = contractSections({ ...options, adoptProposals: false });
174
178
  if (!allowUnresolvedSections && result.unresolvedSections.length > 0) {
175
179
  throw unresolvedSectionsError(result.unresolvedSections);
176
180
  }
177
181
  return result;
178
182
  }
179
183
 
184
+ /** One warning naming the proposals a project has not adopted yet. */
185
+ export function pendingStackSectionsDiagnostic(pendingSections, components) {
186
+ if (pendingSections.length === 0) return null;
187
+ const headings = pendingSections.map(({ name }) => `\`## ${name}\``).join(', ');
188
+ const ids = components.map(({ id }) => id).join(' ');
189
+ return {
190
+ code: 'STACK_SECTION_PROPOSALS_PENDING',
191
+ message: `Selected Stack components propose sections missing from genesis/stack.md: ${headings}. They are not part of the project Stack until adopted. Review them against the application, then adopt them with \`genesis stack add ${ids}\` or author the sections yourself.`,
192
+ details: { sections: pendingSections.map(({ name, lines }) => ({ name, lines })) },
193
+ };
194
+ }
195
+
180
196
  function contractBlocks(projectContracts, hasDecisions) {
181
197
  if (projectContracts.length === 0) {
182
198
  if (hasDecisions) return ['No other operation contract was proposed by these components.'];
@@ -24,7 +24,8 @@ import {
24
24
  import {
25
25
  materializeStackProjectContracts,
26
26
  renderStackPreparationPrompt,
27
- requireMaterializedStackProjectContracts,
27
+ pendingStackSectionsDiagnostic,
28
+ projectOwnedStackContracts,
28
29
  stackPieceContractNames,
29
30
  } from './stack-project-contracts.js';
30
31
  import { parseStackVerificationLines } from './stack-verification.js';
@@ -332,7 +333,7 @@ async function readStackWithContractMode(projectRoot, {
332
333
  })).map((piece) => customizePiece(projectRoot, piece)));
333
334
  const projectContracts = (allowComponentContracts
334
335
  ? materializeStackProjectContracts
335
- : requireMaterializedStackProjectContracts)({
336
+ : projectOwnedStackContracts)({
336
337
  allowUnresolvedSections,
337
338
  components,
338
339
  knownSectionNames: STACK_SECTIONS,
@@ -396,6 +397,7 @@ async function readStackWithContractMode(projectRoot, {
396
397
  environmentFiles,
397
398
  extensions,
398
399
  projectContracts: projectContracts.projectContracts,
400
+ pendingSections: projectContracts.pendingSections,
399
401
  ...(allowUnresolvedSections ? { unresolvedSections: projectContracts.unresolvedSections } : {}),
400
402
  resources,
401
403
  guidance: composedProse(components, 'guidance'),
@@ -422,7 +424,10 @@ export function readStack(projectRoot, { allowUnresolvedSections = false, stackP
422
424
  export function stackDecisionResult(stack) {
423
425
  const unresolvedSections = stack.unresolvedSections || [];
424
426
  return {
425
- diagnostics: unresolvedSections.map(({ diagnostic }) => diagnostic),
427
+ diagnostics: [
428
+ ...unresolvedSections.map(({ diagnostic }) => diagnostic),
429
+ ...stackPendingDiagnostics(stack),
430
+ ],
426
431
  prompt: renderStackPreparationPrompt({
427
432
  components: stack.components,
428
433
  projectContracts: stack.projectContracts,
@@ -431,6 +436,12 @@ export function stackDecisionResult(stack) {
431
436
  };
432
437
  }
433
438
 
439
+ /** Warnings for component proposals the project has not adopted. */
440
+ export function stackPendingDiagnostics(stack) {
441
+ const diagnostic = pendingStackSectionsDiagnostic(stack.pendingSections || [], stack.components);
442
+ return diagnostic ? [diagnostic] : [];
443
+ }
444
+
434
445
  export function readLegacyStack(projectRoot, { stackPackages = [] } = {}) {
435
446
  return readStackWithContractMode(projectRoot, {
436
447
  allowComponentContracts: true,
@@ -91,7 +91,8 @@ export async function applyTemplate({ projectRoot, templateId, stackPackages = [
91
91
  const { templates } = await listTemplates({ projectRoot: root, stackPackages, templateSources });
92
92
  const template = resolveTemplate(templates, String(templateId || ''));
93
93
  const preserved = new Map();
94
- for (const name of GENESIS_BOOTSTRAP_PATHS) preserved.set(name, await existingFile(root, name));
94
+ // A seedable project's .gitignore can only be the one init wrote for the Cities.
95
+ for (const name of [...GENESIS_BOOTSTRAP_PATHS, '.gitignore']) preserved.set(name, await existingFile(root, name));
95
96
  const snapshot = await readGitSnapshot(template);
96
97
  stagingRoot = await mkdtemp(path.join(os.tmpdir(), 'genesis-template-project-'));
97
98
  await runGit(stagingRoot, ['init', '--quiet']);
@@ -109,7 +110,8 @@ export async function applyTemplate({ projectRoot, templateId, stackPackages = [
109
110
  throw new GenesisError('TEMPLATE_STACK_MISMATCH', 'The template Stack does not contain its advertised technology.');
110
111
  }
111
112
  for (const [name, previous] of preserved) {
112
- if (previous === null || name.startsWith('.genesis/')) continue;
113
+ // Initialization below re-adds the City ignore lines to the template's own .gitignore.
114
+ if (previous === null || name.startsWith('.genesis/') || name === '.gitignore') continue;
113
115
  const { contents, linkTarget } = previous;
114
116
  if (name === 'genesis/blueprint.md' && !parseBlueprintSource(contents.toString('utf8')).description) continue;
115
117
  if (name === 'genesis/subsystems.md' && (await inspectSubsystems({ projectRoot: root })).status === 'empty') continue;