genesis-compiler 1.9.3 → 1.10.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 +13 -10
- package/docs/prompt-integration.md +1 -1
- package/docs/stack-components.md +7 -6
- package/docs/templates.md +7 -6
- package/package.json +1 -1
- package/plugins/genesis/.codex-plugin/plugin.json +1 -1
- package/skills/genesis-project/SKILL.md +2 -2
- package/src/index/check.js +6 -3
- package/src/index/code-index.js +5 -0
- package/src/index/init.js +21 -1
- package/src/index/project-inspection.js +18 -1
- package/src/index/stack-project-contracts.js +35 -19
- package/src/index/stack.js +14 -3
- package/src/index/template-project.js +4 -2
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
|
|
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
|
|
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
|
|
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
package/docs/stack-components.md
CHANGED
|
@@ -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
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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
|
|
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
|
|
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
|
-
[
|
|
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
|
|
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.
|
|
102
|
-
preparation and preview routing through its
|
|
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
|
@@ -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
|
|
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
|
|
package/src/index/check.js
CHANGED
|
@@ -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 =
|
|
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
|
package/src/index/code-index.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
123
|
-
if (
|
|
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
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
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
|
|
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.'];
|
package/src/index/stack.js
CHANGED
|
@@ -24,7 +24,8 @@ import {
|
|
|
24
24
|
import {
|
|
25
25
|
materializeStackProjectContracts,
|
|
26
26
|
renderStackPreparationPrompt,
|
|
27
|
-
|
|
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
|
-
:
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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;
|