genesis-compiler 1.2.26 → 1.2.28
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 +326 -55
- package/docs/prompt-integration.md +35 -15
- package/docs/stack-components.md +65 -23
- package/package.json +1 -1
- package/plugins/genesis/.codex-plugin/plugin.json +1 -1
- package/plugins/opencode/project-guidance.js +1 -2
- package/prompts/adopt.txt +5 -4
- package/prompts/deslop.txt +3 -1
- package/prompts/start.txt +48 -18
- package/prompts/work.txt +11 -3
- package/skills/genesis-deslop/SKILL.md +17 -0
- package/skills/genesis-project/SKILL.md +48 -9
- package/src/cli.js +19 -2
- package/src/index/codex-hooks.js +3 -1
- package/src/index/context.js +20 -0
- package/src/index/migration.js +12 -3
- package/src/index/project-format.js +1 -1
- package/src/index/session-context.js +18 -0
- package/src/index/stack-catalog.js +11 -0
- package/src/index/stack-project-contracts.js +205 -0
- package/src/index/stack-section.js +2 -2
- package/src/index/stack.js +91 -57
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
|
|
11
11
|
const require = createRequire(import.meta.url);
|
|
12
12
|
const PACKAGE_NAME = /^(?:@[a-z0-9._-]+\/)?[a-z0-9._-]+$/u;
|
|
13
|
+
const FIRST_PARTY_STACK_PACKAGE = 'genesis-stack';
|
|
13
14
|
|
|
14
15
|
export function normalizeStackPackageName(value) {
|
|
15
16
|
const name = String(value ?? '').trim();
|
|
@@ -50,6 +51,16 @@ async function packageDirectory(packageName, projectRoot) {
|
|
|
50
51
|
);
|
|
51
52
|
}
|
|
52
53
|
|
|
54
|
+
export async function installedFirstPartyStackPackages({ projectRoot } = {}) {
|
|
55
|
+
try {
|
|
56
|
+
await packageDirectory(FIRST_PARTY_STACK_PACKAGE, projectRoot);
|
|
57
|
+
return [FIRST_PARTY_STACK_PACKAGE];
|
|
58
|
+
} catch (error) {
|
|
59
|
+
if (error?.code === 'STACK_PACKAGE_UNAVAILABLE') return [];
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
53
64
|
async function readPieceDirectory(
|
|
54
65
|
directory,
|
|
55
66
|
sourcePrefix,
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { GenesisError } from './errors.js';
|
|
2
|
+
import { composeStackEnvironmentDefaults } from './stack-environment-defaults.js';
|
|
3
|
+
import { composeStackEnvironmentFiles } from './stack-environment-files.js';
|
|
4
|
+
import {
|
|
5
|
+
composeStackSections,
|
|
6
|
+
opaqueStackSections,
|
|
7
|
+
trimStackSectionLines,
|
|
8
|
+
} from './stack-section.js';
|
|
9
|
+
|
|
10
|
+
const PROJECT_CONTRACT_NAMES = [
|
|
11
|
+
'Resources',
|
|
12
|
+
'Environment defaults',
|
|
13
|
+
'Environment files',
|
|
14
|
+
'Verification',
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
function distinctVerificationCommands(components) {
|
|
18
|
+
const seen = new Set();
|
|
19
|
+
return components.flatMap(({ verificationCommands }) => verificationCommands)
|
|
20
|
+
.filter(({ label, argv }) => {
|
|
21
|
+
const key = JSON.stringify([label, argv]);
|
|
22
|
+
if (seen.has(key)) return false;
|
|
23
|
+
seen.add(key);
|
|
24
|
+
return true;
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function projectResource(resource) {
|
|
29
|
+
return {
|
|
30
|
+
id: resource.id,
|
|
31
|
+
kind: resource.kind,
|
|
32
|
+
environmentAlternatives: resource.environmentAlternatives.map((alternative) => ({
|
|
33
|
+
bindings: alternative.bindings,
|
|
34
|
+
...(alternative.allowEmpty.length > 0 ? { allowEmpty: alternative.allowEmpty } : {}),
|
|
35
|
+
...(alternative.preferred ? { preferred: true } : {}),
|
|
36
|
+
})),
|
|
37
|
+
...(Object.keys(resource.optionalBindings).length > 0
|
|
38
|
+
? { optionalBindings: resource.optionalBindings }
|
|
39
|
+
: {}),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function resourceLines(components) {
|
|
44
|
+
const resources = components.flatMap(({ resources }) => resources);
|
|
45
|
+
if (resources.length === 0) return null;
|
|
46
|
+
return resources.flatMap((resource, index) => [
|
|
47
|
+
...(index > 0 ? [''] : []),
|
|
48
|
+
'```json genesis-resource',
|
|
49
|
+
JSON.stringify(projectResource(resource), null, 2),
|
|
50
|
+
'```',
|
|
51
|
+
]);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function environmentDefaultLines(components) {
|
|
55
|
+
const defaults = composeStackEnvironmentDefaults(components);
|
|
56
|
+
if (defaults.length === 0) return null;
|
|
57
|
+
return defaults.map(({ name, value }) => `- Default \`${name}\`: \`${value}\``);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function environmentFileLines(components) {
|
|
61
|
+
const files = composeStackEnvironmentFiles(components, null);
|
|
62
|
+
if (files.length === 0) return null;
|
|
63
|
+
return files.map(({ path }) => `- Dotenv \`${path}\``);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function verificationLines(components) {
|
|
67
|
+
const commands = distinctVerificationCommands(components);
|
|
68
|
+
if (commands.length === 0) return null;
|
|
69
|
+
return commands.map(({ label, argv }) => (
|
|
70
|
+
`- Verify \`${label}\`: ${argv.map((value) => `\`${value}\``).join(' ')}`
|
|
71
|
+
));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function componentGenesisContracts(components, sections) {
|
|
75
|
+
return new Map([
|
|
76
|
+
['Resources', sections.has('Resources') ? null : resourceLines(components)],
|
|
77
|
+
['Environment defaults', sections.has('Environment defaults')
|
|
78
|
+
? null
|
|
79
|
+
: environmentDefaultLines(components)],
|
|
80
|
+
['Environment files', sections.has('Environment files')
|
|
81
|
+
? null
|
|
82
|
+
: environmentFileLines(components)],
|
|
83
|
+
['Verification', sections.has('Verification') ? null : verificationLines(components)],
|
|
84
|
+
].filter(([, lines]) => lines !== null));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function stackPieceContractNames(piece) {
|
|
88
|
+
return [
|
|
89
|
+
...(piece.resources.length > 0 ? ['Resources'] : []),
|
|
90
|
+
...(piece.environmentDefaults.length > 0 ? ['Environment defaults'] : []),
|
|
91
|
+
...(piece.environmentFiles.length > 0 ? ['Environment files'] : []),
|
|
92
|
+
...(piece.verificationCommands.length > 0 ? ['Verification'] : []),
|
|
93
|
+
...piece.extensions.map(({ name }) => name),
|
|
94
|
+
];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function contractSections({ components, knownSectionNames, sections }) {
|
|
98
|
+
const genesisContracts = componentGenesisContracts(components, sections);
|
|
99
|
+
const materializedSections = [];
|
|
100
|
+
const projectLines = new Map();
|
|
101
|
+
|
|
102
|
+
for (const name of PROJECT_CONTRACT_NAMES) {
|
|
103
|
+
if (sections.has(name)) {
|
|
104
|
+
projectLines.set(name, trimStackSectionLines(sections.get(name)));
|
|
105
|
+
} else if (genesisContracts.has(name)) {
|
|
106
|
+
projectLines.set(name, genesisContracts.get(name));
|
|
107
|
+
materializedSections.push(name);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const extensions = composeStackSections(
|
|
112
|
+
components,
|
|
113
|
+
opaqueStackSections(sections, knownSectionNames),
|
|
114
|
+
);
|
|
115
|
+
for (const extension of extensions) {
|
|
116
|
+
const [diagnostic] = extension.diagnostics;
|
|
117
|
+
if (diagnostic) {
|
|
118
|
+
throw new GenesisError(diagnostic.code, diagnostic.message, diagnostic.details);
|
|
119
|
+
}
|
|
120
|
+
projectLines.set(extension.name, trimStackSectionLines(extension.lines));
|
|
121
|
+
if (!sections.has(extension.name)) materializedSections.push(extension.name);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
environmentDefaultLines: projectLines.get('Environment defaults') ?? null,
|
|
126
|
+
environmentFileLines: projectLines.get('Environment files') ?? null,
|
|
127
|
+
extensionSections: [...projectLines.entries()]
|
|
128
|
+
.filter(([name]) => !PROJECT_CONTRACT_NAMES.includes(name))
|
|
129
|
+
.map(([name, lines]) => ({ name, lines })),
|
|
130
|
+
materializedSections,
|
|
131
|
+
projectContracts: [...projectLines].map(([name, lines]) => ({ name, lines })),
|
|
132
|
+
resourceLines: projectLines.get('Resources') ?? null,
|
|
133
|
+
verificationLines: projectLines.get('Verification') ?? null,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function materializeStackProjectContracts(options) {
|
|
138
|
+
return contractSections(options);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function requireMaterializedStackProjectContracts(options) {
|
|
142
|
+
const result = contractSections(options);
|
|
143
|
+
if (result.materializedSections.length > 0) {
|
|
144
|
+
const headings = result.materializedSections.map((name) => `\`## ${name}\``);
|
|
145
|
+
throw new GenesisError(
|
|
146
|
+
'STACK_PROJECT_CONTRACTS_INCOMPLETE',
|
|
147
|
+
`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.`,
|
|
148
|
+
{ sections: result.materializedSections },
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
return result;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function contractBlocks(projectContracts) {
|
|
155
|
+
if (projectContracts.length === 0) {
|
|
156
|
+
return [
|
|
157
|
+
'No operation contract was proposed by these components. Inspect the application you are creating and declare only its real setup, verification, output, and other consumer operations before calling it ready.',
|
|
158
|
+
];
|
|
159
|
+
}
|
|
160
|
+
return projectContracts.flatMap(({ name, lines }) => [
|
|
161
|
+
`### \`## ${name}\``,
|
|
162
|
+
'',
|
|
163
|
+
'~~~markdown',
|
|
164
|
+
...lines,
|
|
165
|
+
'~~~',
|
|
166
|
+
'',
|
|
167
|
+
]);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function renderStackPreparationPrompt({
|
|
171
|
+
components,
|
|
172
|
+
materializedSections = [],
|
|
173
|
+
newlySelected = [],
|
|
174
|
+
projectContracts = [],
|
|
175
|
+
reviewSections = [],
|
|
176
|
+
} = {}) {
|
|
177
|
+
if (newlySelected.length === 0 && materializedSections.length === 0) return '';
|
|
178
|
+
const selected = components.map(({ id }) => `\`${id}\``).join(', ');
|
|
179
|
+
return [
|
|
180
|
+
'# Prepare the selected Stack',
|
|
181
|
+
'',
|
|
182
|
+
`Genesis selected ${selected} and made its concrete operation proposals project-owned in \`genesis/stack.md\`. That file is now the durable contract for this application; future catalog releases must not silently change it.`,
|
|
183
|
+
...(materializedSections.length > 0 ? [
|
|
184
|
+
'',
|
|
185
|
+
`Materialized contract headings: ${materializedSections.map((name) => `\`## ${name}\``).join(', ')}.`,
|
|
186
|
+
] : []),
|
|
187
|
+
...(reviewSections.length > 0 ? [
|
|
188
|
+
'',
|
|
189
|
+
`These existing project contracts remained authoritative while the new components were added: ${reviewSections.map((name) => `\`## ${name}\``).join(', ')}. Reconcile them against the newly selected technology using source evidence; Genesis deliberately did not overwrite them.`,
|
|
190
|
+
] : []),
|
|
191
|
+
'',
|
|
192
|
+
'Continue this same task now:',
|
|
193
|
+
'',
|
|
194
|
+
'1. Read `genesis/blueprint.md`, `genesis/engineering.md`, and the complete `genesis/stack.md`, then run `genesis context .` (or the relevant source paths).',
|
|
195
|
+
'2. Load every applicable installed Agent Skill and follow the selected technology’s authoritative guidance before creating dependencies or source.',
|
|
196
|
+
'3. For a new project, implement the source and commands so every project contract below is true. For existing source, inspect reality first and change a complete project section when the inherited proposal is not already true; do not add compatibility code merely to imitate a proposal.',
|
|
197
|
+
'4. Keep each consumer-owned section opaque to Genesis. Validate it with the consumer or framework that owns its grammar and behavior.',
|
|
198
|
+
'5. Establish or update the non-technical Blueprint and affected Program explanations as the product becomes concrete.',
|
|
199
|
+
'6. Run `genesis check`, resolve structural or incomplete-contract diagnostics, and run `genesis verify` only after the declared verification commands exist. Passing verification is evidence for those commands, not proof of every consumer operation.',
|
|
200
|
+
'',
|
|
201
|
+
'## Project contracts to satisfy',
|
|
202
|
+
'',
|
|
203
|
+
...contractBlocks(projectContracts),
|
|
204
|
+
].join('\n').trimEnd();
|
|
205
|
+
}
|
|
@@ -31,8 +31,8 @@ export function opaqueStackSections(sections, knownNames) {
|
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* Compose consumer-owned Stack sections without interpreting their contents.
|
|
34
|
-
*
|
|
35
|
-
*
|
|
34
|
+
* An existing project declaration wins; otherwise exactly one selected
|
|
35
|
+
* component may propose a section for materialization under a given name.
|
|
36
36
|
*/
|
|
37
37
|
export function composeStackSections(components = [], projectSections = []) {
|
|
38
38
|
const projectByName = new Map(projectSections.map((section) => [section.name, section]));
|
package/src/index/stack.js
CHANGED
|
@@ -10,18 +10,21 @@ import {
|
|
|
10
10
|
import { composeStackCityPresentation } from './stack-city-presentation.js';
|
|
11
11
|
import { resolveStackPieces } from './stack-composition.js';
|
|
12
12
|
import {
|
|
13
|
-
composeStackEnvironmentDefaults,
|
|
14
13
|
parseStackEnvironmentDefaultLines,
|
|
15
14
|
} from './stack-environment-defaults.js';
|
|
16
15
|
import {
|
|
17
|
-
composeStackEnvironmentFiles,
|
|
18
16
|
parseStackEnvironmentFileLines,
|
|
19
17
|
} from './stack-environment-files.js';
|
|
20
18
|
import {
|
|
21
|
-
composeStackSections,
|
|
22
19
|
opaqueStackSections,
|
|
23
20
|
trimStackSectionLines,
|
|
24
21
|
} from './stack-section.js';
|
|
22
|
+
import {
|
|
23
|
+
materializeStackProjectContracts,
|
|
24
|
+
renderStackPreparationPrompt,
|
|
25
|
+
requireMaterializedStackProjectContracts,
|
|
26
|
+
stackPieceContractNames,
|
|
27
|
+
} from './stack-project-contracts.js';
|
|
25
28
|
import { parseStackVerificationLines } from './stack-verification.js';
|
|
26
29
|
import {
|
|
27
30
|
applyStackPieceCustomization,
|
|
@@ -129,7 +132,7 @@ function renderStack({
|
|
|
129
132
|
resourceLines = null,
|
|
130
133
|
extensionSections = [],
|
|
131
134
|
stackPackages = [],
|
|
132
|
-
verificationLines =
|
|
135
|
+
verificationLines = null,
|
|
133
136
|
}) {
|
|
134
137
|
return [
|
|
135
138
|
'# Stack',
|
|
@@ -148,7 +151,9 @@ function renderStack({
|
|
|
148
151
|
...(environmentFileLines === null
|
|
149
152
|
? []
|
|
150
153
|
: ['', '## Environment files', '', ...trimStackSectionLines(environmentFileLines)]),
|
|
151
|
-
...(verificationLines
|
|
154
|
+
...(verificationLines === null
|
|
155
|
+
? []
|
|
156
|
+
: ['', '## Verification', '', ...trimStackSectionLines(verificationLines)]),
|
|
152
157
|
...extensionSections.flatMap(({ name, lines }) => (
|
|
153
158
|
['', `## ${name}`, '', ...trimStackSectionLines(lines)]
|
|
154
159
|
)),
|
|
@@ -185,46 +190,48 @@ export async function addStackPieces({ pieces, projectRoot, stackPackages = [] }
|
|
|
185
190
|
.map((piece) => customizePiece(projectRoot, piece)),
|
|
186
191
|
);
|
|
187
192
|
const selected = selectedPieces.map(({ id }) => id);
|
|
193
|
+
const newlySelectedPieces = selectedPieces.filter(({ id }) => !existing.includes(id));
|
|
188
194
|
const selectedPackages = [...new Set([
|
|
189
195
|
...declaredPackages,
|
|
190
196
|
...selectedPieces.map(({ stackPackage }) => stackPackage).filter(Boolean),
|
|
191
197
|
])].sort();
|
|
192
|
-
const
|
|
193
|
-
|
|
198
|
+
const projectContracts = materializeStackProjectContracts({
|
|
199
|
+
components: selectedPieces,
|
|
200
|
+
knownSectionNames: STACK_SECTIONS,
|
|
201
|
+
sections,
|
|
202
|
+
});
|
|
203
|
+
const verificationLines = projectContracts.verificationLines === null ? null : parseStackVerificationLines(
|
|
204
|
+
projectContracts.verificationLines,
|
|
194
205
|
{ path: STACK_PATH },
|
|
195
206
|
)
|
|
196
207
|
.map(({ label, argv }) => `- Verify \`${label}\`: ${argv.map((value) => `\`${value}\``).join(' ')}`);
|
|
197
|
-
const resourceLines =
|
|
208
|
+
const resourceLines = projectContracts.resourceLines;
|
|
198
209
|
parseStackResourceLines(resourceLines === null ? undefined : resourceLines, { path: STACK_PATH });
|
|
199
|
-
const environmentDefaultLines =
|
|
200
|
-
? sections.get('Environment defaults')
|
|
201
|
-
: null;
|
|
210
|
+
const environmentDefaultLines = projectContracts.environmentDefaultLines;
|
|
202
211
|
parseStackEnvironmentDefaultLines(
|
|
203
212
|
environmentDefaultLines === null ? undefined : environmentDefaultLines,
|
|
204
213
|
{ path: STACK_PATH },
|
|
205
214
|
);
|
|
206
|
-
const environmentFileLines =
|
|
207
|
-
? sections.get('Environment files')
|
|
208
|
-
: null;
|
|
215
|
+
const environmentFileLines = projectContracts.environmentFileLines;
|
|
209
216
|
parseStackEnvironmentFileLines(
|
|
210
217
|
environmentFileLines === null ? undefined : environmentFileLines,
|
|
211
218
|
{ path: STACK_PATH },
|
|
212
219
|
);
|
|
213
|
-
const extensionSections = opaqueStackSections(sections, STACK_SECTIONS);
|
|
214
220
|
const rendered = renderStack({
|
|
215
221
|
componentIds: selected,
|
|
216
222
|
environmentDefaultLines,
|
|
217
223
|
environmentFileLines,
|
|
218
224
|
resourceLines,
|
|
219
|
-
extensionSections,
|
|
225
|
+
extensionSections: projectContracts.extensionSections,
|
|
220
226
|
stackPackages: selectedPackages,
|
|
221
227
|
verificationLines,
|
|
222
228
|
});
|
|
223
229
|
const stackChanged = rendered !== source;
|
|
224
230
|
if (stackChanged) await writeFileAtomic(location, rendered);
|
|
231
|
+
const stack = await readStack(projectRoot, { stackPackages: availablePackages });
|
|
225
232
|
const skills = await syncProjectSkills({
|
|
226
233
|
projectRoot,
|
|
227
|
-
stack
|
|
234
|
+
stack,
|
|
228
235
|
});
|
|
229
236
|
const changedFiles = [
|
|
230
237
|
...(stackChanged ? [STACK_PATH] : []),
|
|
@@ -238,24 +245,28 @@ export async function addStackPieces({ pieces, projectRoot, stackPackages = [] }
|
|
|
238
245
|
components: selected,
|
|
239
246
|
changedFiles,
|
|
240
247
|
diagnostics: skills.diagnostics,
|
|
248
|
+
prompt: renderStackPreparationPrompt({
|
|
249
|
+
components: stack.components,
|
|
250
|
+
materializedSections: projectContracts.materializedSections,
|
|
251
|
+
newlySelected: newlySelectedPieces.map(({ id }) => id),
|
|
252
|
+
projectContracts: stack.projectContracts,
|
|
253
|
+
reviewSections: [...new Set(newlySelectedPieces.flatMap(stackPieceContractNames))]
|
|
254
|
+
.filter((name) => sections.has(name)),
|
|
255
|
+
}),
|
|
241
256
|
};
|
|
242
257
|
}
|
|
243
258
|
|
|
244
|
-
function
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
return components.flatMap((piece) => piece.resources.map((resource) => ({
|
|
256
|
-
component: piece.id,
|
|
257
|
-
resource,
|
|
258
|
-
})));
|
|
259
|
+
export async function materializeProjectStackContracts({ projectRoot, stackPackages = [] }) {
|
|
260
|
+
const source = await readFile(path.join(projectRoot, STACK_PATH), 'utf8');
|
|
261
|
+
const selected = componentIds(parseSections(source));
|
|
262
|
+
if (selected.length === 0) {
|
|
263
|
+
return { changedFiles: [], diagnostics: [], materializedSections: [] };
|
|
264
|
+
}
|
|
265
|
+
const result = await addStackPieces({ pieces: selected, projectRoot, stackPackages });
|
|
266
|
+
return {
|
|
267
|
+
changedFiles: result.changedFiles,
|
|
268
|
+
diagnostics: result.diagnostics,
|
|
269
|
+
};
|
|
259
270
|
}
|
|
260
271
|
|
|
261
272
|
function composedProse(components, field) {
|
|
@@ -280,7 +291,10 @@ async function customizePiece(projectRoot, piece) {
|
|
|
280
291
|
}));
|
|
281
292
|
}
|
|
282
293
|
|
|
283
|
-
|
|
294
|
+
async function readStackWithContractMode(projectRoot, {
|
|
295
|
+
allowComponentContracts,
|
|
296
|
+
stackPackages = [],
|
|
297
|
+
}) {
|
|
284
298
|
const location = path.join(projectRoot, STACK_PATH);
|
|
285
299
|
let source;
|
|
286
300
|
try {
|
|
@@ -305,39 +319,45 @@ export async function readStack(projectRoot, { stackPackages = [] } = {}) {
|
|
|
305
319
|
catalog,
|
|
306
320
|
requested: componentIds(sections),
|
|
307
321
|
})).map((piece) => customizePiece(projectRoot, piece)));
|
|
322
|
+
const projectContracts = (allowComponentContracts
|
|
323
|
+
? materializeStackProjectContracts
|
|
324
|
+
: requireMaterializedStackProjectContracts)({
|
|
325
|
+
components,
|
|
326
|
+
knownSectionNames: STACK_SECTIONS,
|
|
327
|
+
sections,
|
|
328
|
+
});
|
|
308
329
|
const projectVerificationCommands = parseStackVerificationLines(
|
|
309
|
-
|
|
330
|
+
projectContracts.verificationLines || [],
|
|
310
331
|
{ path: STACK_PATH },
|
|
311
332
|
).map(({ line: _line, ...command }) => command);
|
|
312
|
-
const
|
|
313
|
-
const verificationCommands = distinctVerificationCommands(
|
|
314
|
-
projectVerificationCommands.length > 0
|
|
315
|
-
? projectVerificationCommands
|
|
316
|
-
: componentVerificationCommands,
|
|
317
|
-
);
|
|
333
|
+
const verificationCommands = projectVerificationCommands;
|
|
318
334
|
const cityPresentation = composeStackCityPresentation(components);
|
|
319
|
-
const
|
|
320
|
-
?
|
|
321
|
-
:
|
|
322
|
-
|
|
323
|
-
? resourceDeclarations(components)
|
|
324
|
-
: projectResources.map((resource) => ({ component: 'project', resource }));
|
|
335
|
+
const resources = parseStackResourceLines(
|
|
336
|
+
projectContracts.resourceLines === null ? undefined : projectContracts.resourceLines,
|
|
337
|
+
{ path: STACK_PATH },
|
|
338
|
+
).map((resource) => ({ component: 'project', resource }));
|
|
325
339
|
const projectEnvironmentDefaults = parseStackEnvironmentDefaultLines(
|
|
326
|
-
|
|
340
|
+
projectContracts.environmentDefaultLines === null
|
|
341
|
+
? undefined
|
|
342
|
+
: projectContracts.environmentDefaultLines,
|
|
327
343
|
{ path: STACK_PATH },
|
|
328
344
|
);
|
|
329
|
-
const environmentDefaults =
|
|
330
|
-
|
|
331
|
-
: composeStackEnvironmentDefaults(components);
|
|
345
|
+
const environmentDefaults = projectEnvironmentDefaults
|
|
346
|
+
.map((entry) => ({ ...entry, sources: ['project'] }));
|
|
332
347
|
const projectEnvironmentFiles = parseStackEnvironmentFileLines(
|
|
333
|
-
|
|
348
|
+
projectContracts.environmentFileLines === null
|
|
349
|
+
? undefined
|
|
350
|
+
: projectContracts.environmentFileLines,
|
|
334
351
|
{ path: STACK_PATH },
|
|
335
352
|
);
|
|
336
|
-
const environmentFiles =
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
353
|
+
const environmentFiles = (projectEnvironmentFiles || [])
|
|
354
|
+
.map((file) => ({ ...file, source: 'project' }));
|
|
355
|
+
const extensions = projectContracts.extensionSections.map(({ name, lines }) => ({
|
|
356
|
+
name,
|
|
357
|
+
lines,
|
|
358
|
+
source: 'project',
|
|
359
|
+
diagnostics: [],
|
|
360
|
+
}));
|
|
341
361
|
return {
|
|
342
362
|
path: STACK_PATH,
|
|
343
363
|
identityHash: sha256(stableJson({
|
|
@@ -359,6 +379,7 @@ export async function readStack(projectRoot, { stackPackages = [] } = {}) {
|
|
|
359
379
|
environmentDefaults,
|
|
360
380
|
environmentFiles,
|
|
361
381
|
extensions,
|
|
382
|
+
projectContracts: projectContracts.projectContracts,
|
|
362
383
|
resources,
|
|
363
384
|
guidance: composedProse(components, 'guidance'),
|
|
364
385
|
adoption: composedProse(components, 'adoption'),
|
|
@@ -367,9 +388,22 @@ export async function readStack(projectRoot, { stackPackages = [] } = {}) {
|
|
|
367
388
|
};
|
|
368
389
|
}
|
|
369
390
|
|
|
391
|
+
export function readStack(projectRoot, { stackPackages = [] } = {}) {
|
|
392
|
+
return readStackWithContractMode(projectRoot, {
|
|
393
|
+
allowComponentContracts: false,
|
|
394
|
+
stackPackages,
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
export function readLegacyStack(projectRoot, { stackPackages = [] } = {}) {
|
|
399
|
+
return readStackWithContractMode(projectRoot, {
|
|
400
|
+
allowComponentContracts: true,
|
|
401
|
+
stackPackages,
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
|
|
370
405
|
export function stackPromptContext(stack) {
|
|
371
406
|
return {
|
|
372
|
-
stackPackages: stack.stackPackages,
|
|
373
407
|
components: stack.components.map((piece) => ({
|
|
374
408
|
id: piece.id,
|
|
375
409
|
description: piece.description,
|