genesis-compiler 1.2.27 → 1.2.29
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 +338 -59
- package/docs/prompt-integration.md +50 -21
- package/docs/stack-components.md +65 -23
- package/package.json +4 -1
- package/plugins/genesis/.codex-plugin/plugin.json +1 -1
- package/prompts/adopt.txt +5 -4
- package/prompts/deslop.txt +3 -1
- package/prompts/start-existing-uninitialized.txt +8 -0
- package/prompts/start-existing.txt +23 -0
- package/prompts/start-new.txt +27 -0
- package/prompts/start.txt +3 -55
- 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/assets.js +3 -0
- package/src/index/codex-hooks.js +8 -12
- package/src/index/context.js +20 -5
- package/src/index/migration.js +12 -3
- package/src/index/opencode-plugin.js +1 -1
- package/src/index/paths.js +1 -0
- package/src/index/project-files.js +12 -0
- package/src/index/project-format.js +1 -1
- package/src/index/prompt.js +46 -24
- 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 +93 -59
package/src/index/migration.js
CHANGED
|
@@ -11,14 +11,17 @@ import {
|
|
|
11
11
|
writeProjectFormatVersion,
|
|
12
12
|
} from './project-format.js';
|
|
13
13
|
import { PROJECT_VERSION_PATH } from './paths.js';
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
materializeProjectStackContracts,
|
|
16
|
+
readLegacyStack,
|
|
17
|
+
} from './stack.js';
|
|
15
18
|
import { uniqueSorted } from './utils.js';
|
|
16
19
|
|
|
17
20
|
async function validateLegacyProject({ projectRoot, stackPackages }) {
|
|
18
21
|
try {
|
|
19
22
|
await readBlueprint(projectRoot, { required: true });
|
|
20
23
|
await readEngineering(projectRoot);
|
|
21
|
-
await
|
|
24
|
+
await readLegacyStack(projectRoot, { stackPackages });
|
|
22
25
|
try {
|
|
23
26
|
await inspectProgram(projectRoot);
|
|
24
27
|
} catch (error) {
|
|
@@ -33,8 +36,13 @@ async function validateLegacyProject({ projectRoot, stackPackages }) {
|
|
|
33
36
|
}
|
|
34
37
|
}
|
|
35
38
|
|
|
39
|
+
async function materializeProjectContracts({ projectRoot, stackPackages }) {
|
|
40
|
+
return materializeProjectStackContracts({ projectRoot, stackPackages });
|
|
41
|
+
}
|
|
42
|
+
|
|
36
43
|
const MIGRATIONS = new Map([
|
|
37
44
|
[0, validateLegacyProject],
|
|
45
|
+
[1, materializeProjectContracts],
|
|
38
46
|
]);
|
|
39
47
|
|
|
40
48
|
export async function migrateProject({ projectRoot, stackPackages = [] } = {}) {
|
|
@@ -62,7 +70,8 @@ export async function migrateProject({ projectRoot, stackPackages = [] } = {}) {
|
|
|
62
70
|
{ projectVersion: version, supportedVersion: CURRENT_PROJECT_FORMAT_VERSION },
|
|
63
71
|
);
|
|
64
72
|
}
|
|
65
|
-
await migration({ projectRoot: root, stackPackages });
|
|
73
|
+
const result = await migration({ projectRoot: root, stackPackages });
|
|
74
|
+
migratedFiles.push(...(result?.changedFiles || []));
|
|
66
75
|
version += 1;
|
|
67
76
|
await writeProjectFormatVersion(root, version);
|
|
68
77
|
migratedFiles.push(PROJECT_VERSION_PATH);
|
|
@@ -2,9 +2,9 @@ import { readFile } from 'node:fs/promises';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
|
|
4
4
|
import { gitContext } from './git.js';
|
|
5
|
+
import { OPENCODE_PLUGIN_PATH } from './paths.js';
|
|
5
6
|
import { writeFileAtomic } from './utils.js';
|
|
6
7
|
|
|
7
|
-
const OPENCODE_PLUGIN_PATH = '.opencode/plugins/genesis-project-guidance.js';
|
|
8
8
|
const OPENCODE_PLUGIN_SOURCE = new URL('../../plugins/opencode/project-guidance.js', import.meta.url);
|
|
9
9
|
|
|
10
10
|
async function existingSource(location) {
|
package/src/index/paths.js
CHANGED
|
@@ -4,6 +4,7 @@ export const STACK_PATH = 'genesis/stack.md';
|
|
|
4
4
|
export const PROGRAM_ROOT = 'genesis/program';
|
|
5
5
|
export const PROJECT_VERSION_PATH = 'genesis/version';
|
|
6
6
|
export const VERIFICATION_PATH = '.genesis/verification.json';
|
|
7
|
+
export const OPENCODE_PLUGIN_PATH = '.opencode/plugins/genesis-project-guidance.js';
|
|
7
8
|
|
|
8
9
|
export function isProjectContentPath(file) {
|
|
9
10
|
const first = file.split('/')[0];
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
|
|
3
|
+
import { isProjectContentPath, OPENCODE_PLUGIN_PATH } from './paths.js';
|
|
3
4
|
import { runGit } from './process.js';
|
|
4
5
|
import { pathState } from './utils.js';
|
|
5
6
|
|
|
@@ -22,3 +23,14 @@ export async function gitVisibleFileStates(projectRoot, { includePath = () => tr
|
|
|
22
23
|
}
|
|
23
24
|
return states;
|
|
24
25
|
}
|
|
26
|
+
|
|
27
|
+
/** Classifies opening behavior from cheap Git-visible paths and selected Stack state. */
|
|
28
|
+
export async function classifyProjectKind({ projectRoot, stackComponents = [] } = {}) {
|
|
29
|
+
if (stackComponents.length > 0) return 'existing';
|
|
30
|
+
const existing = (await visiblePaths(projectRoot)).some((file) => (
|
|
31
|
+
isProjectContentPath(file)
|
|
32
|
+
&& file !== OPENCODE_PLUGIN_PATH
|
|
33
|
+
&& !file.split('/').includes('node_modules')
|
|
34
|
+
));
|
|
35
|
+
return existing ? 'existing' : 'new';
|
|
36
|
+
}
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
} from './paths.js';
|
|
13
13
|
import { normalizeSource, pathState, writeFileAtomic } from './utils.js';
|
|
14
14
|
|
|
15
|
-
export const CURRENT_PROJECT_FORMAT_VERSION =
|
|
15
|
+
export const CURRENT_PROJECT_FORMAT_VERSION = 2;
|
|
16
16
|
export const CURRENT_PROJECT_FORMAT_SOURCE = `${CURRENT_PROJECT_FORMAT_VERSION}\n`;
|
|
17
17
|
|
|
18
18
|
const VERSION_PATTERN = /^(0|[1-9][0-9]*)\n?$/u;
|
package/src/index/prompt.js
CHANGED
|
@@ -17,8 +17,7 @@ import { inspectVerification } from './project-state.js';
|
|
|
17
17
|
import { missingStackResources } from './stack-preflight.js';
|
|
18
18
|
import { withStackEnvironmentDefaults } from './stack-environment-defaults.js';
|
|
19
19
|
import { stableJson } from './utils.js';
|
|
20
|
-
import {
|
|
21
|
-
import { isProjectContentPath } from './paths.js';
|
|
20
|
+
import { classifyProjectKind } from './project-files.js';
|
|
22
21
|
|
|
23
22
|
const TASKS = new Set(['start', 'adopt', 'work', 'deslop', 'program', 'blueprint', 'describe', 'review']);
|
|
24
23
|
const DEFAULT_REQUEST = {
|
|
@@ -83,6 +82,26 @@ function programContext(program) {
|
|
|
83
82
|
};
|
|
84
83
|
}
|
|
85
84
|
|
|
85
|
+
function startProgramContext(program) {
|
|
86
|
+
return {
|
|
87
|
+
status: program.status,
|
|
88
|
+
subsystems: program.subsystems,
|
|
89
|
+
moduleCount: program.modules.length,
|
|
90
|
+
...(program.diagnostic ? { diagnostic: program.diagnostic } : {}),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function startStackContext(stack) {
|
|
95
|
+
return {
|
|
96
|
+
components: stack.components.map(({ id, description }) => ({ id, description })),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function startInstructions(instructions, projectKind) {
|
|
101
|
+
const specific = await readInstalledAsset(`start-${projectKind}`);
|
|
102
|
+
return `${instructions.trim()}\n\n${specific.trim()}`;
|
|
103
|
+
}
|
|
104
|
+
|
|
86
105
|
function codeIndexContext(index) {
|
|
87
106
|
return {
|
|
88
107
|
machineCity: {
|
|
@@ -231,21 +250,17 @@ async function generateStartPrompt({
|
|
|
231
250
|
root,
|
|
232
251
|
stackPackages,
|
|
233
252
|
}) {
|
|
234
|
-
const availableEngineeringProfiles = (await listEngineeringProfileCatalog())
|
|
235
|
-
.map(({ id, name, description }) => ({ id, name, description }));
|
|
236
253
|
let blueprint;
|
|
237
254
|
try {
|
|
238
255
|
blueprint = await readBlueprint(root, { required: true });
|
|
239
256
|
} catch (error) {
|
|
240
257
|
if (error?.code !== 'BLUEPRINT_REQUIRED') throw error;
|
|
241
|
-
|
|
242
|
-
const existing = [...files.values()].some((state) => state.exists);
|
|
243
|
-
if (!existing) throw error;
|
|
258
|
+
if (await classifyProjectKind({ projectRoot: root }) === 'new') throw error;
|
|
244
259
|
return {
|
|
245
260
|
status: 'ready',
|
|
246
261
|
task: 'start',
|
|
247
262
|
prompt: renderPrompt({
|
|
248
|
-
instructions,
|
|
263
|
+
instructions: await startInstructions(instructions, 'existing-uninitialized'),
|
|
249
264
|
request,
|
|
250
265
|
context: {
|
|
251
266
|
task: 'start',
|
|
@@ -253,8 +268,6 @@ async function generateStartPrompt({
|
|
|
253
268
|
projectKind: 'existing-uninitialized',
|
|
254
269
|
genesis: { initialized: false },
|
|
255
270
|
engineering: engineeringPromptContext(engineering),
|
|
256
|
-
availableEngineeringProfiles,
|
|
257
|
-
program: programContext(program),
|
|
258
271
|
},
|
|
259
272
|
engineeringGuidance: engineering.guidance,
|
|
260
273
|
}),
|
|
@@ -263,42 +276,51 @@ async function generateStartPrompt({
|
|
|
263
276
|
};
|
|
264
277
|
}
|
|
265
278
|
const stack = await readStack(root, { stackPackages });
|
|
279
|
+
const projectKind = await classifyProjectKind({
|
|
280
|
+
projectRoot: root,
|
|
281
|
+
stackComponents: stack.components,
|
|
282
|
+
});
|
|
266
283
|
const availableStackPackages = [...new Set([...stack.stackPackages, ...stackPackages])];
|
|
267
|
-
const [
|
|
268
|
-
|
|
269
|
-
|
|
284
|
+
const [catalog, availableEngineeringProfiles, projectSkills, renderedInstructions] = await Promise.all([
|
|
285
|
+
projectKind === 'new'
|
|
286
|
+
? listStackCatalogPieces({ projectRoot: root, stackPackages: availableStackPackages })
|
|
287
|
+
: Promise.resolve([]),
|
|
288
|
+
projectKind === 'new'
|
|
289
|
+
? listEngineeringProfileCatalog()
|
|
290
|
+
.then((profiles) => profiles.map(({ id, name, description }) => ({ id, name, description })))
|
|
291
|
+
: Promise.resolve([]),
|
|
292
|
+
inspectProjectSkills({ projectRoot: root, stack }),
|
|
293
|
+
startInstructions(instructions, projectKind),
|
|
270
294
|
]);
|
|
271
|
-
const projectSkills = await inspectProjectSkills({ projectRoot: root, stack });
|
|
272
|
-
const newProject = stack.components.length === 0 && index.fileCount === 0;
|
|
273
295
|
return {
|
|
274
296
|
status: 'ready',
|
|
275
297
|
task: 'start',
|
|
276
298
|
prompt: renderPrompt({
|
|
277
|
-
instructions,
|
|
299
|
+
instructions: renderedInstructions,
|
|
278
300
|
request,
|
|
279
301
|
context: {
|
|
280
302
|
task: 'start',
|
|
281
303
|
projectRoot: root,
|
|
282
|
-
projectKind
|
|
304
|
+
projectKind,
|
|
283
305
|
blueprint: {
|
|
284
306
|
path: blueprint.path,
|
|
285
307
|
description: blueprint.description,
|
|
286
308
|
},
|
|
287
309
|
engineering: engineeringPromptContext(engineering),
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
310
|
+
stack: startStackContext(stack),
|
|
311
|
+
...(projectKind === 'new'
|
|
312
|
+
? {
|
|
313
|
+
availableEngineeringProfiles,
|
|
314
|
+
availableStackPieces: stackCatalogContext(catalog, hiddenStackPieces),
|
|
315
|
+
}
|
|
316
|
+
: { program: startProgramContext(program) }),
|
|
293
317
|
},
|
|
294
318
|
engineeringGuidance: engineering.guidance,
|
|
295
319
|
guidance: stack.guidance,
|
|
296
|
-
skills: renderAgentSkillCatalog(projectSkills.skills),
|
|
297
320
|
}),
|
|
298
321
|
warnings: [
|
|
299
322
|
...(program.diagnostic ? [program.diagnostic] : []),
|
|
300
323
|
...projectSkills.diagnostics,
|
|
301
|
-
...index.diagnostics,
|
|
302
324
|
],
|
|
303
325
|
verificationCommands: [],
|
|
304
326
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readEngineering, readEngineeringBaseline } from './engineering.js';
|
|
2
2
|
import { gitContext } from './git.js';
|
|
3
3
|
import { readStack } from './stack.js';
|
|
4
|
+
import { listStackCatalogPieces } from './stack-catalog.js';
|
|
4
5
|
|
|
5
6
|
async function optionalStack(projectRoot, stackPackages) {
|
|
6
7
|
try { return await readStack(projectRoot, { stackPackages }); } catch { return null; }
|
|
@@ -23,12 +24,23 @@ async function optionalEngineering(projectRoot) {
|
|
|
23
24
|
}
|
|
24
25
|
}
|
|
25
26
|
|
|
27
|
+
async function optionalStackComponentIds(projectRoot, stack, stackPackages) {
|
|
28
|
+
try {
|
|
29
|
+
const packages = [...new Set([...(stack?.stackPackages || []), ...stackPackages])];
|
|
30
|
+
return (await listStackCatalogPieces({ projectRoot, stackPackages: packages }))
|
|
31
|
+
.map(({ id }) => id);
|
|
32
|
+
} catch {
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
26
37
|
export async function projectSessionContext({ projectRoot, stackPackages = [] } = {}) {
|
|
27
38
|
const root = (await gitContext(projectRoot)).repositoryRoot;
|
|
28
39
|
const [stack, engineering] = await Promise.all([
|
|
29
40
|
optionalStack(root, stackPackages),
|
|
30
41
|
optionalEngineering(root),
|
|
31
42
|
]);
|
|
43
|
+
const availableComponents = await optionalStackComponentIds(root, stack, stackPackages);
|
|
32
44
|
const selected = stack?.components.map(({ id }) => id) || [];
|
|
33
45
|
const stackStatus = stack
|
|
34
46
|
? (selected.length > 0 ? selected.join(', ') : 'none')
|
|
@@ -38,10 +50,16 @@ export async function projectSessionContext({ projectRoot, stackPackages = [] }
|
|
|
38
50
|
output: [
|
|
39
51
|
'This is a Genesis-enriched project.',
|
|
40
52
|
'- Read `genesis/blueprint.md`, `genesis/engineering.md`, and `genesis/stack.md` for product intent, engineering approach, and selected technology.',
|
|
53
|
+
'- For a new project whose Blueprint does not yet establish product direction, do not research technology or create source until the user has made clear what is being built, who or what will use or invoke it, and the first observable useful outcome. Ask only unresolved high-impact questions. A reply confirms only what it explicitly answers; Stack confirmation is not product intent.',
|
|
54
|
+
'- Once product direction is clear, choose one smallest implementation path through selected technology guidance, or authoritative technology documentation when the catalog has no match, and read only what that path requires. Do not survey alternatives, clone whole technology repositories, inspect unrelated package internals, or delegate research unless one concrete failure requires one exact investigation.',
|
|
55
|
+
`- Available Stack components: ${availableComponents.length > 0 ? availableComponents.map((id) => `\`${id}\``).join(', ') : 'none'}.`,
|
|
56
|
+
'- When the user names an unselected technology that exactly matches this catalog, run `genesis stack list`, ask whether to add and prepare it, and wait for confirmation. If confirmed, run `genesis stack add <piece...>` and follow its returned preparation prompt in the same task; then use `genesis context` and any installed technology skill. Never infer dependency commands from a component id. If declined or unmatched, continue through authoritative technology documentation without inventing a component.',
|
|
57
|
+
'- Project-owned operation sections in `genesis/stack.md` are the durable application contract. Make the implementation satisfy them or update a complete section to match evidenced reality; Genesis executes only Verification.',
|
|
41
58
|
'- Use the relevant project Agent Skills below `.agents/skills/`.',
|
|
42
59
|
'- 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.',
|
|
43
60
|
'- Program is fallible explanation; code, tests, and runtime behavior remain evidence.',
|
|
44
61
|
'- 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.',
|
|
62
|
+
'- 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.',
|
|
45
63
|
'- Deslop only when explicitly requested. Genesis defines its behavior-preserving committed scope; selected Stack components may add technology-specific cleanup guidance.',
|
|
46
64
|
'- This guidance is loaded for a new session and refreshed after compaction. Continue the active request without restarting completed work.',
|
|
47
65
|
`Engineering profile: ${engineering.profile?.id || 'invalid; run the Genesis `check` operation'}.`,
|
|
@@ -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]));
|