genesis-compiler 1.2.2 → 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 +56 -20
- package/docs/assurance-model.md +16 -1
- package/docs/preview-identity-command.md +113 -0
- package/docs/prompt-integration.md +22 -14
- package/docs/stack-components.md +112 -22
- package/package.json +4 -2
- 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 +76 -3
- package/src/index/agent-skills.js +25 -4
- package/src/index/assets.js +1 -0
- package/src/index/city-presentation.js +83 -0
- package/src/index/code-index.js +24 -3
- package/src/index/codex-hooks.js +3 -2
- package/src/index/init.js +1 -1
- package/src/index/prompt.js +58 -1
- package/src/index/stack-catalog.js +3 -0
- package/src/index/stack-city-presentation.js +178 -0
- package/src/index/stack-environment-defaults.js +2 -0
- package/src/index/stack-launch.js +5 -6
- package/src/index/stack-piece.js +35 -11
- package/src/index/stack.js +47 -2
- package/src/index/utils.js +1 -1
- package/src/index/workspace-setup.js +76 -0
- package/src/index.js +10 -3
- 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 +34 -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
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
Read and follow `.agents/skills/genesis-project/SKILL.md` and
|
|
2
|
+
`.agents/skills/genesis-program/SKILL.md` completely.
|
|
3
|
+
|
|
4
|
+
This is an existing-project adoption turn. Import the codebase's factual
|
|
5
|
+
product and operational contracts into Genesis without changing application
|
|
6
|
+
behavior. The user has already approved adoption; do not ask them to approve
|
|
7
|
+
Genesis again or require them to understand Genesis terminology.
|
|
8
|
+
|
|
9
|
+
Application source, dependencies, migrations, runtime behavior, and deployment
|
|
10
|
+
files are read-only in this turn. You may edit `genesis/blueprint.md`,
|
|
11
|
+
`genesis/stack.md`, explanatory Markdown below `genesis/program/`, and
|
|
12
|
+
Genesis-managed skills/indexes produced by `genesis stack add` or Genesis
|
|
13
|
+
inspection commands. Do not install dependencies, prepare a database, run
|
|
14
|
+
migrations or seeds, start a server, deploy, or add compatibility code.
|
|
15
|
+
|
|
16
|
+
If Stack has no components, inspect direct evidence such as manifests,
|
|
17
|
+
lockfiles, build files, entrypoints, package scripts, tests, configuration,
|
|
18
|
+
database and migration code, and existing project-tool metadata. Record only
|
|
19
|
+
technologies the implementation already uses with `genesis stack add
|
|
20
|
+
<piece...>`; this is factual import, not a new-technology choice. Ask a concise
|
|
21
|
+
product-language question only when evidence is genuinely ambiguous. Then run
|
|
22
|
+
`genesis prompt --task adopt "Continue the approved existing-project import."`
|
|
23
|
+
and follow the regenerated prompt so every selected Stack piece contributes its
|
|
24
|
+
Adoption requirements.
|
|
25
|
+
|
|
26
|
+
Read every selected technology skill named by the regenerated prompt and its
|
|
27
|
+
relevant existing-application migration reference. Apply all SELECTED STACK
|
|
28
|
+
ADOPTION GUIDANCE. Component defaults describe current foundations and are
|
|
29
|
+
hypotheses until they match this source. They do not upgrade an older project.
|
|
30
|
+
|
|
31
|
+
Build one evidence-backed import inventory covering, where applicable:
|
|
32
|
+
|
|
33
|
+
- package manager, install/build commands, runtimes, workdirs, and generated
|
|
34
|
+
prerequisites;
|
|
35
|
+
- environment files, public defaults, required resources, and alternative
|
|
36
|
+
environment names, without reading or returning secret values;
|
|
37
|
+
- database engine, migration ownership and ledger, preparation command,
|
|
38
|
+
idempotent seed behavior, and fresh versus existing-database safety;
|
|
39
|
+
- every long-running target's exact argv, host/port placeholders, URL path,
|
|
40
|
+
readiness predicate, and shutdown ownership;
|
|
41
|
+
- application-owned Preview identity helper, protocol, supported selectors,
|
|
42
|
+
environment names, runtime, and timeout when authenticated preview exists;
|
|
43
|
+
- concrete verification commands, browser-test assumptions, and deployment
|
|
44
|
+
preparation/build/migration/serve/readiness declarations.
|
|
45
|
+
|
|
46
|
+
Translate observed contracts into exact project-owned Stack sections whenever
|
|
47
|
+
the selected components' defaults differ. A project section replaces the
|
|
48
|
+
component declaration for that concern, so it must be complete. Inspect retired
|
|
49
|
+
or tool-specific manifests as evidence and carry every still-valid fact into
|
|
50
|
+
Genesis; never add a permanent legacy reader or shim. Do not delete an old
|
|
51
|
+
manifest until all of its live facts have been accounted for.
|
|
52
|
+
|
|
53
|
+
Create the non-technical Blueprint and useful subsystem-oriented Program from
|
|
54
|
+
current source and tests. Do not cite archive internals or Git object storage as
|
|
55
|
+
implementation source; Program sources must be current Git-visible files.
|
|
56
|
+
|
|
57
|
+
Run `genesis check` only for structural validation. Missing environment values,
|
|
58
|
+
workspace markers, commands, helpers, seeds, or readiness evidence are honest
|
|
59
|
+
import findings, not permission to invent or execute them. Finish with a clear
|
|
60
|
+
inventory of what was imported, what remains unconfigured or blocked, and what
|
|
61
|
+
would require a separately approved implementation port. Never claim the
|
|
62
|
+
project is ready merely because Stack components were selected.
|
package/prompts/start.txt
CHANGED
|
@@ -23,12 +23,25 @@ When `projectKind` is `existing`, ask what the user wants to understand or
|
|
|
23
23
|
change. Use the current Blueprint, Stack, Program, and code index when present.
|
|
24
24
|
If the existing implementation is not yet explained, recommend the Genesis
|
|
25
25
|
description/adoption path instead of pretending its intent is known.
|
|
26
|
+
When an existing implementation has no selected Stack yet, inspect its actual
|
|
27
|
+
package scripts, build files, setup commands, server entrypoint, and readiness
|
|
28
|
+
route before recording the user's technology choice. Component Workspace setup
|
|
29
|
+
and Launch declarations describe that component's current foundation; selecting
|
|
30
|
+
the component does not port an older implementation to it. If the existing
|
|
31
|
+
commands differ, add exact project-owned `## Workspace setup` and `## Launch`
|
|
32
|
+
sections that replace the component defaults and match the code that exists.
|
|
33
|
+
Never report the inherited setup or Launch as usable merely because the
|
|
34
|
+
component was selected, and never add compatibility implementation without the
|
|
35
|
+
user asking for a port.
|
|
26
36
|
|
|
27
37
|
When `projectKind` is `existing-uninitialized`, ask what the user wants to
|
|
28
|
-
understand or change and strongly recommend
|
|
29
|
-
work. Explain
|
|
30
|
-
|
|
31
|
-
|
|
38
|
+
understand or change and strongly recommend preparing the existing project for
|
|
39
|
+
guided editing before substantial work. Explain in product language that this
|
|
40
|
+
preserves the implementation while importing its current setup, run, database,
|
|
41
|
+
preview, verification, and product context. Do not require the user to know
|
|
42
|
+
Genesis terminology or type a command. If they approve, run `genesis adopt`
|
|
43
|
+
yourself and follow its adoption prompt. If they decline, continue normally and
|
|
44
|
+
do not repeat the recommendation.
|
|
32
45
|
|
|
33
46
|
If the request already contains an unambiguous product description and an
|
|
34
47
|
explicit Stack choice, you may apply that choice and establish the Blueprint.
|
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,8 +9,13 @@ import {
|
|
|
9
9
|
getContext,
|
|
10
10
|
indexCodebase,
|
|
11
11
|
initialize,
|
|
12
|
+
inspectDeployment,
|
|
13
|
+
inspectEnvironment,
|
|
14
|
+
inspectLaunch,
|
|
15
|
+
inspectWorkspaceSetup,
|
|
12
16
|
installCodex,
|
|
13
17
|
listStackPieces,
|
|
18
|
+
prepareWorkspace,
|
|
14
19
|
verify,
|
|
15
20
|
} from './index.js';
|
|
16
21
|
import {
|
|
@@ -30,8 +35,10 @@ const USAGE = `Usage:
|
|
|
30
35
|
genesis stack add <piece...>
|
|
31
36
|
genesis context <path...>
|
|
32
37
|
genesis index [function-or-path...]
|
|
38
|
+
genesis inspect <workspace|environment|launch|deployment>
|
|
39
|
+
genesis prepare
|
|
33
40
|
genesis prompt [request...]
|
|
34
|
-
genesis prompt --task <start|work|deslop|program|blueprint|describe|review> [request...]
|
|
41
|
+
genesis prompt --task <start|adopt|work|deslop|program|blueprint|describe|review> [request...]
|
|
35
42
|
genesis verify
|
|
36
43
|
genesis check
|
|
37
44
|
|
|
@@ -46,7 +53,7 @@ prompt to the agent you already use. Review all edits through the ordinary Git
|
|
|
46
53
|
diff, then run genesis verify for the Stack's concrete checks.
|
|
47
54
|
`;
|
|
48
55
|
|
|
49
|
-
const COMMANDS = new Set(['adopt', 'check', 'codex', 'context', 'hook', 'index', 'init', 'prompt', 'stack', 'verify']);
|
|
56
|
+
const COMMANDS = new Set(['adopt', 'check', 'codex', 'context', 'hook', 'index', 'init', 'inspect', 'prepare', 'prompt', 'stack', 'verify']);
|
|
50
57
|
|
|
51
58
|
function parseCommand(argv) {
|
|
52
59
|
if (argv.length === 0 || argv.includes('--help') || argv.includes('-h') || argv[0] === 'help') {
|
|
@@ -94,9 +101,16 @@ function parseCommand(argv) {
|
|
|
94
101
|
}
|
|
95
102
|
} else if (command === 'context' && operands.length === 0) {
|
|
96
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
|
+
}
|
|
97
111
|
} else if (command === 'hook' && (operands.length !== 1 || !['begin', 'discover', 'end', 'session', 'stop'].includes(operands[0]))) {
|
|
98
112
|
fail('CLI_HOOK_ACTION_REQUIRED', 'Command hook requires exactly one of: discover, session, begin, stop, end.');
|
|
99
|
-
} 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) {
|
|
100
114
|
fail('CLI_EXTRA_ARGUMENT', `Command ${command} accepts no arguments.`);
|
|
101
115
|
}
|
|
102
116
|
return { command, operands, options };
|
|
@@ -126,6 +140,31 @@ function writeCheck(result) {
|
|
|
126
140
|
line(process.stdout, `Check: ${result.status}`);
|
|
127
141
|
}
|
|
128
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
|
+
|
|
129
168
|
function writeResult(command, result) {
|
|
130
169
|
if (command === 'prompt') {
|
|
131
170
|
process.stdout.write(result.prompt.endsWith('\n') ? result.prompt : `${result.prompt}\n`);
|
|
@@ -165,6 +204,23 @@ function writeResult(command, result) {
|
|
|
165
204
|
line(process.stdout, `index: ${result.status}`);
|
|
166
205
|
return;
|
|
167
206
|
}
|
|
207
|
+
if (command === 'inspect') {
|
|
208
|
+
writeInspection(result);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
if (command === 'prepare') {
|
|
212
|
+
if (result.summary) line(process.stdout, result.summary);
|
|
213
|
+
namedItems('Commands', result.commands?.map(({ label, argv }) => `${label}: ${argv.join(' ')}`));
|
|
214
|
+
for (const item of result.diagnostics || []) {
|
|
215
|
+
line(process.stdout, `${item.code}: ${item.message}`);
|
|
216
|
+
for (const field of ['stdout', 'stderr']) {
|
|
217
|
+
const output = item.details?.[field]?.trim();
|
|
218
|
+
if (output) line(process.stdout, output);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
line(process.stdout, `prepare: ${result.status}`);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
168
224
|
if (command === 'hook') {
|
|
169
225
|
if (['discover', 'session'].includes(result.kind) && result.output) line(process.stdout, result.output);
|
|
170
226
|
else if (result.kind === 'stop') line(process.stdout, JSON.stringify(result.output));
|
|
@@ -226,6 +282,23 @@ async function execute({ command, operands, options }) {
|
|
|
226
282
|
}
|
|
227
283
|
if (command === 'context') return getContext({ paths: operands, projectRoot });
|
|
228
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
|
+
}
|
|
294
|
+
if (command === 'prepare') {
|
|
295
|
+
return prepareWorkspace({
|
|
296
|
+
projectRoot,
|
|
297
|
+
onEvent: (event) => {
|
|
298
|
+
if (!options.json) line(process.stderr, event.message);
|
|
299
|
+
},
|
|
300
|
+
});
|
|
301
|
+
}
|
|
229
302
|
if (command === 'hook') {
|
|
230
303
|
if (operands[0] === 'discover') {
|
|
231
304
|
return { kind: 'discover', ...await codexAdoptionRecommendation({ projectRoot }) };
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
3
|
import {
|
|
4
|
-
cp,
|
|
5
4
|
lstat,
|
|
6
5
|
mkdir,
|
|
7
6
|
readFile,
|
|
8
7
|
readdir,
|
|
9
8
|
rename,
|
|
10
9
|
rm,
|
|
10
|
+
writeFile,
|
|
11
11
|
} from 'node:fs/promises';
|
|
12
12
|
import path from 'node:path';
|
|
13
13
|
import { fileURLToPath } from 'node:url';
|
|
@@ -92,7 +92,7 @@ async function skillTree(directory, relative = '') {
|
|
|
92
92
|
files.push({
|
|
93
93
|
path: child,
|
|
94
94
|
hash: sha256(content),
|
|
95
|
-
mode: (info.mode &
|
|
95
|
+
mode: (info.mode & 0o100) === 0 ? 0o100644 : 0o100755,
|
|
96
96
|
});
|
|
97
97
|
}
|
|
98
98
|
return files;
|
|
@@ -251,7 +251,7 @@ async function replaceSkill(source, target) {
|
|
|
251
251
|
await mkdir(path.dirname(target), { recursive: true });
|
|
252
252
|
let replaced = false;
|
|
253
253
|
try {
|
|
254
|
-
await
|
|
254
|
+
await copySkillTree(source, temporary);
|
|
255
255
|
try {
|
|
256
256
|
await rename(target, backup);
|
|
257
257
|
replaced = true;
|
|
@@ -271,6 +271,23 @@ async function replaceSkill(source, target) {
|
|
|
271
271
|
}
|
|
272
272
|
}
|
|
273
273
|
|
|
274
|
+
async function copySkillTree(source, target) {
|
|
275
|
+
const info = await lstat(source);
|
|
276
|
+
if (info.isSymbolicLink() || (!info.isDirectory() && !info.isFile())) {
|
|
277
|
+
invalidSkill(`Agent Skill contains a non-ordinary entry: ${source}.`);
|
|
278
|
+
}
|
|
279
|
+
if (info.isDirectory()) {
|
|
280
|
+
await mkdir(target, { mode: 0o777 });
|
|
281
|
+
const entries = await readdir(source, { withFileTypes: true });
|
|
282
|
+
for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
283
|
+
await copySkillTree(path.join(source, entry.name), path.join(target, entry.name));
|
|
284
|
+
}
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
const mode = (info.mode & 0o100) === 0 ? 0o666 : 0o777;
|
|
288
|
+
await writeFile(target, await readFile(source), { flag: 'wx', mode });
|
|
289
|
+
}
|
|
290
|
+
|
|
274
291
|
function targetFiles(skill) {
|
|
275
292
|
return skill.files.map(({ path: file }) => `${SKILLS_ROOT}/${skill.name}/${file}`);
|
|
276
293
|
}
|
|
@@ -304,6 +321,10 @@ export async function syncProjectSkills({ projectRoot, stack } = {}) {
|
|
|
304
321
|
continue;
|
|
305
322
|
}
|
|
306
323
|
if (installed.hash !== managed.hash) {
|
|
324
|
+
if (installed.hash === skill.hash) {
|
|
325
|
+
manifest.value.skills[skill.name] = { hash: skill.hash, source: skill.source };
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
307
328
|
diagnostics.push({
|
|
308
329
|
code: 'AGENT_SKILL_CUSTOMIZED',
|
|
309
330
|
message: `Preserved locally modified Agent Skill ${skill.name}.`,
|
|
@@ -388,7 +409,7 @@ export async function inspectProjectSkills({ projectRoot, stack } = {}) {
|
|
|
388
409
|
message: `Project Agent Skill ${expected.name} is externally managed; Genesis preserved it.`,
|
|
389
410
|
details: { name: expected.name, path: `${SKILLS_ROOT}/${expected.name}/SKILL.md` },
|
|
390
411
|
});
|
|
391
|
-
} else if (installed.hash !== managed.hash) {
|
|
412
|
+
} else if (installed.hash !== managed.hash && installed.hash !== expected.hash) {
|
|
392
413
|
customized = true;
|
|
393
414
|
diagnostics.push({
|
|
394
415
|
code: 'AGENT_SKILL_CUSTOMIZED',
|
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),
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
|
|
3
|
+
function presentationRegionForPath(filePath, regions) {
|
|
4
|
+
return regions.find((region) => (
|
|
5
|
+
!region.fallback
|
|
6
|
+
&& (filePath === region.pathPrefix || filePath.startsWith(`${region.pathPrefix}/`))
|
|
7
|
+
)) || regions.find(({ fallback }) => fallback) || null;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function parentDirectory(filePath) {
|
|
11
|
+
const parent = path.posix.dirname(filePath);
|
|
12
|
+
return parent === '.' ? '' : parent;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function presentationCampusPath(filePath, region, { file = false } = {}) {
|
|
16
|
+
const ownershipPath = file ? parentDirectory(filePath) : filePath;
|
|
17
|
+
if (!region || region.fallback) {
|
|
18
|
+
return ownershipPath.split('/').filter(Boolean)[0] || '';
|
|
19
|
+
}
|
|
20
|
+
if (ownershipPath === region.pathPrefix) return region.pathPrefix;
|
|
21
|
+
const relative = ownershipPath.slice(region.pathPrefix.length + 1);
|
|
22
|
+
const child = relative.split('/').filter(Boolean)[0];
|
|
23
|
+
return child ? `${region.pathPrefix}/${child}` : region.pathPrefix;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function campusId(regionId, campusPath) {
|
|
27
|
+
return `presentation-campus:${regionId}:${campusPath || '.'}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Resolves Stack-declared City regions and campus roots into Machine City facts. */
|
|
31
|
+
export function buildCityPresentation({ districts = [], files = [], regions = [] } = {}) {
|
|
32
|
+
if (regions.length === 0) {
|
|
33
|
+
return {
|
|
34
|
+
campuses: [],
|
|
35
|
+
placementForPath: () => ({ presentationCampusId: null, presentationRegionId: null }),
|
|
36
|
+
regions: [],
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
const districtsByPath = new Map(districts.map((district) => [district.path, district]));
|
|
40
|
+
const placements = new Map();
|
|
41
|
+
const mutableCampuses = new Map();
|
|
42
|
+
const mutableRegions = new Map(regions.map((region) => [region.id, {
|
|
43
|
+
...region,
|
|
44
|
+
buildingCount: 0,
|
|
45
|
+
}]));
|
|
46
|
+
|
|
47
|
+
function resolvePlacement(filePath = '', { file = false } = {}) {
|
|
48
|
+
if (placements.has(filePath)) return placements.get(filePath);
|
|
49
|
+
const region = presentationRegionForPath(filePath, regions);
|
|
50
|
+
const campusPath = presentationCampusPath(filePath, region, { file });
|
|
51
|
+
const placement = {
|
|
52
|
+
presentationCampusId: campusId(region.id, campusPath),
|
|
53
|
+
presentationRegionId: region.id,
|
|
54
|
+
};
|
|
55
|
+
placements.set(filePath, placement);
|
|
56
|
+
if (!mutableCampuses.has(placement.presentationCampusId)) {
|
|
57
|
+
mutableCampuses.set(placement.presentationCampusId, {
|
|
58
|
+
id: placement.presentationCampusId,
|
|
59
|
+
regionId: region.id,
|
|
60
|
+
path: campusPath,
|
|
61
|
+
title: campusPath ? path.posix.basename(campusPath) : 'Project root',
|
|
62
|
+
districtId: districtsByPath.get(campusPath)?.id || null,
|
|
63
|
+
buildingCount: 0,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return placement;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
for (const file of files) {
|
|
70
|
+
const placement = resolvePlacement(file.path, { file: true });
|
|
71
|
+
mutableRegions.get(placement.presentationRegionId).buildingCount += 1;
|
|
72
|
+
mutableCampuses.get(placement.presentationCampusId).buildingCount += 1;
|
|
73
|
+
}
|
|
74
|
+
for (const district of districts) resolvePlacement(district.path);
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
campuses: [...mutableCampuses.values()]
|
|
78
|
+
.filter(({ buildingCount }) => buildingCount > 0)
|
|
79
|
+
.sort((left, right) => left.id.localeCompare(right.id)),
|
|
80
|
+
placementForPath: (filePath = '') => placements.get(filePath) || resolvePlacement(filePath),
|
|
81
|
+
regions: [...mutableRegions.values()],
|
|
82
|
+
};
|
|
83
|
+
}
|
package/src/index/code-index.js
CHANGED
|
@@ -3,6 +3,8 @@ import path from 'node:path';
|
|
|
3
3
|
|
|
4
4
|
import { astGrepCodeIndexers } from './code-indexers/ast-grep.js';
|
|
5
5
|
import { asDiagnostic } from './errors.js';
|
|
6
|
+
import { buildCityPresentation } from './city-presentation.js';
|
|
7
|
+
import { cityPathExcluded } from './stack-city-presentation.js';
|
|
6
8
|
import { gitContext } from './git.js';
|
|
7
9
|
import { isProjectContentPath } from './paths.js';
|
|
8
10
|
import { inspectProgram } from './program.js';
|
|
@@ -128,8 +130,14 @@ function mergeIndexedFiles(contributions) {
|
|
|
128
130
|
};
|
|
129
131
|
}
|
|
130
132
|
|
|
131
|
-
function machineCity({ components, contributions, diagnostics, indexers }) {
|
|
133
|
+
function machineCity({ cityRegions, components, contributions, diagnostics, indexers }) {
|
|
132
134
|
const merged = mergeIndexedFiles(contributions);
|
|
135
|
+
const structuralDistricts = directoryRecords(merged.files.map(({ path: filePath }) => filePath));
|
|
136
|
+
const presentation = buildCityPresentation({
|
|
137
|
+
districts: structuralDistricts,
|
|
138
|
+
files: merged.files,
|
|
139
|
+
regions: cityRegions,
|
|
140
|
+
});
|
|
133
141
|
const visibility = new Map(merged.functions.map((entry) => [entry.id, entry.visibility]));
|
|
134
142
|
const codeHash = sha256(stableJson(merged.files.map(({ path: filePath, hash, mode }) => ({
|
|
135
143
|
path: filePath,
|
|
@@ -141,13 +149,19 @@ function machineCity({ components, contributions, diagnostics, indexers }) {
|
|
|
141
149
|
schemaVersion: 1,
|
|
142
150
|
status: indexers.length === 0 ? 'unconfigured' : diagnostics.length > 0 ? 'completed-with-warning' : 'current',
|
|
143
151
|
codeHash,
|
|
152
|
+
presentationRegions: presentation.regions,
|
|
153
|
+
presentationCampuses: presentation.campuses,
|
|
144
154
|
stackComponents: components,
|
|
145
155
|
indexers,
|
|
146
156
|
diagnostics,
|
|
147
157
|
functions: merged.functions,
|
|
148
|
-
districts:
|
|
158
|
+
districts: structuralDistricts.map((district) => ({
|
|
159
|
+
...district,
|
|
160
|
+
...presentation.placementForPath(district.path),
|
|
161
|
+
})),
|
|
149
162
|
buildings: merged.files.map((file) => ({
|
|
150
163
|
...file,
|
|
164
|
+
...presentation.placementForPath(file.path),
|
|
151
165
|
districtId: directoryId(parentDirectory(file.path)),
|
|
152
166
|
title: path.posix.basename(file.path),
|
|
153
167
|
publicFunctionCount: file.functionIds.filter((id) => visibility.get(id) === 'public').length,
|
|
@@ -229,7 +243,13 @@ export async function buildProjectIndex({ projectRoot, queries = [], write = tru
|
|
|
229
243
|
const stack = await readStack(root);
|
|
230
244
|
const states = await gitVisibleFileStates(root, { includePath: isProjectContentPath });
|
|
231
245
|
const files = [...states]
|
|
232
|
-
.filter(([, state]) =>
|
|
246
|
+
.filter(([filePath, state]) => (
|
|
247
|
+
state.exists
|
|
248
|
+
&& !state.symlink
|
|
249
|
+
&& !state.special
|
|
250
|
+
&& state.hash
|
|
251
|
+
&& !cityPathExcluded(filePath, stack.cityExclusions)
|
|
252
|
+
))
|
|
233
253
|
.map(([filePath, state]) => ({ path: filePath, hash: state.hash, mode: state.mode }));
|
|
234
254
|
const indexers = [...new Set(stack.components.flatMap((component) => component.indexers || []))].sort();
|
|
235
255
|
const contributions = [];
|
|
@@ -249,6 +269,7 @@ export async function buildProjectIndex({ projectRoot, queries = [], write = tru
|
|
|
249
269
|
}
|
|
250
270
|
}
|
|
251
271
|
const machine = machineCity({
|
|
272
|
+
cityRegions: stack.cityRegions,
|
|
252
273
|
components: stack.components.map(({ id }) => id),
|
|
253
274
|
contributions,
|
|
254
275
|
diagnostics,
|
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/init.js
CHANGED
|
@@ -12,7 +12,7 @@ async function createIfMissing(projectRoot, relativePath, source) {
|
|
|
12
12
|
const location = path.join(projectRoot, relativePath);
|
|
13
13
|
await mkdir(path.dirname(location), { recursive: true });
|
|
14
14
|
try {
|
|
15
|
-
await writeFile(location, source, { flag: 'wx', mode:
|
|
15
|
+
await writeFile(location, source, { flag: 'wx', mode: 0o666 });
|
|
16
16
|
return relativePath;
|
|
17
17
|
} catch (error) {
|
|
18
18
|
if (error?.code === 'EEXIST') return null;
|
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 {
|
|
@@ -61,8 +61,11 @@ export async function listBuiltinStackPieces() {
|
|
|
61
61
|
id: piece.id,
|
|
62
62
|
description: piece.description,
|
|
63
63
|
guidance: piece.guidance,
|
|
64
|
+
adoption: piece.adoption,
|
|
64
65
|
requires: piece.requires,
|
|
65
66
|
conflicts: piece.conflicts,
|
|
67
|
+
cityExclusions: piece.cityExclusions,
|
|
68
|
+
cityRegions: piece.cityRegions,
|
|
66
69
|
indexers: piece.indexers,
|
|
67
70
|
environmentDefaults: piece.environmentDefaults,
|
|
68
71
|
launchTargets: piece.launchTargets.map(({ id }) => id),
|