genesis-compiler 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/.agents/plugins/marketplace.json +20 -0
  2. package/README.md +423 -0
  3. package/bin/genesis.js +15 -0
  4. package/docs/assurance-model.md +26 -0
  5. package/docs/prompt-integration.md +92 -0
  6. package/docs/stack-components.md +269 -0
  7. package/package.json +56 -7
  8. package/plugins/genesis/.codex-plugin/plugin.json +19 -0
  9. package/plugins/genesis/hooks.json +18 -0
  10. package/prompts/blueprint.txt +9 -0
  11. package/prompts/describe.txt +17 -0
  12. package/prompts/deslop.txt +14 -0
  13. package/prompts/program.txt +12 -0
  14. package/prompts/reconcile.txt +12 -0
  15. package/prompts/review.txt +12 -0
  16. package/prompts/work.txt +21 -0
  17. package/skills/genesis-deslop/SKILL.md +36 -0
  18. package/skills/genesis-deslop/agents/openai.yaml +4 -0
  19. package/skills/genesis-program/SKILL.md +66 -0
  20. package/skills/genesis-program/agents/openai.yaml +4 -0
  21. package/skills/genesis-project/SKILL.md +53 -0
  22. package/skills/genesis-project/agents/openai.yaml +4 -0
  23. package/src/cli.js +276 -0
  24. package/src/index/agent-skills.js +425 -0
  25. package/src/index/assets.js +18 -0
  26. package/src/index/blueprint.js +38 -0
  27. package/src/index/check.js +102 -0
  28. package/src/index/code-index.js +283 -0
  29. package/src/index/code-indexers/ast-grep.js +414 -0
  30. package/src/index/codex-hooks.js +367 -0
  31. package/src/index/codex-plugin.js +73 -0
  32. package/src/index/context.js +137 -0
  33. package/src/index/errors.js +26 -0
  34. package/src/index/git.js +26 -0
  35. package/src/index/init.js +48 -0
  36. package/src/index/launch.js +34 -0
  37. package/src/index/paths.js +10 -0
  38. package/src/index/process.js +78 -0
  39. package/src/index/program.js +181 -0
  40. package/src/index/project-files.js +24 -0
  41. package/src/index/project-state.js +87 -0
  42. package/src/index/prompt.js +239 -0
  43. package/src/index/stack-catalog.js +72 -0
  44. package/src/index/stack-command.js +65 -0
  45. package/src/index/stack-composition.js +38 -0
  46. package/src/index/stack-launch.js +428 -0
  47. package/src/index/stack-piece.js +277 -0
  48. package/src/index/stack-preflight.js +25 -0
  49. package/src/index/stack-process.js +25 -0
  50. package/src/index/stack-workspace-setup.js +117 -0
  51. package/src/index/stack.js +272 -0
  52. package/src/index/utils.js +85 -0
  53. package/src/index/verification.js +77 -0
  54. package/src/index/workspace-setup.js +30 -0
  55. package/src/index.js +97 -0
  56. package/stacks/pieces/cpp.md +22 -0
  57. package/stacks/pieces/csharp.md +22 -0
  58. package/stacks/pieces/go.md +22 -0
  59. package/stacks/pieces/java.md +22 -0
  60. package/stacks/pieces/jskit-mysql.md +37 -0
  61. package/stacks/pieces/jskit.md +66 -0
  62. package/stacks/pieces/kotlin.md +22 -0
  63. package/stacks/pieces/mysql.md +18 -0
  64. package/stacks/pieces/nodejs.md +25 -0
  65. package/stacks/pieces/php.md +23 -0
  66. package/stacks/pieces/python.md +23 -0
  67. package/stacks/pieces/ruby.md +22 -0
  68. package/stacks/pieces/rust.md +22 -0
  69. package/stacks/pieces/shell.md +23 -0
  70. package/stacks/pieces/vue.md +19 -0
