genesis-compiler 1.2.3 → 1.2.4
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 +46 -16
- package/docs/assurance-model.md +1 -1
- package/docs/preview-identity-command.md +113 -0
- package/docs/prompt-integration.md +22 -14
- package/docs/stack-components.md +68 -22
- package/package.json +3 -1
- package/plugins/genesis/.codex-plugin/plugin.json +1 -1
- package/prompts/adopt.txt +62 -0
- package/prompts/start.txt +17 -4
- package/prompts/work.txt +1 -1
- package/skills/genesis-project/SKILL.md +9 -1
- package/src/cli.js +53 -3
- package/src/index/assets.js +1 -0
- package/src/index/codex-hooks.js +3 -2
- package/src/index/prompt.js +58 -1
- package/src/index/stack-catalog.js +1 -0
- package/src/index/stack-environment-defaults.js +2 -0
- package/src/index/stack-launch.js +5 -6
- package/src/index/stack-piece.js +28 -11
- package/src/index/stack.js +39 -2
- package/src/index.js +2 -2
- package/stacks/pieces/cpp.md +12 -0
- package/stacks/pieces/jskit-mysql.md +10 -0
- package/stacks/pieces/jskit-postgresql.md +10 -0
- package/stacks/pieces/jskit.md +23 -0
- package/stacks/pieces/mysql.md +11 -0
- package/stacks/pieces/nodejs.md +11 -0
- package/stacks/pieces/postgresql.md +12 -0
- package/stacks/pieces/vue.md +9 -0
package/prompts/work.txt
CHANGED
|
@@ -10,7 +10,7 @@ and reuse an existing function when it already owns the behavior.
|
|
|
10
10
|
This is the implementation turn. Do not edit the Blueprint, Program, or
|
|
11
11
|
`.genesis/`; the separate reconciliation turn owns explanatory changes. Keep an
|
|
12
12
|
exact Stack `## Launch` declaration aligned when the implementation establishes
|
|
13
|
-
or intentionally changes how the project is started or how
|
|
13
|
+
or intentionally changes how the project is started or how a preview host may request
|
|
14
14
|
an application preview identity. Declare only command paths, protocol, supported
|
|
15
15
|
identity types, environment variable names, runtimes, and timeout policy; never
|
|
16
16
|
put an environment value or secret in Stack. Never invent missing external-resource
|
|
@@ -21,6 +21,14 @@ codebase, tests, Git review, or the coding agent.
|
|
|
21
21
|
6. Load applicable technology skills from `.agents/skills/` or from the
|
|
22
22
|
agent's own installed skill catalog.
|
|
23
23
|
|
|
24
|
+
For an existing application's first Stack selection, inspect its real setup,
|
|
25
|
+
build, and launch commands before relying on component defaults. A selected
|
|
26
|
+
component describes its current foundation; it does not silently port older
|
|
27
|
+
source. When the existing commands differ, keep the implementation unchanged
|
|
28
|
+
and declare exact project-owned `## Workspace setup` and `## Launch` overrides.
|
|
29
|
+
Do not claim the inherited component recipe is usable until it matches the
|
|
30
|
+
source.
|
|
31
|
+
|
|
24
32
|
Program is concise, fallible explanation. Its Sources and optional
|
|
25
33
|
Implementation maps aid navigation but never substitute for reading code,
|
|
26
34
|
tests, and runtime behavior.
|
|
@@ -36,7 +44,7 @@ Neither is authority or proof; both may be regenerated with `genesis index`.
|
|
|
36
44
|
- Do not edit the Blueprint, Program, or `.genesis/` during implementation; the
|
|
37
45
|
separate reconciliation turn owns explanatory updates. Keep an exact Stack
|
|
38
46
|
`## Launch` declaration aligned when the implementation establishes or
|
|
39
|
-
intentionally changes how the project is started or how
|
|
47
|
+
intentionally changes how the project is started or how a preview host may request
|
|
40
48
|
an application preview identity. Record only capability metadata and
|
|
41
49
|
environment variable names there, never environment values or secrets.
|
|
42
50
|
- Follow established project and technology seams instead of creating parallel
|
package/src/cli.js
CHANGED
|
@@ -9,6 +9,10 @@ import {
|
|
|
9
9
|
getContext,
|
|
10
10
|
indexCodebase,
|
|
11
11
|
initialize,
|
|
12
|
+
inspectDeployment,
|
|
13
|
+
inspectEnvironment,
|
|
14
|
+
inspectLaunch,
|
|
15
|
+
inspectWorkspaceSetup,
|
|
12
16
|
installCodex,
|
|
13
17
|
listStackPieces,
|
|
14
18
|
prepareWorkspace,
|
|
@@ -31,9 +35,10 @@ const USAGE = `Usage:
|
|
|
31
35
|
genesis stack add <piece...>
|
|
32
36
|
genesis context <path...>
|
|
33
37
|
genesis index [function-or-path...]
|
|
38
|
+
genesis inspect <workspace|environment|launch|deployment>
|
|
34
39
|
genesis prepare
|
|
35
40
|
genesis prompt [request...]
|
|
36
|
-
genesis prompt --task <start|work|deslop|program|blueprint|describe|review> [request...]
|
|
41
|
+
genesis prompt --task <start|adopt|work|deslop|program|blueprint|describe|review> [request...]
|
|
37
42
|
genesis verify
|
|
38
43
|
genesis check
|
|
39
44
|
|
|
@@ -48,7 +53,7 @@ prompt to the agent you already use. Review all edits through the ordinary Git
|
|
|
48
53
|
diff, then run genesis verify for the Stack's concrete checks.
|
|
49
54
|
`;
|
|
50
55
|
|
|
51
|
-
const COMMANDS = new Set(['adopt', 'check', 'codex', 'context', 'hook', 'index', 'init', 'prepare', 'prompt', 'stack', 'verify']);
|
|
56
|
+
const COMMANDS = new Set(['adopt', 'check', 'codex', 'context', 'hook', 'index', 'init', 'inspect', 'prepare', 'prompt', 'stack', 'verify']);
|
|
52
57
|
|
|
53
58
|
function parseCommand(argv) {
|
|
54
59
|
if (argv.length === 0 || argv.includes('--help') || argv.includes('-h') || argv[0] === 'help') {
|
|
@@ -96,9 +101,16 @@ function parseCommand(argv) {
|
|
|
96
101
|
}
|
|
97
102
|
} else if (command === 'context' && operands.length === 0) {
|
|
98
103
|
fail('CONTEXT_PATH_REQUIRED', 'Command context requires at least one project path.');
|
|
104
|
+
} else if (command === 'inspect') {
|
|
105
|
+
if (operands.length !== 1 || !['workspace', 'environment', 'launch', 'deployment'].includes(operands[0])) {
|
|
106
|
+
fail(
|
|
107
|
+
'CLI_INSPECT_TARGET_REQUIRED',
|
|
108
|
+
'Command inspect requires exactly one of: workspace, environment, launch, deployment.',
|
|
109
|
+
);
|
|
110
|
+
}
|
|
99
111
|
} else if (command === 'hook' && (operands.length !== 1 || !['begin', 'discover', 'end', 'session', 'stop'].includes(operands[0]))) {
|
|
100
112
|
fail('CLI_HOOK_ACTION_REQUIRED', 'Command hook requires exactly one of: discover, session, begin, stop, end.');
|
|
101
|
-
} else if (!['adopt', 'context', 'hook', 'index', 'prompt'].includes(command) && operands.length > 0) {
|
|
113
|
+
} else if (!['adopt', 'context', 'hook', 'index', 'inspect', 'prompt'].includes(command) && operands.length > 0) {
|
|
102
114
|
fail('CLI_EXTRA_ARGUMENT', `Command ${command} accepts no arguments.`);
|
|
103
115
|
}
|
|
104
116
|
return { command, operands, options };
|
|
@@ -128,6 +140,31 @@ function writeCheck(result) {
|
|
|
128
140
|
line(process.stdout, `Check: ${result.status}`);
|
|
129
141
|
}
|
|
130
142
|
|
|
143
|
+
function writeInspection(result) {
|
|
144
|
+
const title = result.inspection[0].toUpperCase() + result.inspection.slice(1);
|
|
145
|
+
line(process.stdout, `${title}: ${result.status}`);
|
|
146
|
+
namedItems('Components', result.components);
|
|
147
|
+
namedItems('Runtimes', result.runtimeRequirements);
|
|
148
|
+
if (result.inspection === 'workspace') {
|
|
149
|
+
namedItems('Preparation steps', result.steps.map(({ label, argv, workdir }) => (
|
|
150
|
+
`${label} in ${workdir}: ${argv.join(' ')}`
|
|
151
|
+
)));
|
|
152
|
+
} else if (result.inspection === 'environment') {
|
|
153
|
+
namedItems('Public defaults', result.environmentDefaults.map(({ name, value }) => `${name}=${value}`));
|
|
154
|
+
namedItems('Environment files', result.files.map(({ format, path: filePath }) => `${format}: ${filePath}`));
|
|
155
|
+
} else if (result.inspection === 'launch') {
|
|
156
|
+
namedItems('Targets', result.targets.map(({ id, label, urlPath, steps }) => (
|
|
157
|
+
`${id} (${label}) ${urlPath}: ${steps.map(({ argv }) => argv.join(' ')).join(' -> ')}`
|
|
158
|
+
)));
|
|
159
|
+
} else {
|
|
160
|
+
namedItems('Deployment steps', result.steps.map(({ label, argv }) => `${label}: ${argv.join(' ')}`));
|
|
161
|
+
}
|
|
162
|
+
namedItems('Resources', result.resources.map(({ component, resource }) => (
|
|
163
|
+
`${resource.id} (${resource.kind}, ${component})`
|
|
164
|
+
)));
|
|
165
|
+
for (const item of result.diagnostics || []) line(process.stdout, `${item.code}: ${item.message}`);
|
|
166
|
+
}
|
|
167
|
+
|
|
131
168
|
function writeResult(command, result) {
|
|
132
169
|
if (command === 'prompt') {
|
|
133
170
|
process.stdout.write(result.prompt.endsWith('\n') ? result.prompt : `${result.prompt}\n`);
|
|
@@ -167,6 +204,10 @@ function writeResult(command, result) {
|
|
|
167
204
|
line(process.stdout, `index: ${result.status}`);
|
|
168
205
|
return;
|
|
169
206
|
}
|
|
207
|
+
if (command === 'inspect') {
|
|
208
|
+
writeInspection(result);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
170
211
|
if (command === 'prepare') {
|
|
171
212
|
if (result.summary) line(process.stdout, result.summary);
|
|
172
213
|
namedItems('Commands', result.commands?.map(({ label, argv }) => `${label}: ${argv.join(' ')}`));
|
|
@@ -241,6 +282,15 @@ async function execute({ command, operands, options }) {
|
|
|
241
282
|
}
|
|
242
283
|
if (command === 'context') return getContext({ paths: operands, projectRoot });
|
|
243
284
|
if (command === 'index') return indexCodebase({ projectRoot, queries: operands });
|
|
285
|
+
if (command === 'inspect') {
|
|
286
|
+
const inspections = {
|
|
287
|
+
workspace: inspectWorkspaceSetup,
|
|
288
|
+
environment: inspectEnvironment,
|
|
289
|
+
launch: inspectLaunch,
|
|
290
|
+
deployment: inspectDeployment,
|
|
291
|
+
};
|
|
292
|
+
return { inspection: operands[0], ...await inspections[operands[0]]({ projectRoot }) };
|
|
293
|
+
}
|
|
244
294
|
if (command === 'prepare') {
|
|
245
295
|
return prepareWorkspace({
|
|
246
296
|
projectRoot,
|
package/src/index/assets.js
CHANGED
|
@@ -3,6 +3,7 @@ import { readFile } from 'node:fs/promises';
|
|
|
3
3
|
const root = new URL('../../', import.meta.url);
|
|
4
4
|
const assets = {
|
|
5
5
|
start: new URL('prompts/start.txt', root),
|
|
6
|
+
adopt: new URL('prompts/adopt.txt', root),
|
|
6
7
|
work: new URL('prompts/work.txt', root),
|
|
7
8
|
describe: new URL('prompts/describe.txt', root),
|
|
8
9
|
reconcile: new URL('prompts/reconcile.txt', root),
|
package/src/index/codex-hooks.js
CHANGED
|
@@ -156,8 +156,9 @@ export async function codexAdoptionRecommendation({ projectRoot = process.cwd()
|
|
|
156
156
|
output: [
|
|
157
157
|
'IMPORTANT GENESIS RECOMMENDATION:',
|
|
158
158
|
'This existing project has no Genesis explanatory layer.',
|
|
159
|
-
'In your first response, strongly recommend
|
|
160
|
-
'
|
|
159
|
+
'In your first response, strongly recommend preparing this existing project for guided editing before substantial work.',
|
|
160
|
+
'Explain in product language that preparation preserves the implementation while importing its current operational and product context. Do not require the user to know Genesis terminology or type a command.',
|
|
161
|
+
'If the user approves, run `genesis adopt` yourself and follow the returned adoption prompt.',
|
|
161
162
|
'Do not run it without the user\'s approval. If they agree, run it and follow the returned prompt in this conversation.',
|
|
162
163
|
'If the user declines, continue normally and do not repeat the recommendation in this session.',
|
|
163
164
|
].join('\n'),
|
package/src/index/prompt.js
CHANGED
|
@@ -14,9 +14,10 @@ import { stableJson } from './utils.js';
|
|
|
14
14
|
import { gitVisibleFileStates } from './project-files.js';
|
|
15
15
|
import { isProjectContentPath } from './paths.js';
|
|
16
16
|
|
|
17
|
-
const TASKS = new Set(['start', 'work', 'deslop', 'program', 'blueprint', 'describe', 'review']);
|
|
17
|
+
const TASKS = new Set(['start', 'adopt', 'work', 'deslop', 'program', 'blueprint', 'describe', 'review']);
|
|
18
18
|
const DEFAULT_REQUEST = {
|
|
19
19
|
start: 'Start a conversation about this project.',
|
|
20
|
+
adopt: 'Import the existing project into a truthful Genesis project contract.',
|
|
20
21
|
work: 'Implement the product intent expressed by the current Blueprint.',
|
|
21
22
|
deslop: 'Simplify the current Git-visible work without making unrelated changes.',
|
|
22
23
|
program: 'Refresh the complete useful Program for the code that exists now.',
|
|
@@ -97,6 +98,7 @@ function renderPrompt({
|
|
|
97
98
|
skills = '',
|
|
98
99
|
guidance = '',
|
|
99
100
|
cleanup = '',
|
|
101
|
+
adoption = '',
|
|
100
102
|
}) {
|
|
101
103
|
return [
|
|
102
104
|
instructions.trim(),
|
|
@@ -111,12 +113,58 @@ function renderPrompt({
|
|
|
111
113
|
stableJson(context).trimEnd(),
|
|
112
114
|
'```',
|
|
113
115
|
...(guidance ? ['', 'SELECTED STACK GUIDANCE', '', guidance] : []),
|
|
116
|
+
...(adoption ? ['', 'SELECTED STACK ADOPTION GUIDANCE', '', adoption] : []),
|
|
114
117
|
...(skills ? ['', 'AVAILABLE AGENT SKILLS', '', skills] : []),
|
|
115
118
|
...(cleanup ? ['', 'SELECTED STACK CLEANUP GUIDANCE', '', cleanup] : []),
|
|
116
119
|
'',
|
|
117
120
|
].join('\n');
|
|
118
121
|
}
|
|
119
122
|
|
|
123
|
+
async function generateAdoptionPrompt({ environment, instructions, program, request, root }) {
|
|
124
|
+
const blueprint = await readBlueprint(root, { required: true });
|
|
125
|
+
const [stack, index, catalog] = await Promise.all([
|
|
126
|
+
readStack(root),
|
|
127
|
+
buildProjectIndex({ projectRoot: root, write: false }),
|
|
128
|
+
listBuiltinStackPieces(),
|
|
129
|
+
]);
|
|
130
|
+
const projectSkills = await inspectProjectSkills({ projectRoot: root, stack });
|
|
131
|
+
const missing = missingStackResources({
|
|
132
|
+
environment: withStackEnvironmentDefaults(environment, stack.environmentDefaults),
|
|
133
|
+
resources: stack.resources,
|
|
134
|
+
});
|
|
135
|
+
return {
|
|
136
|
+
status: 'ready',
|
|
137
|
+
task: 'adopt',
|
|
138
|
+
prompt: renderPrompt({
|
|
139
|
+
instructions,
|
|
140
|
+
request,
|
|
141
|
+
context: {
|
|
142
|
+
task: 'adopt',
|
|
143
|
+
projectRoot: root,
|
|
144
|
+
projectKind: 'existing',
|
|
145
|
+
blueprint: { path: blueprint.path, source: blueprint.source },
|
|
146
|
+
stack: stackPromptContext(stack),
|
|
147
|
+
availableStackPieces: stackCatalogContext(catalog),
|
|
148
|
+
program: programContext(program),
|
|
149
|
+
resourceInputs: missing.length > 0
|
|
150
|
+
? { status: 'missing', diagnostics: missing }
|
|
151
|
+
: { status: 'present' },
|
|
152
|
+
codeIndex: codeIndexContext(index),
|
|
153
|
+
},
|
|
154
|
+
guidance: stack.guidance,
|
|
155
|
+
adoption: stack.adoption,
|
|
156
|
+
skills: renderAgentSkillCatalog(projectSkills.skills),
|
|
157
|
+
}),
|
|
158
|
+
warnings: [
|
|
159
|
+
...(program.diagnostic ? [program.diagnostic] : []),
|
|
160
|
+
...missing,
|
|
161
|
+
...projectSkills.diagnostics,
|
|
162
|
+
...index.diagnostics,
|
|
163
|
+
],
|
|
164
|
+
verificationCommands: [],
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
120
168
|
async function generateExplanationPrompt({ instructions, program, request, root, task }) {
|
|
121
169
|
const blueprint = await readBlueprint(root, {
|
|
122
170
|
required: true,
|
|
@@ -268,6 +316,15 @@ export async function generateProjectPrompt({
|
|
|
268
316
|
root,
|
|
269
317
|
});
|
|
270
318
|
}
|
|
319
|
+
if (task === 'adopt') {
|
|
320
|
+
return generateAdoptionPrompt({
|
|
321
|
+
environment,
|
|
322
|
+
instructions,
|
|
323
|
+
program,
|
|
324
|
+
request: userRequest,
|
|
325
|
+
root,
|
|
326
|
+
});
|
|
327
|
+
}
|
|
271
328
|
let stack;
|
|
272
329
|
if (task === 'work') {
|
|
273
330
|
try {
|
|
@@ -14,6 +14,8 @@ export function parseStackEnvironmentDefaultLines(lines, {
|
|
|
14
14
|
path: defaultsPath = 'stack environment defaults',
|
|
15
15
|
} = {}) {
|
|
16
16
|
if (lines === undefined) return [];
|
|
17
|
+
const present = lines.map((line) => line.trim()).filter(Boolean);
|
|
18
|
+
if (present.length === 1 && present[0] === '- Nothing.') return [];
|
|
17
19
|
const defaults = [];
|
|
18
20
|
for (let index = 0; index < lines.length; index += 1) {
|
|
19
21
|
const source = lines[index].trim();
|
|
@@ -7,9 +7,8 @@ import {
|
|
|
7
7
|
|
|
8
8
|
const ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
|
|
9
9
|
const PLACEHOLDER = /\{([a-z][a-z0-9-]*)\}/gu;
|
|
10
|
-
const PREVIEW_IDENTITY_COMMAND_PATH = /^\.vibe64\/bin\/[A-Za-z0-9][A-Za-z0-9._-]*$/u;
|
|
11
10
|
const PREVIEW_IDENTITY_ENVIRONMENT_NAME = /^[A-Z_][A-Z0-9_]*$/u;
|
|
12
|
-
const PREVIEW_IDENTITY_PROTOCOL = '
|
|
11
|
+
const PREVIEW_IDENTITY_PROTOCOL = 'genesis.preview-identity.command.v1';
|
|
13
12
|
const PREVIEW_IDENTITY_RESERVED_ENVIRONMENT_NAMES = new Set([
|
|
14
13
|
'HOME',
|
|
15
14
|
'LOGNAME',
|
|
@@ -17,8 +16,6 @@ const PREVIEW_IDENTITY_RESERVED_ENVIRONMENT_NAMES = new Set([
|
|
|
17
16
|
'PATH',
|
|
18
17
|
'TMPDIR',
|
|
19
18
|
'USER',
|
|
20
|
-
'VIBE64_PREVIEW_IDENTITY_ENABLED',
|
|
21
|
-
'VIBE64_PREVIEW_IDENTITY_SECRET',
|
|
22
19
|
]);
|
|
23
20
|
const PREVIEW_IDENTITY_TYPES = new Set(['email', 'login', 'user-id']);
|
|
24
21
|
const PREVIEW_FIELDS = [
|
|
@@ -291,12 +288,14 @@ function normalizePreviewIdentity(draft, launchPath, targetId) {
|
|
|
291
288
|
if (
|
|
292
289
|
command.length > 64
|
|
293
290
|
|| command.some((entry) => !entry.trim() || entry.length > 4096 || /[\r\n]/u.test(entry))
|
|
294
|
-
|| !
|
|
291
|
+
|| !command[0].includes('/')
|
|
292
|
+
|| command[0].includes('\\')
|
|
293
|
+
|| command[0].split('/').some((part) => !part || ['.', '..'].includes(part))
|
|
295
294
|
|| command.some((entry) => /\{(?:host|port)\}/u.test(entry))
|
|
296
295
|
) {
|
|
297
296
|
invalid(
|
|
298
297
|
launchPath,
|
|
299
|
-
'Preview identity command must use
|
|
298
|
+
'Preview identity command must use a committed app-owned project-relative executable and contain at most 64 literal arguments.',
|
|
300
299
|
draft.line,
|
|
301
300
|
);
|
|
302
301
|
}
|
package/src/index/stack-piece.js
CHANGED
|
@@ -10,7 +10,7 @@ import { normalizeSource } from './utils.js';
|
|
|
10
10
|
|
|
11
11
|
const STACK_PIECE_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
|
|
12
12
|
const STACK_CUSTOMIZATION_TITLE = /^# Stack customization: ([a-z0-9]+(?:-[a-z0-9]+)*)\s*$/u;
|
|
13
|
-
const STACK_CUSTOMIZATION_FIELDS = new Set(['Description', 'Guidance', 'Deslop']);
|
|
13
|
+
const STACK_CUSTOMIZATION_FIELDS = new Set(['Description', 'Guidance', 'Adoption', 'Deslop']);
|
|
14
14
|
|
|
15
15
|
export function normalizeStackPieceId(value, {
|
|
16
16
|
code = 'STACK_PIECE_INVALID',
|
|
@@ -93,12 +93,12 @@ function customizationSections(source, piecePath) {
|
|
|
93
93
|
if (/^#{1,3}\s+/u.test(line)) invalid(piecePath, `Unknown customization heading: ${line.trim()}.`);
|
|
94
94
|
if (field) content.push(line);
|
|
95
95
|
else if (line.trim()) {
|
|
96
|
-
invalid(piecePath, 'Customization content must be below Description, Guidance, or Deslop.');
|
|
96
|
+
invalid(piecePath, 'Customization content must be below Description, Guidance, Adoption, or Deslop.');
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
99
|
finishField();
|
|
100
100
|
if (Object.values(result).every((values) => Object.keys(values).length === 0)) {
|
|
101
|
-
invalid(piecePath, 'Stack customization must add or override Description, Guidance, or Deslop.');
|
|
101
|
+
invalid(piecePath, 'Stack customization must add or override Description, Guidance, Adoption, or Deslop.');
|
|
102
102
|
}
|
|
103
103
|
return result;
|
|
104
104
|
}
|
|
@@ -151,18 +151,22 @@ function skill(all, piecePath) {
|
|
|
151
151
|
};
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
-
function
|
|
155
|
-
|
|
154
|
+
export function parseStackResourceLines(lines, {
|
|
155
|
+
path: resourcePath = 'stack resources',
|
|
156
|
+
} = {}) {
|
|
157
|
+
if (lines === undefined) return [];
|
|
158
|
+
const source = lines.join('\n').trim();
|
|
156
159
|
if (!source) return [];
|
|
160
|
+
if (source === '- Nothing.') return [];
|
|
157
161
|
const blocks = [...source.matchAll(/```json genesis-resource\s*\n([\s\S]*?)\n```/gu)];
|
|
158
162
|
const outside = source.replace(/```json genesis-resource\s*\n[\s\S]*?\n```/gu, '').trim();
|
|
159
163
|
if (outside || blocks.length === 0) {
|
|
160
|
-
invalid(
|
|
164
|
+
invalid(resourcePath, '## Resources may contain only `json genesis-resource` fenced objects.');
|
|
161
165
|
}
|
|
162
166
|
const parsed = blocks.map((match) => {
|
|
163
167
|
let resource;
|
|
164
168
|
try { resource = JSON.parse(match[1]); } catch (error) {
|
|
165
|
-
invalid(
|
|
169
|
+
invalid(resourcePath, `A Stack resource is invalid JSON: ${error.message}`);
|
|
166
170
|
}
|
|
167
171
|
if (
|
|
168
172
|
!resource
|
|
@@ -173,7 +177,7 @@ function resources(all, piecePath) {
|
|
|
173
177
|
|| !Array.isArray(resource.environmentAlternatives)
|
|
174
178
|
|| resource.environmentAlternatives.length === 0
|
|
175
179
|
) {
|
|
176
|
-
invalid(
|
|
180
|
+
invalid(resourcePath, 'A Stack resource needs an id, kind, and at least one environment alternative.');
|
|
177
181
|
}
|
|
178
182
|
const environmentAlternatives = resource.environmentAlternatives.map((alternative) => {
|
|
179
183
|
const required = alternative?.required;
|
|
@@ -182,7 +186,7 @@ function resources(all, piecePath) {
|
|
|
182
186
|
!Array.isArray(required)
|
|
183
187
|
|| !Array.isArray(allowEmpty)
|
|
184
188
|
) {
|
|
185
|
-
invalid(
|
|
189
|
+
invalid(resourcePath, `Stack resource ${resource.id} has an invalid environment alternative.`);
|
|
186
190
|
}
|
|
187
191
|
const names = [...required, ...allowEmpty];
|
|
188
192
|
if (
|
|
@@ -191,17 +195,23 @@ function resources(all, piecePath) {
|
|
|
191
195
|
|| allowEmpty.some((name) => !required.includes(name))
|
|
192
196
|
|| new Set(required).size !== required.length
|
|
193
197
|
|| new Set(allowEmpty).size !== allowEmpty.length
|
|
194
|
-
) invalid(
|
|
198
|
+
) invalid(resourcePath, `Stack resource ${resource.id} has an invalid environment alternative.`);
|
|
195
199
|
return { required: [...required], allowEmpty: [...allowEmpty] };
|
|
196
200
|
});
|
|
197
201
|
return { id: resource.id, kind: resource.kind, environmentAlternatives };
|
|
198
202
|
});
|
|
199
203
|
if (new Set(parsed.map(({ id }) => id)).size !== parsed.length) {
|
|
200
|
-
invalid(
|
|
204
|
+
invalid(resourcePath, '## Resources contains a duplicate resource id.');
|
|
201
205
|
}
|
|
202
206
|
return parsed;
|
|
203
207
|
}
|
|
204
208
|
|
|
209
|
+
function resources(all, piecePath) {
|
|
210
|
+
return parseStackResourceLines(all.has('Resources') ? all.get('Resources') : undefined, {
|
|
211
|
+
path: piecePath,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
205
215
|
/**
|
|
206
216
|
* Stack pieces are guidance, not a second programming language. Genesis reads
|
|
207
217
|
* only the few sections it uses and deliberately ignores explanatory sections.
|
|
@@ -218,6 +228,7 @@ export function parseStackPieceSource(value, {
|
|
|
218
228
|
const all = sections(source);
|
|
219
229
|
const description = textSection(all, 'Description');
|
|
220
230
|
const guidance = textSection(all, 'Guidance');
|
|
231
|
+
const adoption = textSection(all, 'Adoption');
|
|
221
232
|
const deslop = textSection(all, 'Deslop');
|
|
222
233
|
if (!description) invalid(piecePath, 'Stack piece needs Description.');
|
|
223
234
|
const commands = parseStackCommandLines(all.get('Commands') || [], { path: piecePath });
|
|
@@ -248,6 +259,7 @@ export function parseStackPieceSource(value, {
|
|
|
248
259
|
id,
|
|
249
260
|
description,
|
|
250
261
|
guidance,
|
|
262
|
+
adoption,
|
|
251
263
|
requires: idList(all, 'Requires', piecePath, { required: true }),
|
|
252
264
|
conflicts: idList(all, 'Conflicts', piecePath),
|
|
253
265
|
indexers: idList(all, 'Indexers', piecePath),
|
|
@@ -299,6 +311,11 @@ export function applyStackPieceCustomization(piece, customization) {
|
|
|
299
311
|
customization.override.guidance,
|
|
300
312
|
customization.add.guidance,
|
|
301
313
|
),
|
|
314
|
+
adoption: customizedText(
|
|
315
|
+
piece.adoption,
|
|
316
|
+
customization.override.adoption,
|
|
317
|
+
customization.add.adoption,
|
|
318
|
+
),
|
|
302
319
|
deslop: customizedText(piece.deslop, customization.override.deslop, customization.add.deslop),
|
|
303
320
|
};
|
|
304
321
|
}
|
package/src/index/stack.js
CHANGED
|
@@ -10,6 +10,7 @@ import { composeStackDeployment, parseStackDeploymentLines } from './stack-deplo
|
|
|
10
10
|
import { resolveStackPieces } from './stack-composition.js';
|
|
11
11
|
import {
|
|
12
12
|
composeStackEnvironmentDefaults,
|
|
13
|
+
parseStackEnvironmentDefaultLines,
|
|
13
14
|
} from './stack-environment-defaults.js';
|
|
14
15
|
import {
|
|
15
16
|
composeStackEnvironmentFiles,
|
|
@@ -24,6 +25,7 @@ import {
|
|
|
24
25
|
applyStackPieceCustomization,
|
|
25
26
|
normalizeStackPieceId,
|
|
26
27
|
parseStackPieceCustomizationSource,
|
|
28
|
+
parseStackResourceLines,
|
|
27
29
|
} from './stack-piece.js';
|
|
28
30
|
import { STACK_PATH } from './paths.js';
|
|
29
31
|
import { sha256, stableJson, writeFileAtomic } from './utils.js';
|
|
@@ -32,6 +34,8 @@ const COMPONENT_LINE = /^- `([a-z0-9]+(?:-[a-z0-9]+)*)`$/u;
|
|
|
32
34
|
export const EMPTY_STACK_SOURCE = '# Stack\n\n## Components\n';
|
|
33
35
|
const STACK_SECTIONS = new Set([
|
|
34
36
|
'Components',
|
|
37
|
+
'Resources',
|
|
38
|
+
'Environment defaults',
|
|
35
39
|
'Environment files',
|
|
36
40
|
'Workspace setup',
|
|
37
41
|
'Commands',
|
|
@@ -104,8 +108,10 @@ function renderStack({
|
|
|
104
108
|
commandLines = [],
|
|
105
109
|
componentIds: componentIdsValue,
|
|
106
110
|
deploymentLines = null,
|
|
111
|
+
environmentDefaultLines = null,
|
|
107
112
|
environmentFileLines = null,
|
|
108
113
|
launchLines = null,
|
|
114
|
+
resourceLines = null,
|
|
109
115
|
workspaceSetupLines = null,
|
|
110
116
|
}) {
|
|
111
117
|
return [
|
|
@@ -113,6 +119,12 @@ function renderStack({
|
|
|
113
119
|
'',
|
|
114
120
|
'## Components',
|
|
115
121
|
...componentIdsValue.map((id) => `- \`${id}\``),
|
|
122
|
+
...(resourceLines === null
|
|
123
|
+
? []
|
|
124
|
+
: ['', '## Resources', '', ...withoutOuterBlankLines(resourceLines)]),
|
|
125
|
+
...(environmentDefaultLines === null
|
|
126
|
+
? []
|
|
127
|
+
: ['', '## Environment defaults', '', ...withoutOuterBlankLines(environmentDefaultLines)]),
|
|
116
128
|
...(environmentFileLines === null
|
|
117
129
|
? []
|
|
118
130
|
: ['', '## Environment files', '', ...withoutOuterBlankLines(environmentFileLines)]),
|
|
@@ -153,6 +165,15 @@ export async function addStackPieces({ pieces, projectRoot }) {
|
|
|
153
165
|
)).map(({ id }) => id);
|
|
154
166
|
const commandLines = parseStackCommandLines(sections.get('Commands') || [], { path: STACK_PATH })
|
|
155
167
|
.map(({ label, argv }) => `- Verify \`${label}\`: ${argv.map((value) => `\`${value}\``).join(' ')}`);
|
|
168
|
+
const resourceLines = sections.has('Resources') ? sections.get('Resources') : null;
|
|
169
|
+
parseStackResourceLines(resourceLines === null ? undefined : resourceLines, { path: STACK_PATH });
|
|
170
|
+
const environmentDefaultLines = sections.has('Environment defaults')
|
|
171
|
+
? sections.get('Environment defaults')
|
|
172
|
+
: null;
|
|
173
|
+
parseStackEnvironmentDefaultLines(
|
|
174
|
+
environmentDefaultLines === null ? undefined : environmentDefaultLines,
|
|
175
|
+
{ path: STACK_PATH },
|
|
176
|
+
);
|
|
156
177
|
const environmentFileLines = sections.has('Environment files')
|
|
157
178
|
? sections.get('Environment files')
|
|
158
179
|
: null;
|
|
@@ -178,8 +199,10 @@ export async function addStackPieces({ pieces, projectRoot }) {
|
|
|
178
199
|
commandLines,
|
|
179
200
|
componentIds: selected,
|
|
180
201
|
deploymentLines,
|
|
202
|
+
environmentDefaultLines,
|
|
181
203
|
environmentFileLines,
|
|
182
204
|
launchLines,
|
|
205
|
+
resourceLines,
|
|
183
206
|
workspaceSetupLines,
|
|
184
207
|
});
|
|
185
208
|
const stackChanged = rendered !== source;
|
|
@@ -266,8 +289,19 @@ export async function readStack(projectRoot) {
|
|
|
266
289
|
const componentCommands = components.flatMap((piece) => piece.commands);
|
|
267
290
|
const commands = distinctCommands(projectCommands.length > 0 ? projectCommands : componentCommands);
|
|
268
291
|
const cityPresentation = composeStackCityPresentation(components);
|
|
269
|
-
const
|
|
270
|
-
|
|
292
|
+
const projectResources = sections.has('Resources')
|
|
293
|
+
? parseStackResourceLines(sections.get('Resources'), { path: STACK_PATH })
|
|
294
|
+
: null;
|
|
295
|
+
const resources = projectResources === null
|
|
296
|
+
? resourceDeclarations(components)
|
|
297
|
+
: projectResources.map((resource) => ({ component: 'project', resource }));
|
|
298
|
+
const projectEnvironmentDefaults = parseStackEnvironmentDefaultLines(
|
|
299
|
+
sections.has('Environment defaults') ? sections.get('Environment defaults') : undefined,
|
|
300
|
+
{ path: STACK_PATH },
|
|
301
|
+
);
|
|
302
|
+
const environmentDefaults = sections.has('Environment defaults')
|
|
303
|
+
? projectEnvironmentDefaults.map((entry) => ({ ...entry, sources: ['project'] }))
|
|
304
|
+
: composeStackEnvironmentDefaults(components);
|
|
271
305
|
const projectEnvironmentFiles = parseStackEnvironmentFileLines(
|
|
272
306
|
sections.has('Environment files') ? sections.get('Environment files') : undefined,
|
|
273
307
|
{ path: STACK_PATH },
|
|
@@ -313,6 +347,7 @@ export async function readStack(projectRoot) {
|
|
|
313
347
|
workspaceSetup,
|
|
314
348
|
resources,
|
|
315
349
|
guidance: composedProse(components, 'guidance'),
|
|
350
|
+
adoption: composedProse(components, 'adoption'),
|
|
316
351
|
deslop: composedProse(components, 'deslop'),
|
|
317
352
|
};
|
|
318
353
|
}
|
|
@@ -323,12 +358,14 @@ export function stackPromptContext(stack) {
|
|
|
323
358
|
id: piece.id,
|
|
324
359
|
description: piece.description,
|
|
325
360
|
...(piece.guidance ? { guidance: piece.guidance } : {}),
|
|
361
|
+
...(piece.adoption ? { adoption: piece.adoption } : {}),
|
|
326
362
|
requires: piece.requires,
|
|
327
363
|
})),
|
|
328
364
|
cityExclusions: stack.cityExclusions,
|
|
329
365
|
cityRegions: stack.cityRegions,
|
|
330
366
|
verifyCommands: stack.commands.map(({ label, argv }) => ({ label, argv })),
|
|
331
367
|
environmentDefaults: stack.environmentDefaults,
|
|
368
|
+
resources: stack.resources,
|
|
332
369
|
environmentFiles: stack.environmentFiles,
|
|
333
370
|
launchTargets: stack.launchTargets,
|
|
334
371
|
deployment: stack.deployment,
|
package/src/index.js
CHANGED
|
@@ -44,7 +44,7 @@ export async function adoptProject({ projectRoot = process.cwd(), request = '' }
|
|
|
44
44
|
const description = await generateProjectPrompt({
|
|
45
45
|
projectRoot,
|
|
46
46
|
request,
|
|
47
|
-
task: '
|
|
47
|
+
task: 'adopt',
|
|
48
48
|
});
|
|
49
49
|
return {
|
|
50
50
|
status: 'ready',
|
|
@@ -54,7 +54,7 @@ export async function adoptProject({ projectRoot = process.cwd(), request = '' }
|
|
|
54
54
|
changedFiles: initialized.changedFiles,
|
|
55
55
|
prompt: description.prompt,
|
|
56
56
|
warnings: description.warnings,
|
|
57
|
-
guidance: 'Follow the generated
|
|
57
|
+
guidance: 'Follow the generated adoption prompt now. Genesis does not start another agent.',
|
|
58
58
|
};
|
|
59
59
|
}
|
|
60
60
|
|
package/stacks/pieces/cpp.md
CHANGED
|
@@ -12,6 +12,18 @@ C and C++ translation-unit, header, callable, ownership, build, and test convent
|
|
|
12
12
|
|
|
13
13
|
- `cpp`
|
|
14
14
|
|
|
15
|
+
## Adoption
|
|
16
|
+
|
|
17
|
+
- Identify the actual build system and presets, compiler and language level,
|
|
18
|
+
package/runtime dependencies, generated-source steps, and supported build
|
|
19
|
+
configurations without substituting a preferred CMake or Meson layout.
|
|
20
|
+
- Record exact configure/build/test commands and the executable argv, workdir,
|
|
21
|
+
host/port handling, HTTP readiness predicate, and signal/shutdown contract for
|
|
22
|
+
every web-service target.
|
|
23
|
+
- Preserve public headers, ABI and platform assumptions. Treat databases,
|
|
24
|
+
queues, TLS material, and external service credentials as explicit Resources,
|
|
25
|
+
not implicit properties of the native binary.
|
|
26
|
+
|
|
15
27
|
## Deslop
|
|
16
28
|
|
|
17
29
|
- Preserve public header boundaries, ABI expectations, build configuration,
|
|
@@ -35,6 +35,16 @@ JSKIT MySQL runtime, live-schema CRUD generation, and persistence conventions.
|
|
|
35
35
|
}
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
+
## Adoption
|
|
39
|
+
|
|
40
|
+
- Confirm that the application's effective MySQL environment alternatives and
|
|
41
|
+
driver match this Resource declaration; add a project Resource contract when
|
|
42
|
+
the existing application legitimately differs.
|
|
43
|
+
- Verify from source that the database preparation path discovers every
|
|
44
|
+
package-owned migration, preserves the existing ledger, orders cross-package
|
|
45
|
+
constraints safely, and invokes an app-owned idempotent seed. Do not execute
|
|
46
|
+
it during adoption.
|
|
47
|
+
|
|
38
48
|
## Deslop
|
|
39
49
|
|
|
40
50
|
- Use the installed JSKIT database runtime and generated resource/repository
|
|
@@ -35,6 +35,16 @@ JSKIT PostgreSQL runtime, live-schema CRUD generation, and persistence conventio
|
|
|
35
35
|
}
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
+
## Adoption
|
|
39
|
+
|
|
40
|
+
- Confirm that the application's effective PostgreSQL environment alternatives
|
|
41
|
+
and driver match this Resource declaration; add a project Resource contract
|
|
42
|
+
when the existing application legitimately differs.
|
|
43
|
+
- Verify from source that the database preparation path discovers every
|
|
44
|
+
package-owned migration, preserves the existing ledger, orders cross-package
|
|
45
|
+
constraints safely, and invokes an app-owned idempotent seed. Do not execute
|
|
46
|
+
it during adoption.
|
|
47
|
+
|
|
38
48
|
## Deslop
|
|
39
49
|
|
|
40
50
|
- Use the installed JSKIT database runtime and generated resource/repository
|
package/stacks/pieces/jskit.md
CHANGED
|
@@ -27,6 +27,29 @@ application-owned composition.
|
|
|
27
27
|
user-command failures through JSKIT's shared non-layout-shifting action
|
|
28
28
|
feedback/snackbar path.
|
|
29
29
|
|
|
30
|
+
## Adoption
|
|
31
|
+
|
|
32
|
+
- Read the installed JSKIT skill's existing-application migration guidance
|
|
33
|
+
completely before editing Stack or assessing readiness.
|
|
34
|
+
- Inventory the coordinated `@jskit-ai/*` package graph, application-owned
|
|
35
|
+
composition, package-owned migrations, explicit repositories/features/named
|
|
36
|
+
actions, and current client resources without running a broad package update.
|
|
37
|
+
- Compare the real package scripts with JSKIT's current Workspace setup,
|
|
38
|
+
Launch, verification, and Deployment declarations. Older source needs complete
|
|
39
|
+
project-owned overrides until a separately approved port supplies the current
|
|
40
|
+
commands.
|
|
41
|
+
- Inspect existing setup and database preparation for both migration discovery
|
|
42
|
+
and an app-owned idempotent seed. Never infer `db:prepare` from the selected
|
|
43
|
+
component when the script or preparation file does not exist.
|
|
44
|
+
- When authenticated Preview exists, preserve the app-owned Preview identity
|
|
45
|
+
command, selectors, protocol, environment names, runtime, and timeout in the
|
|
46
|
+
project's Launch declaration. A host-saved identity name/value is not a
|
|
47
|
+
substitute for this application contract.
|
|
48
|
+
- Treat generator descriptors, receipts, sync commands, legacy service
|
|
49
|
+
locators, and retired tool manifests as migration evidence only. Translate
|
|
50
|
+
live facts into current source/Stack and report remaining port work; do not
|
|
51
|
+
add compatibility readers or claim adoption performed the port.
|
|
52
|
+
|
|
30
53
|
## Workspace setup
|
|
31
54
|
|
|
32
55
|
- Prepare `Install dependencies` with `nodejs` when `package.json` exists: `npm` `install`
|