@@ -0,0 +1,18 @@
1
+ import { readFile } from 'node:fs/promises';
2
+
3
+ const root = new URL('../../', import.meta.url);
4
+ const assets = {
5
+ work: new URL('prompts/work.txt', root),
6
+ describe: new URL('prompts/describe.txt', root),
7
+ reconcile: new URL('prompts/reconcile.txt', root),
8
+ deslop: new URL('prompts/deslop.txt', root),
9
+ program: new URL('prompts/program.txt', root),
10
+ blueprint: new URL('prompts/blueprint.txt', root),
11
+ review: new URL('prompts/review.txt', root),
12
+ };
13
+
14
+ export async function readInstalledAsset(name) {
15
+ const location = assets[name];
16
+ if (!location) throw new TypeError(`Unknown Genesis asset: ${name}.`);
17
+ return readFile(location, 'utf8');
18
+ }
@@ -0,0 +1,38 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+
4
+ import { GenesisError } from './errors.js';
5
+ import { BLUEPRINT_PATH } from './paths.js';
6
+ import { normalizeSource } from './utils.js';
7
+
8
+ export const BLUEPRINT_SKELETON_SOURCE = '# Blueprint\n\n';
9
+
10
+ export function parseBlueprintSource(value, { requireDescription = false } = {}) {
11
+ const source = normalizeSource(value);
12
+ if (!/^# Blueprint[ \t]*$/mu.test(source)) {
13
+ throw new GenesisError('BLUEPRINT_INVALID', 'Blueprint needs one `# Blueprint` title.', {
14
+ path: BLUEPRINT_PATH,
15
+ });
16
+ }
17
+ const description = source.replace(/^# Blueprint[ \t]*\n?/mu, '').trim();
18
+ if (requireDescription && !description) {
19
+ throw new GenesisError('BLUEPRINT_INVALID', 'Blueprint needs a product description.', {
20
+ path: BLUEPRINT_PATH,
21
+ });
22
+ }
23
+ return { path: BLUEPRINT_PATH, source, description };
24
+ }
25
+
26
+ export async function readBlueprint(projectRoot, { required = false, requireDescription = false } = {}) {
27
+ try {
28
+ return parseBlueprintSource(await readFile(path.join(projectRoot, BLUEPRINT_PATH), 'utf8'), {
29
+ requireDescription,
30
+ });
31
+ } catch (error) {
32
+ if (['ENOENT', 'ENOTDIR'].includes(error?.code) && !required) return null;
33
+ if (['ENOENT', 'ENOTDIR'].includes(error?.code)) {
34
+ throw new GenesisError('BLUEPRINT_REQUIRED', `Blueprint does not exist: ${BLUEPRINT_PATH}.`);
35
+ }
36
+ throw error;
37
+ }
38
+ }
@@ -0,0 +1,102 @@
1
+ import { readBlueprint } from './blueprint.js';
2
+ import { inspectProjectSkills } from './agent-skills.js';
3
+ import { readStack } from './stack.js';
4
+ import { asDiagnostic } from './errors.js';
5
+ import { gitContext } from './git.js';
6
+ import { inspectProgram } from './program.js';
7
+ import { inspectVerification } from './project-state.js';
8
+ import { missingStackResources } from './stack-preflight.js';
9
+
10
+ function invalidResult(area, error) {
11
+ return {
12
+ status: 'invalid',
13
+ blueprint: area === 'blueprint' ? 'invalid' : 'valid',
14
+ stack: area === 'stack' ? 'invalid' : 'unknown',
15
+ skills: area === 'skills' ? 'invalid' : 'unknown',
16
+ program: 'unknown',
17
+ resources: 'unknown',
18
+ verification: 'unknown',
19
+ programFiles: [],
20
+ subsystems: [],
21
+ diagnostics: [asDiagnostic(error)],
22
+ guidance: error.message,
23
+ };
24
+ }
25
+
26
+ export async function checkProject({ environment = process.env, projectRoot } = {}) {
27
+ const root = (await gitContext(projectRoot)).repositoryRoot;
28
+ try {
29
+ await readBlueprint(root, { required: true, requireDescription: true });
30
+ } catch (error) {
31
+ return invalidResult('blueprint', error);
32
+ }
33
+
34
+ let stack;
35
+ try {
36
+ stack = await readStack(root);
37
+ } catch (error) {
38
+ return invalidResult('stack', error);
39
+ }
40
+
41
+ let program;
42
+ try {
43
+ program = await inspectProgram(root);
44
+ } catch (error) {
45
+ program = {
46
+ status: 'invalid',
47
+ files: [],
48
+ subsystems: [],
49
+ diagnostic: asDiagnostic(error),
50
+ };
51
+ }
52
+ let skills;
53
+ try {
54
+ skills = await inspectProjectSkills({ projectRoot: root, stack });
55
+ } catch (error) {
56
+ skills = { status: 'invalid', diagnostics: [asDiagnostic(error)], skills: [] };
57
+ }
58
+ const missingResources = missingStackResources({ environment, resources: stack.resources });
59
+ const verification = await inspectVerification({ projectRoot: root, stack });
60
+ const diagnostics = [
61
+ ...(program.diagnostic ? [program.diagnostic] : []),
62
+ ...skills.diagnostics,
63
+ ...missingResources,
64
+ ...(verification.status === 'invalid' ? [{
65
+ code: 'VERIFICATION_EVIDENCE_INVALID',
66
+ message: 'Saved verification evidence is malformed.',
67
+ }] : []),
68
+ ];
69
+
70
+ const guidance = [];
71
+ if (program.status === 'missing') guidance.push('Generate an explanatory Program prompt with genesis prompt --task program.');
72
+ if (program.status === 'invalid') guidance.push('Repair the Program with genesis prompt --task program.');
73
+ if (skills.status === 'missing') guidance.push('Run genesis init to install selected project Agent Skills.');
74
+ if (skills.status === 'invalid') guidance.push('Repair the reported Agent Skill or managed-skill manifest.');
75
+ if (missingResources.length > 0) {
76
+ guidance.push(`${missingResources.map(({ message }) => message).join(' ')} Prompt generation remains available.`);
77
+ }
78
+ if (['missing', 'stale'].includes(verification.status)) guidance.push('Run genesis verify to refresh concrete evidence.');
79
+ if (verification.status === 'unconfigured') guidance.push('Add project verification commands to genesis/stack.md.');
80
+ guidance.push('Use genesis prompt --task review for a semantic, evidence-based comparison.');
81
+
82
+ const needsAttention = program.status === 'missing'
83
+ || skills.status === 'missing'
84
+ || missingResources.length > 0
85
+ || ['missing', 'stale', 'unconfigured'].includes(verification.status);
86
+
87
+ return {
88
+ status: program.status === 'invalid' || skills.status === 'invalid' || verification.status === 'invalid'
89
+ ? 'invalid'
90
+ : needsAttention ? 'attention' : 'ok',
91
+ blueprint: 'valid',
92
+ stack: 'valid',
93
+ skills: skills.status,
94
+ program: program.status,
95
+ resources: missingResources.length > 0 ? 'missing' : 'inputs-present',
96
+ verification: verification.status,
97
+ programFiles: program.files,
98
+ subsystems: program.subsystems,
99
+ diagnostics,
100
+ guidance: guidance.join(' '),
101
+ };
102
+ }
@@ -0,0 +1,283 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+
4
+ import { astGrepCodeIndexers } from './code-indexers/ast-grep.js';
5
+ import { asDiagnostic } from './errors.js';
6
+ import { gitContext } from './git.js';
7
+ import { isProjectContentPath } from './paths.js';
8
+ import { inspectProgram } from './program.js';
9
+ import { gitVisibleFileStates } from './project-files.js';
10
+ import { readStack } from './stack.js';
11
+ import { sha256, stableJson, writeFileAtomic } from './utils.js';
12
+
13
+ export const MACHINE_CITY_PATH = '.genesis/machine-city.json';
14
+ export const PROGRAM_CITY_PATH = '.genesis/program-city.json';
15
+
16
+ const INDEXERS = astGrepCodeIndexers;
17
+
18
+ function directoryId(value) {
19
+ return `directory:${value || '.'}`;
20
+ }
21
+
22
+ function fileId(value) {
23
+ return `file:${value}`;
24
+ }
25
+
26
+ function subsystemId(value) {
27
+ return `subsystem:${value}`;
28
+ }
29
+
30
+ function operationId(module) {
31
+ return `operation:${module.subsystem}/${module.name}`;
32
+ }
33
+
34
+ function parentDirectory(value) {
35
+ const parent = path.posix.dirname(value);
36
+ return parent === '.' ? '' : parent;
37
+ }
38
+
39
+ function directoryRecords(paths) {
40
+ const directories = new Set(['']);
41
+ for (const value of paths) {
42
+ let current = parentDirectory(value);
43
+ while (true) {
44
+ directories.add(current);
45
+ if (!current) break;
46
+ current = parentDirectory(current);
47
+ }
48
+ }
49
+ return [...directories].sort().map((directory) => ({
50
+ id: directoryId(directory),
51
+ path: directory,
52
+ title: directory ? path.posix.basename(directory) : 'Project',
53
+ parentId: directory ? directoryId(parentDirectory(directory)) : null,
54
+ }));
55
+ }
56
+
57
+ function subsystemRecords(subsystems) {
58
+ const paths = new Set();
59
+ for (const subsystem of subsystems) {
60
+ const segments = subsystem.split('/');
61
+ for (let count = 1; count <= segments.length; count += 1) {
62
+ paths.add(segments.slice(0, count).join('/'));
63
+ }
64
+ }
65
+ return [...paths].sort().map((subsystem) => ({
66
+ id: subsystemId(subsystem),
67
+ path: subsystem,
68
+ title: path.posix.basename(subsystem).replace(/-/gu, ' '),
69
+ parentId: subsystem.includes('/') ? subsystemId(parentDirectory(subsystem)) : null,
70
+ }));
71
+ }
72
+
73
+ function normalizedFunction(entry, file, extractor) {
74
+ const qualifiedName = entry.qualifiedName || entry.name;
75
+ return {
76
+ id: `function:${file.path}:${qualifiedName}:${entry.kind}:${entry.line}:${entry.column}`,
77
+ name: entry.name,
78
+ qualifiedName,
79
+ kind: entry.kind,
80
+ visibility: entry.visibility,
81
+ container: entry.container || null,
82
+ path: file.path,
83
+ fileId: fileId(file.path),
84
+ line: entry.line,
85
+ column: entry.column,
86
+ parameters: entry.parameters || [],
87
+ async: entry.async === true,
88
+ generator: entry.generator === true,
89
+ static: entry.static === true,
90
+ role: file.role,
91
+ language: file.language,
92
+ extractor,
93
+ };
94
+ }
95
+
96
+ function mergeIndexedFiles(contributions) {
97
+ const files = new Map();
98
+ const functions = [];
99
+ for (const contribution of contributions) {
100
+ for (const file of contribution.files || []) {
101
+ const existing = files.get(file.path);
102
+ const record = existing || {
103
+ id: fileId(file.path),
104
+ path: file.path,
105
+ language: file.language,
106
+ role: file.role,
107
+ mode: file.mode,
108
+ bytes: file.bytes,
109
+ lines: file.lines,
110
+ hash: file.hash,
111
+ extractors: [],
112
+ functionIds: [],
113
+ };
114
+ if (!record.extractors.includes(contribution.extractor)) record.extractors.push(contribution.extractor);
115
+ for (const entry of file.functions || []) {
116
+ const normalized = normalizedFunction(entry, file, contribution.extractor);
117
+ functions.push(normalized);
118
+ record.functionIds.push(normalized.id);
119
+ }
120
+ files.set(file.path, record);
121
+ }
122
+ }
123
+ return {
124
+ files: [...files.values()].sort((left, right) => left.path.localeCompare(right.path)),
125
+ functions: functions.sort((left, right) => (
126
+ left.path.localeCompare(right.path) || left.line - right.line || left.qualifiedName.localeCompare(right.qualifiedName)
127
+ )),
128
+ };
129
+ }
130
+
131
+ function machineCity({ components, contributions, diagnostics, indexers }) {
132
+ const merged = mergeIndexedFiles(contributions);
133
+ const visibility = new Map(merged.functions.map((entry) => [entry.id, entry.visibility]));
134
+ const codeHash = sha256(stableJson(merged.files.map(({ path: filePath, hash, mode }) => ({
135
+ path: filePath,
136
+ hash,
137
+ mode,
138
+ }))));
139
+ return {
140
+ schema: 'genesis.machine-city.v1',
141
+ schemaVersion: 1,
142
+ status: indexers.length === 0 ? 'unconfigured' : diagnostics.length > 0 ? 'completed-with-warning' : 'current',
143
+ codeHash,
144
+ stackComponents: components,
145
+ indexers,
146
+ diagnostics,
147
+ functions: merged.functions,
148
+ districts: directoryRecords(merged.files.map(({ path: filePath }) => filePath)),
149
+ buildings: merged.files.map((file) => ({
150
+ ...file,
151
+ districtId: directoryId(parentDirectory(file.path)),
152
+ title: path.posix.basename(file.path),
153
+ publicFunctionCount: file.functionIds.filter((id) => visibility.get(id) === 'public').length,
154
+ internalFunctionCount: file.functionIds.filter((id) => visibility.get(id) === 'internal').length,
155
+ })),
156
+ };
157
+ }
158
+
159
+ async function programCity(projectRoot) {
160
+ try {
161
+ const program = await inspectProgram(projectRoot);
162
+ const operations = program.modules.map((module) => ({
163
+ id: operationId(module),
164
+ name: module.name,
165
+ title: module.title,
166
+ description: module.description,
167
+ publicContract: module.publicContract,
168
+ implementationMap: module.implementationMap,
169
+ path: module.path,
170
+ subsystem: module.subsystem,
171
+ districtId: subsystemId(module.subsystem),
172
+ sources: module.sources,
173
+ sourceFileIds: module.sources.map(fileId),
174
+ }));
175
+ return {
176
+ schema: 'genesis.program-city.v1',
177
+ schemaVersion: 1,
178
+ status: program.status,
179
+ programHash: sha256(stableJson(operations)),
180
+ diagnostics: [],
181
+ districts: subsystemRecords(program.subsystems),
182
+ buildings: operations,
183
+ links: operations.flatMap((operation) => operation.sourceFileIds.map((targetId) => ({
184
+ kind: 'implemented-by',
185
+ fromId: operation.id,
186
+ toId: targetId,
187
+ }))),
188
+ };
189
+ } catch (error) {
190
+ return {
191
+ schema: 'genesis.program-city.v1',
192
+ schemaVersion: 1,
193
+ status: 'invalid',
194
+ programHash: sha256('invalid'),
195
+ diagnostics: [asDiagnostic(error)],
196
+ districts: [],
197
+ buildings: [],
198
+ links: [],
199
+ };
200
+ }
201
+ }
202
+
203
+ async function writeIfChanged(projectRoot, relativePath, value) {
204
+ const location = path.join(projectRoot, relativePath);
205
+ const source = stableJson(value);
206
+ try {
207
+ if (await readFile(location, 'utf8') === source) return false;
208
+ } catch (error) {
209
+ if (!['ENOENT', 'ENOTDIR'].includes(error?.code)) throw error;
210
+ }
211
+ await writeFileAtomic(location, source);
212
+ return true;
213
+ }
214
+
215
+ function functionMatches(entry, queries) {
216
+ if (queries.length === 0) return false;
217
+ const haystack = [entry.name, entry.qualifiedName, entry.path, entry.kind, entry.visibility]
218
+ .join('\n')
219
+ .toLowerCase();
220
+ return queries.every((query) => haystack.includes(query.toLowerCase()));
221
+ }
222
+
223
+ function formatCount(value, singular) {
224
+ return `${value} ${singular}${value === 1 ? '' : 's'}`;
225
+ }
226
+
227
+ export async function buildProjectIndex({ projectRoot, queries = [], write = true } = {}) {
228
+ const root = (await gitContext(projectRoot)).repositoryRoot;
229
+ const stack = await readStack(root);
230
+ const states = await gitVisibleFileStates(root, { includePath: isProjectContentPath });
231
+ const files = [...states]
232
+ .filter(([, state]) => state.exists && !state.symlink && !state.special && state.hash)
233
+ .map(([filePath, state]) => ({ path: filePath, hash: state.hash, mode: state.mode }));
234
+ const indexers = [...new Set(stack.components.flatMap((component) => component.indexers || []))].sort();
235
+ const contributions = [];
236
+ const diagnostics = [];
237
+ for (const id of indexers) {
238
+ const indexer = INDEXERS.get(id);
239
+ if (!indexer) {
240
+ diagnostics.push({ code: 'CODE_INDEXER_UNAVAILABLE', message: `No installed code indexer exists for ${id}.` });
241
+ continue;
242
+ }
243
+ try {
244
+ const contribution = await indexer.extract({ files, projectRoot: root });
245
+ contributions.push({ ...contribution, extractor: id });
246
+ diagnostics.push(...(contribution.diagnostics || []).map((diagnostic) => ({ ...diagnostic, extractor: id })));
247
+ } catch (error) {
248
+ diagnostics.push({ ...asDiagnostic(error), code: 'CODE_INDEXER_FAILED', extractor: id });
249
+ }
250
+ }
251
+ const machine = machineCity({
252
+ components: stack.components.map(({ id }) => id),
253
+ contributions,
254
+ diagnostics,
255
+ indexers,
256
+ });
257
+ const program = await programCity(root);
258
+ const changedFiles = [];
259
+ if (write) {
260
+ if (await writeIfChanged(root, MACHINE_CITY_PATH, machine)) changedFiles.push(MACHINE_CITY_PATH);
261
+ if (await writeIfChanged(root, PROGRAM_CITY_PATH, program)) changedFiles.push(PROGRAM_CITY_PATH);
262
+ }
263
+ const allMatches = machine.functions.filter((entry) => functionMatches(entry, queries));
264
+ const allDiagnostics = [...diagnostics, ...program.diagnostics];
265
+ return {
266
+ status: allDiagnostics.length > 0 ? 'completed-with-warning' : 'ready',
267
+ summary: [
268
+ `Indexed ${formatCount(machine.functions.length, 'function')}`,
269
+ `in ${formatCount(machine.buildings.length, 'file')}`,
270
+ `and ${formatCount(program.buildings.length, 'Program operation')}.`,
271
+ ].join(' '),
272
+ paths: { machine: MACHINE_CITY_PATH, program: PROGRAM_CITY_PATH },
273
+ changedFiles,
274
+ diagnostics: allDiagnostics,
275
+ functionCount: machine.functions.length,
276
+ fileCount: machine.buildings.length,
277
+ operationCount: program.buildings.length,
278
+ totalMatches: allMatches.length,
279
+ matches: allMatches.slice(0, 200),
280
+ machine,
281
+ program,
282
+ };
283
+ }